13
votes

I have a pretty standard setup of UITableview as master controller in UISplitviewController, universal iOS9 app.

Again, standard fare, I inserted a UISearchBar as the header of the table.

self.searchController = [[UISearchController alloc] initWithSearchResultsController:nil];
self.searchController.searchResultsUpdater = self;
self.searchController.dimsBackgroundDuringPresentation = NO;
self.searchController.hidesNavigationBarDuringPresentation = NO;

[self.searchController.searchBar sizeToFit];
self.tableView.tableHeaderView = self.searchController.searchBar;

self.definesPresentationContext = YES;

No need for showing the rest of my code, the search and all work as expected, on the iPhone. My issue is that the search bar does not ever receive focus or present the keyboard when run on iPad. No response, seems not to receive touches. Does not even respond to programmatically attempting to becomeFirstResponder.

Nothing happens.

The search bar is visible and placed appropriately, there are no underlay or overlay issues.

It works fine on iPhone. Receives touch, presents keyboard, search works. Presumably there is a critical difference in presentation when UISplitviewController is collapsed. With hours of search, pouring through the documentation and every tutorial I come across, I have not found a similar experience which is surprising.

Additional note: If I use multi tasking to shrink the split view to collapsed on iPad, the search bar then works, same as on iPhone. It definitely has to do with the collapsed state of the split view.

UPDATE: (Getting closer)

After further experimentation I find that the search bar works on the iPad if you start the app in landscape. In portrait mode, my primary controller is hidden. It slides in by selection of the displayModeButtonItem, which is set to the leftBarButtonItem of the detail controller. That is when the searchBar breaks and no longer will respond to touches.

If I start in landscape, then rotate to portrait, it stops working as well. Rotating back to landscape does not fix it. Once broke, it stays broke until a restart.

I tried moving definesPresentationContext to the viewWillAppear method of the master controller, but that had no effect.

Further Update:

Another unexpected circumstance is that there is no problem with the search bar at all on iPhone 6plus. Works regardless of orientation, collapsed or no and doesn't matter which state it starts in. I would expect this would be same as iPad when in landscape orientation. Works completely on iPhone.

I still have not figured this out. My latest attempt to fix, I moved all the search controller initialization to the viewDidLayoutSubviews method. No change whatsoever.

Also, I noticed that when the search is active, keyboard onscreen, if I rotate the iPad the keyboard goes away but the search bar remains active. I didn't realize at first, then I saw the cancel button still displayed. It won't receive touches and does not have keyboard, but it is apparently still first responder.

Sample Project Uploaded to GitHub: Github Repository

NOTE- There was no issue until I added my UISplitview Delegate methods to the project. I wanted to add this to my question right away, so, I haven't yet even tried to see exactly how these delegate methods affect the search bar but obviously the issue is created there somewhere.

Update:

I tried a few more variations with no success.

  • Move search bar to section header. No joy.
  • Put search bar in a UIView as a content view. No joy.
  • Remove search bar on disappear, reinsert on did appear. No joy.
  • Manipulate the size of search bar frame to ensure it is not clipped. No joy.

I also looked at the view hierarchy in profiler, the search bar is top level, not covered by anything else.

Final Wrap:

@tomSwift workaround did correct my issue on iPad. On further testing, I found it broke my iPhone UI. Presumably, this is more an issue with my own setup and the timing of creation of the Master/Detail View Controllers. It would seem some important objects no longer were created in time as perhaps the detail view controller didn't exist yet. I poked around to fix by moving critical items into App Delegate but that became unwieldy and I couldn't easily narrow it down, so I hacked a fix with:

if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
    splitViewController.preferredDisplayMode = UISplitViewControllerDisplayModeAllVisible;
}

A more elegant solution is likely to be had, but this has taken enough of my time. Everything works now. Radar filed- 28304096

2
Dean, please provide a demo project where this reproduces.Leo Natan
@LeoNatan, I did manage to reproduce the issue and uploaded a sample project to github. Thanks.Dean Davids
@Dean your sample project is missing files; does not open and build in Xcode. E.g. Main.storyboard and LaunchScreen.storyboard are missingTomSwift
It's been a while since I put something out there. I'll check and fix ASAP.Dean Davids
According to this you aren't supposed to use UISplitViewControllers on a Navigation stack, but both your master and detail views are nested under navigation controllers. You might try removing those.inorganik

2 Answers

4
votes

This is clearly a bug in UISplitViewController and/or UISearchController. Certainly file a radar w/ Apple and attach your code sample.

I think the bug involves UISplitViewController.displayMode UISplitViewControllerDisplayModeAutomatic. When I change the preferredDisplayMode to UISplitViewControllerDisplayModeAllVisible then everything starts to work:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    UISplitViewController *splitViewController = (UISplitViewController *)self.window.rootViewController;
    UINavigationController *navigationController = [splitViewController.viewControllers lastObject];
    navigationController.topViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem;
    splitViewController.delegate = self;
    splitViewController.preferredDisplayMode = UISplitViewControllerDisplayModeAllVisible;

    return YES;
}

The main thing this changes is the default layout for portrait on iPad, which I understand may not be ideal. I also played with setting preferredDisplayMode to UISplitViewControllerDisplayModeAllVisible initially then setting it back to UISplitViewControllerDisplayModeAutomatic; this worked great until the screen orientation changed - then it all broke again. You might have some luck going further down that path.

I also made the following addition in the AppDelegate, to force the search controller to deactivate on displayMode change. (I also had to expose the searchController property publicly on the MasterViewController).

- (void)splitViewController:(UISplitViewController *)svc willChangeToDisplayMode:(UISplitViewControllerDisplayMode)displayMode {

    UINavigationController *navigationController = [svc.viewControllers firstObject];
    MasterViewController* mvc = (MasterViewController*) navigationController.topViewController;
    [mvc.searchController setActive: NO];
}
1
votes

Played around with your project and discovered that the issue is that your search controller is never set to active. So I subclassed UISplitViewController and made it a delegate for itself so it could notify the child view controllers when the mode changes, so you can set it active at the right time.

Use this delegate method to create the notification:

- (void)splitViewController:(UISplitViewController *)svc willChangeToDisplayMode:(UISplitViewControllerDisplayMode)displayMode {
    NSLog(@"WILL CHANGE TO DISPLAY MODE: %ld", (long)displayMode);

    NSNumber *displayNumber = [NSNumber numberWithInteger:displayMode];
    NSDictionary *userInfo = @{ @"displayMode": displayNumber };
    NSNotification *modeChangedNotif = [NSNotification notificationWithName:@"modeChanged" object:nil userInfo:userInfo];
    [[NSNotificationCenter defaultCenter] postNotification:modeChangedNotif];
}

Then in your MasterViewController you can do this in viewDidLoad:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(modeChanged) name:@"modeChanged" object:nil];

In that method, you can do a check for a particular displayMode enum, but for the sake of getting it working I just did:

- (void) modeChanged {
    [self.searchController setActive:YES];
}