5
votes

I am trying to use UISearchController however I confronted with retain issue that I can't solve. MainTableview has two sections.

Section 1

Filtered Data based on some Regex

Section 2

All Data

I added UISearchController to my tableview and attached ResultsTableController as resultsTableController. It works when user search something, ResultsTableController comes forward and because I set tableview delegate to self, selecting item from ResultsTableController calls didSelectRowAtIndexPath in my MainTableViewController. However I have allocation issue if user selects something from resultsTableController.

Following happens for different scenarios

  • User doesn't search anything, just selects an item from MainTableview, I see deinit messages
  • User searches something, cancel the search, select item from MainTableview, I see deinit messages
  • User searches something, and selects an item from ResultsTableController, I don't get deinit in my viewcontrollers

MainTableViewController.swift

var searchController: UISearchController!

// Secondary search results table view.
var resultsTableController: ResultsTableController!
var allCompanies = ["Data1","Data2","Data3"]

override func viewDidLoad() {
    super.viewDidLoad()
     resultsTableController = ResultsTableController()
    // We want to be the delegate for our filtered table so didSelectRowAtIndexPath(_:) is called for both tables.
    resultsTableController.tableView.delegate = self
    searchController = UISearchController(searchResultsController: resultsTableController)
    searchController.searchResultsUpdater = self
    searchController.searchBar.sizeToFit()
    tableView.tableHeaderView = searchController.searchBar

    searchController.delegate = self
    searchController.dimsBackgroundDuringPresentation = false 
    searchController.searchBar.delegate = self   
    definesPresentationContext = true
    }
}



// MARK: UISearchBarDelegate

func searchBarSearchButtonClicked(searchBar: UISearchBar) {
    searchBar.resignFirstResponder()
}

// MARK: UISearchResultsUpdating
func updateSearchResultsForSearchController(searchController: UISearchController) {
    // Update the filtered array based on the search text.

    let filteredResults = allCompanies.filter({ company in
        (company.lowercaseString as NSString).containsString(searchController.searchBar.text.lowercaseString)
    })

    // Hand over the filtered results to our search results table.
    let resultsController = searchController.searchResultsController as! ResultsTableController
    resultsController.searchResult = filteredResults
    resultsController.tableView.reloadData()
}

// usual tableview methods

    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {

        if resultsTableController.searchResult.count > 0 {
        selectedCompany = resultsTableController.searchResult[index]
        //do something with selected company
        navigationController?.popViewControllerAnimated(true)
        return
     }
     //
     selectedCompany = allCompanies[index]
      navigationController?.popViewControllerAnimated(true)

}

deinit {
    println("MainTableView deinit")
}

ResultTableController.swift

class ResultsTableController:UITableViewController {

     var searchResult = [String]()

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
     }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

        return searchResult.count
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! UITableViewCell
        let index = indexPath.row
        cell.textLabel?.font = UIFont(name: "Avenir-Roman", size: 16)
        cell.textLabel?.text = searchResult[index].description
        return cell

    }

    deinit {
        println("ResultTableController deinit")
    }
}
2

2 Answers

11
votes

Hey there I ran into the issue today apparently I need to force the dismiss of the searchController to work around the retain issue

  override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(animated)
    searchController?.dismissViewControllerAnimated(false, completion: nil)
  }

here is my sample project https://www.dropbox.com/s/zzs0m4n9maxd2u5/TestSearch.zip?dl=0

7
votes

The solution does seem to be to call dismissViewControllerAnimated on the UISearchController at some point. Most people probably don't do that since the UISearchController is somewhat of an implementation detail related to your view controller that is hosting the UISearchController.

My solution, which seems to work no matter how you present your search UI (standard present or in a popover) is to call searchController.dismissViewControllerAnimated() from your host's viewDidDisappear, after checking to see if the view controller is no longer being presented. This catches all cases, especially the popover case where the user taps outside the popover to automatically dismiss the UI, or the case where the search UI is disappearing simply because you pushed something else onto the navigation stack. In the latter case, you don't want to dismiss the UISearchController.

override func viewDidDisappear(animated: Bool)
{
    super.viewDidDisappear(animated)

    if presentingViewController == nil
    {
        searchController.dismissViewControllerAnimated(false, completion: nil)
    }
}