Search code examples
iosswiftuinavigationcontrollermaster-detailuisearchcontroller

iOS: Adding a search bar to the navigation bar in a master detail app


What did I do?

I used the Xcode 9 template to create a universal Master-Detail app with Core Data support in Swift.

What do I want?

I would like to add a search bar to the detail's navigation bar.

How did I do it?

let searchController = UISearchController(searchResultsController: nil)
navigationItem.searchController = searchController

What happens? (the actual issue)

The search bar does not appear in the detail's navigation bar. Instead the navigation bar loses it's shadow line and when I go back to the master, search bar appears in the navigation for the time of the animation and then disappears.

enter image description here

This issue only happens when I run the code on an iPhone, not on an iPad (or iPhone in landscape mode) where the split view holds the master view on the left and the detail view on the right.

For me it looks like there is something wrong with the navigation bar, but I just can't get my head around it.

You can download the example project here on Github.


Solution

  • I’ve isolated it down to the segue (kind = Show Detail (e.g. Replace) from the table view item to a {NavigationController-DetailViewController}. That segue’s job is to replace the detail view controller in a split view, or push the detail view controller in compact mode.

    For example, if one tears out the Show Detail (e.g. Replace) segue, and wire the table view cell directly to the DetailViewController, using a “Show (e.g. Push)” segue kind, the UISearchBar will appear as expected.

    For this case, it looks like the segue is presenting the view controller in a way that is affecting the search controller inside the search bar. Code paths are different enough between devices which can explain why it works on the iPad.

    I've been confirmed by Apple's Developer Technical Support that this is a bug in iOS.

    As a cheap workaround is to use a custom segue to manually perform the adaptive presentation (push or replace):

    class PushOrPresentSegue: UIStoryboardSegue
    {
        override func perform() {
            let splitVC = self.source.splitViewController
            let destVC = self.destination
            let nav = destVC as! UINavigationController
            let topVC = nav.topViewController
    
            if (splitVC?.traitCollection.horizontalSizeClass == .regular) {
                // Replace
                let splitViewControllerVCs = splitVC?.viewControllers
                splitVC?.viewControllers = [splitViewControllerVCs![0], destVC];
            }
            else
            {
                // Push
                let sourceVC = self.source
                sourceVC.navigationController?.pushViewController(topVC!, animated: true)
            }
        }
    
    }
    

    In the Storyboard, one has to change the segue between Master View Controller’s cell to Detail ViewContorller’s Navigation Controller: Kind = Custom, Class = PushOrPresentSegue