UISearchController with UISplitViewController

There seems to be a big problem when embedding a UISearchController into the detail view controller of a UISplitViewController with the new iOS 11 feature:

navigationItem.searchController = searchController
navigationController?navigationBar.preferesLargeTitles = true


On iPad, everything works properly as expected without a problem.


On iPhone, however - the search bar in the detail view controller does not appear initially when the view first loads while the large title does.

Upon segue-ing to another view controller in the stack and coming back to the detail view controller, it suddenly appears.

Even though it does appear, but tapping the search bar does not really fit correctly into the top of the view as one would expect.


I have created this demo app on github to explain the situation better: https://github.com/aakhedr/LargeTile

Could somebody please guide me to what I am doing wrong here?

If this is making any sense I keep on getting this printed in the console the first time the detail view controller appears with or without the search bar on both iPhone and iPad:

2017-10-03 05:20:22.282576+0200 LargeTitle[9606:430429] +[CATransaction synchronize] called within transaction

Replies

Edit: My apologies. I had bookmarked a number of UISplitViewCOntoller/UISearchBar posts. After posting this, I realized I posted on the wrong bookmark. This solution addresses a bug where the search controller does NOT display on the intended detail view controller when the split view is collapsed, but DOES erroneously display on the next segued view controller. This workaround is for THAT situation. I'm going to leave this post here, because it may help you, and for others struggling to find a solution for these problems, as I have been for several weeks.


I'm afraid there is no 100% equitable solution (that I've found) in dealing with UISearchController madness, or even the UISearchDisplayController of yesteryear, for that matter. They are worse in a UISplitViewController that is in collapsed mode, but many other layout bugs have long existed, dealing with everything from orientation to status bar neglect, and on and on.


But, you can get most of the way there with workarounds that I believe are, at least, futureproof. If you defer setting the navigationItem.searchController from viewDidLoad, until viewDidAppear, you circumvent the confusion that UISplitViewController has about WHICH nav item is currently its topmost item, and it will be correctly assigned to the right view controller. That seems to be the root issue. This is what I do in viewDidAppear...


-(void)viewDidAppear:(BOOL)animated{

    [super viewDidAppear:animated];

    if (IS_OS_11_OR_LATER){
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpartial-availability"
        if (!self.navigationItem.searchController){
            self.navigationItem.searchController = self.searchController;
        }
#pragma clang diagnostic pop
    }
}


This ensures the search controller gets assigned to the correct nav item when the split view is collapsed. Once you're past this hurdle, it should remain permanently tacked to the correct nav item, and overcome the bug you've seen.


But, if you have the search bar permanently visible by setting self.navigationItem.hidesSearchBarWhenScrolling to NO, this will leave an empty gap where the search bar is supposed to be. If you drag the table view down a bit and back up, it will reappear. But, of course, that's not a winner. So, what I do is, when I create my search controller in viewDidLoad, I set self.navigationItem.hidesSearchBarWhenScrolling to YES, so that it's hidden, and the user never knows it isn't there until a table drag, because they have to drag to see it. Then, in the search delegate method, I set these to what I want them to be:


-(void)willPresentSearchController:(UISearchController *)searchController{
   
    if (IS_OS_11_OR_LATER){
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpartial-availability"
        self.searchController.hidesNavigationBarDuringPresentation = NO;
        self.navigationItem.hidesSearchBarWhenScrolling = NO;
#pragma clang diagnostic pop
    }
}


This works for me. Given my druthers, I'd like the search bar visible when appearing. But - I have to pick my battles. I just include a search bar button item in the toolbar so user knows that search is available, and set isActive to YES when tapped. If you don't like that, you could hack it a bit further, and forgo creating the search controller in viewDidLoad, and instead create it from scratch in viewDidAppear EACH TIME. Doing it that way, for whatever reason, ensures that it is always visible without forcing the user to drag the table view. But, you'd lose the definesPresentationContext functionality. That's a little too much hacking for me, though, but your mileage may vary.


I've been fighting with search controllers since at least iOS 7 or 8. Each release brings a new bag of woes. I really wish Apple would devote some time into debugging them.


Danny

At the cost of redoing your app, it might work better if you employ a tabbar, with a splitview in it, and then make search one of the other tabs.


See: https://developer.apple.com/library/content/documentation/WindowsViews/Conceptual/ViewControllerCatalog/Chapters/CombiningViewControllers.html

Hi Danny


Thanks for keeping the post. I had tried out these two workarounds.

1) Initialize the search controller entirely in viewDidAppear and setting the navigation item search controller property there too.

2) Initialize the search controller in viewDidLoad without setting the navigation item until viewDidAppear.

Both produce the same scenario as the initial post.


Apple have got this implemented perfectly in the new Files app but their `splitViewController(_:collapseSecondary:onto:)` returns false

There must be somethign in the UISplitViewControllerDelegate that tells the collapsed view controller there is a search bar in your navigation item?


Best


Ahmed

On compact width devices, upon segue from master to detail, the detail navigation controller is on top of the master view controller, unlike regular width where the two navigation controllers keep their respective separate root view controllers (master and detail)


So, the UINavigationController of the detail view controller needs to be removed upon segue in combact width devices using UISplitViewControllerDelegate method: splitViewController(_:showDetail:sender:)


func splitViewController(_ splitViewController: UISplitViewController, showDetail vc: UIViewController, sender: Any?) -> Bool {
     if splitViewController.isCollapsed, let navController = vc as? UINavigationController {
          if let detailVC = navController.topViewController {
               splitViewController.showDetailViewController(detailVC, sender: sender)
               return true
          }
     }
     return false
}