16

Peek and Pop is working with a UISearchController. However, Peek and Pop stops working once you start searching the table using updateSearchResults.

I've extended Apple's Table Search with UISearchController demo to support Peek and Pop as an example: enter image description here

Problem is when I start searching the table, Peek and Pop doesn't work anymore. It just select highlights it: enter image description here

The updates I made were to MainTableViewController are:

class MainTableViewController: BaseTableViewController, UISearchBarDelegate, UISearchControllerDelegate, UISearchResultsUpdating {
    override func viewDidLoad() {
        super.viewDidLoad()
        ...
        if traitCollection.forceTouchCapability == .available {
            registerForPreviewing(with: self, sourceView: tableView)
        }
    }
}

extension MainTableViewController: UIViewControllerPreviewingDelegate {

    func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
        guard let indexPath = tableView?.indexPathForRow(at: location),
            let cell = tableView?.cellForRow(at: indexPath),
            let controller = storyboard?.instantiateViewController(withIdentifier: "DetailViewController") as? DetailViewController
                else { return nil }

        previewingContext.sourceRect = cell.frame

        controller.product = products[0]

        return controller
    }

    func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) {
        guard let controller = viewControllerToCommit as? DetailViewController else { return }
        controller.product = products[0]
        show(controller, sender: self)
    }
}

Is the search context controller interfering with peek and pop (could even be the keyboard)? I can get it to work when the table initially all data, but it does not once I start using the search. I attached a working sample here if you want to run it and see the issue.

TruMan1
  • 33,665
  • 59
  • 184
  • 335

1 Answers1

18

First, in your MainTableViewController.viewDidLoad() you need to also register your resultsTableController.tableView, since that is a separate view that will receive peek/pop information:

if traitCollection.forceTouchCapability == .available {
    previewingContext = registerForPreviewing(with: self, sourceView: tableView)
    if let resultVC = searchController.searchResultsController as? ResultsTableController {
        resultVC.registerForPreviewing(with: self, sourceView: resultVC.tableView)
    }
}

When testing this solution, I noticed a strange problem, that the first row in the result set wasn't peekable, and blank rows in the result set WERE peekable. So, the second fix in previewingContext(_:viewControllerForLocation:):

func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
    guard let tableView = previewingContext.sourceView as? UITableView,
        let indexPath = tableView.indexPathForRow(at: location),

In your original code, it was using the tableView property on the MainTableViewController instead of the tableView that was the sourceView for the interaction.

Now, this works when you're searching, and when you're not. However, when you've entered the search, but haven't entered any search text yet, the UISearchController is active, but the UITableView is the one from MainTableViewController, and you cannot register a view as a source view twice. So, we have a little more work to do:

// local property to store the result from registerForPreviewing(with:sourceView:)
var previewingContext: UIViewControllerPreviewing?

func didPresentSearchController(_ searchController: UISearchController) {
    if let context = previewingContext {
        unregisterForPreviewing(withContext: context)
        previewingContext = searchController.registerForPreviewing(with: self, sourceView: tableView)
    }
}

func didDismissSearchController(_ searchController: UISearchController) {
    if let context = previewingContext {
        searchController.unregisterForPreviewing(withContext: context)
        previewingContext = registerForPreviewing(with: self, sourceView: tableView)
    }
}

Basically, when the UISearchController is presented, we unregister MainTableViewController and register the search controller. When it is dismissed, we do the reverse.

With these changes, peek and pop work in all three states.

Dave Weston
  • 6,527
  • 1
  • 29
  • 44
  • @TruMan1, was my answer helpful? – Dave Weston Feb 18 '17 at 10:15
  • 1
    This works fine as long as you have a separate searchResultsController. However, if you use the table view controller itself to show the search results, searchController.searchResultsController is nil (as per Apples documents: "searchResultsController: The view controller that displays the search results. Specify nil if you want to display the search results in the same view controller that displays your searchable content." – vilmoskörte May 03 '17 at 05:18
  • 1
    I couldn't get Peek and Pop to work with the searchController. The searchController.registerForPreviewing(...) did the job for me, i just set it in viewDidLoad. I spent hours trying different things, but with self.registerForPreviewing(...) it didn't call the implemented delegate methods. Thanks. – dkrdennis Oct 25 '17 at 12:02
  • The suggestion by @dkrdennis worked for me, however, I had to call `registerForPreviewing(with:sourceView:)` on the search controller after calling it on my view controller holding the search controller. – simonbs Jun 25 '18 at 19:41
  • 1
    After further testing, I'd like to change my comment above slightly. I found that it works best when the search controller is registered for previewing in `willPresentSearchController(_:)` of UISearchControllerDelegate. At the same time, the view controller should be unregistered. Vice versa in `willDismissSearchController(_:)`. – simonbs Jun 25 '18 at 19:50