How to Segue from a table filled with results from search controller

1k Views Asked by At

The old UISearchDisplayController class is now deprecated and instead we have to use the new UISearchController. There used to be a property in the old class called "SearchResultsTableView" but it's gone from the new class.

I populate a table with data and all works as intended - including segueing each row's details to another scene. I throw a search bar in there (programmatically - using the new searchController) and it successfully reloads the original table with any found results. HOWEVER, when touching a selected row after a search, the segue passed along is that of the original table row that happens to be in the same position of the one touched now! (i.e. if I choose the current second row of a search, the next scene will segue the details of the second row of the original table!) That's because despite the data in the rows are being successfuly repopulated with the search data, the index numbers are still those of the old data.

It used to be with the old type that we would check this as such:

if (self.resultSearchController.active) {
let indexPath = self.searchDisplayController!.searchResultsTableView.indexPathForSelectedRow()
} else {
let indexPath = self.tableView.indexPathForSelectedRow()

So I think that with the old UISearchDisplayController class you actually got a new table, whereas with the new SearchController Class you only get new rows inside the old table? This totaly doesn't make sense !

Here is my full code per request:

import UIKit
import Foundation
class secondTableViewController: UITableViewController, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating {

var filteredTableData = [String]()
var resultSearchController = UISearchController()




//these 2 are standard for the title and subtitle
var TableTitle:Array< String > = Array < String >()
var TableSub:Array< String > = Array < String >()

//the following are for my seque to next scene
var the_fname:Array< String > = Array < String >()
var the_basics:Array< String > = Array < String >()
var the_p_method:Array< String > = Array < String >()
var the_seats:Array< String > = Array < String >()
var the_notes:Array< String > = Array < String >()
var the_tableData:Array< String > = Array < String >()





override func viewDidLoad() {


    tableView.delegate = self
    tableView.dataSource = self


    self.title = currentBus


    super.viewDidLoad()

    self.resultSearchController = ({
        let controller = UISearchController(searchResultsController: nil)



        controller.searchResultsUpdater = self
        controller.dimsBackgroundDuringPresentation = false
        controller.searchBar.sizeToFit()

        self.tableView.tableHeaderView = controller.searchBar

        return controller
    })()

    // Reload the table
    self.tableView.reloadData()




    var url = "http://the_path_to_my_json_file"

    get_data_from_url(url)

}



override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

// MARK: - Table view data source

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    // #warning Potentially incomplete method implementation.
    // Return the number of sections.
    return 1
}


override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // 2
    if (self.resultSearchController.active) {
        return self.filteredTableData.count
    }
    else {
        return TableTitle.count
    }
}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = self.tableView.dequeueReusableCellWithIdentifier("secondtableCell", forIndexPath: indexPath) as! UITableViewCell

    // Configure the cell...
if (self.resultSearchController.active) {
cell.textLabel?.text = filteredTableData[indexPath.row]
//cell.detailTextLabel?.text = TableSub[indexPath.row]
}else{
cell.textLabel?.text = TableTitle[indexPath.row]
cell.detailTextLabel?.text = TableSub[indexPath.row]



    }
    return cell



}


func get_data_from_url(url:String)
{
    let httpMethod = "GET"
    let timeout = 15
    let url = NSURL(string: url)

    let urlRequest = NSMutableURLRequest(URL: url!,
        cachePolicy: .ReloadIgnoringLocalAndRemoteCacheData,
        timeoutInterval: 15.0)
    let queue = NSOperationQueue()
    NSURLConnection.sendAsynchronousRequest(
        urlRequest,
        queue: queue,
        completionHandler: {(response: NSURLResponse!,
            data: NSData!,
            error: NSError!) in
            if data.length > 0 && error == nil{
                let json = NSString(data: data, encoding: NSASCIIStringEncoding)
                self.extract_json(json!)
            }else if data.length == 0 && error == nil{
                println("Nothing was downloaded")
            } else if error != nil{
                println("Error happened = \(error)")
            }
        }
    )
}






func extract_json(data:NSString)
{
    var parseError: NSError?
    let jsonData:NSData = data.dataUsingEncoding(NSASCIIStringEncoding)!
    let json: AnyObject? = NSJSONSerialization.JSONObjectWithData(jsonData, options: nil, error: &parseError)
    if (parseError == nil)
    {
        if let my_pass_list = json as? NSArray
        {
            for (var i = 0; i < my_pass_list.count ; i++ )
            {
                if let each_pass = my_pass_list[i] as? NSDictionary
                {
                    if let fname = each_pass["fname"] as? String
                    {
                        if let lname = each_pass["lname"] as? String
                        {
                            if let numofseats = each_pass["numofseats"] as? String
                            {
                                if let showed_up = each_pass["showed_up"] as? String
                                {
                                    if let res_id = each_pass["resnum"] as? String
                                    {
                                        if let res_notes = each_pass["res_notes"] as? String
                                        {
                                            if let payment_description = each_pass["payment_description"] as? String
                                            {




                                                // the_tableData.append(fname)
                                                the_fname.append(fname)
                                                the_basics.append(fname + " " + lname)
                                                the_p_method.append(payment_description)
                                                the_seats.append(numofseats)
                                                the_notes.append(res_notes)

                                                TableTitle.append(fname + " " + lname)
                                                TableSub.append("Seats Reserved: " + numofseats + ". Showed Up: " + showed_up + ". Notes:" + res_notes)

                                                the_tableData = TableTitle
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    do_table_refresh();
}


func do_table_refresh()
{
    dispatch_async(dispatch_get_main_queue(), {
        self.tableView.reloadData()
        return
    })
}






// MARK: - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    // Get the new view controller using [segue destinationViewController].

    var thirdScene = segue.destinationViewController as! customer_details_View_Controller

        if let indexPath = self.tableView.indexPathForSelectedRow() {

            /*
so what I'm missing is to be able to check
if (self.resultSearchController.active) {
and if yes have indexPath be the self.resultSearchController.resultSearchTableView.indexPathForSelectedRow() {
or something of that nature
*/

            thirdScene.dotrav = todayString
            thirdScene.from = currentBus
            thirdScene.basics = the_basics[indexPath.row]
            thirdScene.p_method = the_basics[indexPath.row]
            thirdScene.seats = the_tableData[indexPath.row]
            thirdScene.notes = the_notes[indexPath.row]



        }
          // Pass the selected object to the new view controller.
}


func updateSearchResultsForSearchController(searchController: UISearchController)
{
    filteredTableData.removeAll(keepCapacity: false)

    let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %@", searchController.searchBar.text)
    let array = (the_tableData as NSArray).filteredArrayUsingPredicate(searchPredicate)
    filteredTableData = array as! [String]

    self.tableView.reloadData()
}
}
1

There are 1 best solutions below

15
On

You need to account for the fact that you are going to have different data in your tableView depending on the search result. You can still use self.tableView.indexPathForSelectedRow.

What I do, is keep a reference to my base data, and then keep a reference to my filtered data, and display my filtered data in the tableView at all times. If my searchBar has no text, then my filtered data is equal to my base data.

Example:

class MyTableViewController: UITableViewController, UISearchResultsUpdating {

    var data: [String] = ["One", "Two", "Three", "Four", "Five"]

    var filteredData: [String]!

    var searchController: UISearchController!


    override func viewDidLoad() {
        super.viewDidLoad()
        setUpSearchController()
        setFilteredDataForCurrentSearch()
    }

    private func setUpSearchController() {
        searchController = UISearchController(searchResultsController: nil)
        searchController.searchResultsUpdater = self
        self.tableView.tableHeaderView = searchController.searchBar
    }


    private func setFilteredDataForCurrentSearch() {
        if let searchString = searchController.searchBar.text where !searchString.isEmpty {
            filteredData = data.filter({ (string: String) -> Bool in

                return searchString.rangeOfString(string, options: NSStringCompareOptions.CaseInsensitiveSearch) != nil

            })
        } else {
            filteredData = data
        }
    }

    func updateSearchResultsForSearchController(searchController: UISearchController) {
        setFilteredDataForCurrentSearch()
    }

}

Now, you can implement all of your UITableViewDataSource and UITableViewDelegate methods using the filteredData.

In prepareForSegue, you retrieve the correct selected object like:

let indexPath = tableView.indexPathForSelectedRow()
let selectedObject = filteredData[indexPath.row]