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
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];
}
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];
}
Related
I am implementing an application that uses a UINavigationController. It currently shows 3 views, each a UITableViewController. The first view that is shown only has a title, while the other two have a title and prompt. All segues are created in the storyboard, which is rather straightforward:
The problem: As soon as I animate AWAY from a view that has a prompt, the animation glitches - the title (and sometimes the back button) "fly in" from the bottom, instead of from the right.
Example: https://youtu.be/N-K8piEJ1aY (recording with slow animations turned on)
Here you can see that the animation from first to second view works fine, but from second to third view is glitchy. animating back works.
This issue seems similar to Weird animations when changing NavigationItem prompt . The conclusion in that thread was that this only occurs on iOS 7, I am running on iOS 10.0/10.1, though. The issue occurs both in simulator and on the real device.
Any ideas?
The only solution I found was an absolute hack inspired by an absolute hack inspired by Catalina T. over on this post:
Either in viewWillAppear: on the appearing VC or after calling pushViewController:animated: on the navigation controller (or I'm guessing after calling performSegueWithIdentifier:sender: add the following code:
ObjC
// This is a hack that's because UINavigationBar with prompts is broken
navigationController.navigationBarHidden = YES;
navigationController.navigationBarHidden = NO;
Swift
// This is a hack that's because UINavigationBar with prompts is broken
navigationController.isNavigationBarHidden = true
navigationController.isNavigationBarHidden = false
where navigationController is a reference to the UINavigationController that's doing all the pushing (e.g. viewController.navigationController)
EDIT: It appears now there're other weird animations that occur when popping and repushing a VC, so this answer isn't a true solution. Leaving it up so as to help someone else down a similar rabbit hole.
I have an application created from the tabbed application template. (ARC, iOS 4)
There are several tabs and there is a button on the 2. tabs viewcontroller.view(ViewCont2).
This button loads another viewcontroller's(ModalViewCont) view by presentModalViewController method.
There is a close button on ModalViewCont which calls dismissModalViewControllerAnimated.
In viewDidDisappear of ViewCont2, i am setting self.view = nil and other outlets to nil to unload the view so it will be fresh loaded next time it appears on screen. I am doing this because it inherits from a base class(BaseViewCont) which initializes some general properties of the view controller and adds some buttons, labels etc. in viewDidLoad method. So, ViewControllers that inherit from this base class may configure those properties differently as they wish in their viewDidLoad method.
Problem
Now, when ModalViewCont on screen, pressing the Home button to put application in background and after getting the application back, closing the ModalViewCont does not bring back the ViewCont2's view but a black screen with the tabbar at the bottom. The same thing happens without putting the application background/foreground; if other tabs tapped before tapping the 2. tab.(EDIT : This happens only if self.view set to nil in viewWillDisappear instead of viewDidDisappear.)
I determined that ViewCont2 loads a new view (checked it's reference) but view's superview is nil so the new view is not displayed but a black screen.
Things that did not work
Using [self.view removeFromSuperview]; before setting self.view=nil,
In viewWillAppear adding view to the parent; [self.parentViewController.view addSubview:self.view]; This one did not work smoothly, view placed slightly up of the screen. This is because there are several other superviews in the hierarchy.
Solutions i considered;
1- If superview is nil in viewDidLoad, it becomes available in viewWillAppear (assumption). So, viewWillAppear method of ViewCont2 could be used to get the superview loaded correctly by the following;
_
if (self.view.superview == nil)
{
self.tabBarController.selectedViewController = nil;
self.tabBarController.selectedViewController = self;
}
2- viewWillAppear method of base class could be used instead for initialization so there is no need to unload the view. So, performance could be optimized, it will not be unloaded each time view disappears. Also, it would be better to perform initialization only once by checking a flag, instead of performing it every time it appears.
Questions
1- Why does not the superview restored? What should i do for it? (This is the main problem i want to understand and solve instead of trying alternatives...)
2- Am i doing something wrong by assigning nil to view for unloading it? If so, how should i unload the view properly in such case like this(tabbed application)?
3- Is anything wrong with the 1. solution? Does it seem like a kludge? Is that assumption about superview and viewWillAppear correct?
EDIT : It seems that when viewDidLoad is called earlier than it should(i.e when view nilled in viewWillDisappear instead of viewDidDisappear), superview is not set.
It seems weird, but your suggestion (1) is indeed a correct workaround for this problem:
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (!self.view.superview) { // check if view has been added to view hierarchy
self.tabBarController.selectedViewController = nil;
self.tabBarController.selectedViewController = self;
}
}
Your second suggestion is good for performance (because view loading is an expensive operation) - but it will not solve the problem. You can also end up with a black screen without setting the view to nil in the following situation (test this in the iOS simulator):
open the modal view
simulate a memory warning -> this will unload the views in the tabbarcontroller
press home button and open the app again
close modal view -> black screen
Generally you can assume that in viewDidLoad the view property is set and in viewWillAppear + viewDidAppear the view has been added to the view hierarchy; so the superview should be there at that time (Here the superview is a private view of the tabbarcontroller of class UIViewControllerWrapperView). However in our case, although the view is reloaded (at the time of app resume), it is not added to the view hierarchy resulting in a black screen. This seems to be a bug in UITabBarController.
The workaround forces the appearance selectors to be performed again. So viewWillAppear will be called again, this time with a superview in place. Also viewDidAppear will be called twice!
Setting self.view to nil is okay, but should not be necessary in most cases. Let the system decide when to unload the view (iOS can unload views when memory gets low). The view controller code should be designed in a way so that the UI can be reconfigured at any time without reloading the view.
You do not have full control over when views are loaded and unloaded, and you are not supposed to load/unload views manually yourself.
Instead, you should think of view loading/unloading as something that's entirely up to your UIViewControllers, with you being responsible only for:
Implementing the actual loading, by associating your UIViewController subclass with a nib file or by implementing loadView manually.
Optionally implementing the viewDidLoad, viewWillUnload and viewDidUnload callbacks, which are called by the view controller when it decides to load/unload its view.
The fact that you have no full control of when the above callbacks will be called, has implications about what should go into them.
In your case, if I understand correctly, whenever your ViewCont2's view disappears, you want to reset it so that when it reappears it will be in some "clean" state. I would implement this state reset in some method, and call it both from viewDidLoad and from viewDidDisappear. Alternatively, you can have the "clean" logic in viewWillAppear.
Or maybe you want to clean ViewCont2's view only when the present button is tapped? In that case, clean the view both in viewDidLoad, and when the button is tapped.
What I offer is that when the modal view controller is active, and you dismiss the view, that you add a new view to the navigation view controllers viewControllers, then that view is told to remove its predecessor.
You can play with my project to see if you think it works for you.
EDIT: my comment on the selected answer is that this technique obviously works now, but I myself am having a hard time followiing it. The code in my project uses the system in a simple and direct fashion - when the modal view is told to dismiss itself, it calls a method (could be in any class) that adds a new view to the navigation controller's array, then dismisses itself. For a bit of time there are two view controllers of the same time, the new one stacked over the old one. When the new view controller appears, based on seeing a flag it silently and behind the scenes removes the undesired viewController from the nab bar's stack, and poof, it goes away.
I have found the actual solution to the UITabBarController bug(memory warning,app enter back/foreground,dismiss modal). Using UITabBarController as the root view controller is the cause of the bug. So, we could use another view controller as the root view controller and present the tab bar from it. I have tested it on iOS 5.1 simulator.
Of course, the overhead of extra UIViewController is subject to debate. Also, it's against the Apple documentation;
Unlike other view controllers, a tab bar interface should never be installed as a child of another view controller.UITabBarController Class Reference
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// A root view controller other than the actual UITabBarController is required.
self.window.rootViewController = [[UIViewController alloc] init];
[self.window makeKeyAndVisible];
self.tabBarController = [[UITabBarController alloc] init];
self.tabBarController.viewControllers = [NSArray arrayWithObjects:viewController1, ..., nil];
[self.window.rootViewController
presentModalViewController:self.tabBarController animated:NO];
}
I have found other solutions;
First one causes the warning: "Application windows are expected to have a root view controller at the end of application launch" although there is root view controller.
Although it seems kludgy, the temporary view controller will be released with the first one.
Second one seems more reasonable.
.
- (void) tabBarBlankScreenFix1
{
self.window.rootViewController = [[UIViewController alloc] init];
[self.window makeKeyAndVisible];
[self.window addSubview:self.tabBarController.view];
self.window.rootViewController = self.tabBarController;
}
- (void) tabBarBlankScreenFix2
{
self.window.rootViewController = [[UIViewController alloc] init];
[self.window makeKeyAndVisible];
[self.window addSubview:self.tabBarController.view];
}
I think you shouldn't assign the view to nil.
If I understand you right you want to refresh/reload content every time the view appears.
So instead of setting the view to nil, you should try to refresh it. You can do it by adding:
- (void)viewWillAppear{
[self.view setNeedsDisplay];}
Please tell me if I understand your issue right
As has been reported in other questions here on SO, iOS 5 changes how rotation callbacks for split view controllers are sent as per this release note. This is not a dupe (I think), as I can't find another question on SO that deals with how to adjust split view controller usage in iOS 5 to cope with the change:
Rotation callbacks in iOS 5 are not applied to view controllers that
are presented over a full screen. What this means is that if your code
presents a view controller over another view controller, and then the
user subsequently rotates the device to a different orientation, upon
dismissal, the underlying controller (i.e. presenting controller) will
not receive any rotation callbacks. Note however that the presenting
controller will receive a viewWillLayoutSubviews call when it is
redisplayed, and the interfaceOrientation property can be queried from
this method and used to lay out the controller correctly.
I'm having trouble configuring the popover button in my root split view controller (the one that is supposed to show the left pane view in a popover when you're in portrait). Here's how my app startup sequence used to work in iOS 4.x when the device is in landscape mode:
Install split view controller into window with [window addSubview:splitViewController.view]; [window makeKeyAndVisible];. This results in splitViewController:willHideViewController:withBarButtonItem:forPopoverController: being called on the delegate (i.e. simulating a landscape -> portrait rotation) even though the device is already in landscape mode.
Present a fullscreen modal (my loading screen) which completely covers the split view underneath.
Finish loading and dismiss the loading screen modal. Since the device is in landscape mode, as the split view controller is revealed, this causes splitViewController:willShowViewController:invalidatingBarButtonItem: to be called on the delegate (i.e. simulating a portrait -> landscape rotation), thereby invalidating the bar button item, removing it from the right-side of the split view, and leaving us where we want to be. Hooray!
So, the problem is that because of the change described in that release note, whatever happens internally in iOS 4.3 that results in splitViewController:willShowViewController:invalidatingBarButtonItem: being called no longer happens in iOS 5. I tried subclassing UISplitViewController so I could provide a custom implementation of viewWillLayoutSubviews as suggested by the release note, but I don't know how to reproduce the desired sequence of internal events that iOS 4 triggers. I tried this:
- (void) viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
UINavigationController *rightStack = [[self viewControllers] objectAtIndex:1];
UIViewController *rightRoot = [[rightStack viewControllers] objectAtIndex:0];
BOOL rightRootHasButton = ... // determine if bar button item for portrait mode is there
// iOS 4 never goes inside this 'if' branch
if (UIInterfaceOrientationIsLandscape( [self interfaceOrientation] ) &&
rightRootHasButton)
{
// Manually invoke the delegate method to hide the popover bar button item
[self.delegate splitViewController:self
willShowViewController:[[self viewControllers] objectAtIndex:0]
invalidatingBarButtonItem:rightRoot.navigationItem.leftBarButtonItem];
}
}
This mostly works, but not 100%. The problem is that invoking the delegate method yourself doesn't actually invalidate the bar button item, so the first time you rotate to portrait, the system thinks the bar button item is still installed properly and doesn't try to reinstall it. It's only after you rotate again to landscape and then back to portrait has the system got back into the right state and will actually install the popover bar button item in portrait mode.
Based on this question, I also tried invoking all the rotation callbacks manually instead of firing the delegate method, e.g.:
// iOS 4 never goes inside this 'if' branch
if (UIInterfaceOrientationIsLandscape( [self interfaceOrientation] ) &&
rightRootHasButton)
{
[self willRotateToInterfaceOrientation:self.interfaceOrientation duration:0];
[self willAnimateRotationToInterfaceOrientation:self.interfaceOrientation duration:0];
[self didRotateFromInterfaceOrientation:self.interfaceOrientation];
}
However this just seems to cause an infinite loop back into viewWillLayoutSubviews :(
Does anyone know what the correct way to simulate the iOS4-style rotation events is for a split view controller that appears from behind a full-screen modal? Or should you not simulate them at all and is there another best-practices approach that has become the standard for iOS5?
Any help really appreciated as this issue is holding us up from submitting our iOS5 bugfix release to the App Store.
I don't know the right way to handle this situation. However, the following seems to be working for me in iOS 5.
In splitViewController:willHideViewController:withBarButtonItem:forPopoverController:, store a reference to the barButtonItem in something like self.barButtonItem. Move the code for showing the button into a separate method, say ShowRootPopoverButtonItem.
In splitViewController:willShowViewController:invalidatingBarButtonItem:, clear that self.barButtonItem reference out. Move the code for showing the button into a separate method, say InvalidateRootPopoverButtonItem.
In viewWillLayoutSubviews, manually show or hide the button, depending on the interface orientation
Here's my implementation of viewWillLayoutSubviews. Note that calling self.interfaceOrientation always returned portrait, hence my use of statusBarOrientation.
- (void)viewWillLayoutSubviews
{
if (UIInterfaceOrientationIsPortrait(
[UIApplication sharedApplication].statusBarOrientation))
{
[self ShowRootPopoverButtonItem:self.barButtonItem];
}
else
{
[self InvalidateRootPopoverButtonItem:self.barButtonItem];
}
}
I am creating an application with Landscape Right orientation. For that I set the Initial interface orientation property in info.plist file. Then in every view I handled
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return(interfaceOrientation==UIInterfaceOrientationLandscapeRight);
}
It works fine in simulator but in device its behave differently.
My first view is in proper orientation. There is is popover which display another view that comes in portrait mode. Still my status bar is in Landscape Right..
For navigating from one view to another view I am using..
self.window.rootViewController = self.myNav;
I have multiple navigation Controller and adding those using the upper code.
I am not getting what is the problem.
Any help will be appreciated.
EDIT: I had used
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
I never get this issue in simulator but getting this in device and not every time. I have used Supported interface orientations (iPad) too and set Landscape (right home button) value for item0.
Thanks In advance
You need to set the "Simulated Metrics > Orientation" property of your top view (in all your xib files) to be "Landscape". The default is portrait.
The question was answered pretty well here - Landscape Mode ONLY for iPhone or iPad .
I also have an app that like yours only supports UIInterfaceOrientationLandscapeRight. I haven't run into any orientation issues so far. I only have one UIViewController under the window. This UIViewController has its own UITabBar that I use to change pages. So, we change pages differently. I set my UIViewController using the rootViewController property of the window just like you, but again I only have one.
Also, I never had to do anything like the [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications] call that you included. Since you only support LandscapeRight orientation, you shouldn't care to be notified of changes.
EDIT
I created a sample project, which I can post if necessary, and I think I may know your problem. First - do you only encounter the sizing issue inside popovers? If so, then I don't think orientation is throwing you off but the popover itself. My sample project has 3 view controllers. The first loads the second by changing the window's rootViewController. That worked fine. The second view controller has a button to open a popover. This will show up wrong unless you set the contentSizeForPopover on the view controller as shown below:
- (IBAction) showVC3InPopover
{
UIViewController * vc3 = [[VC3 alloc] init];
/** Set the contentSizeForViewInPopover property to determine how
large the view will be in the popover! You can do this in the
init method(s) of the view controller if you prefer, it's just
easy to do here */
vc3.contentSizeForViewInPopover = vc3.view.frame.size;
[_popover release], _popover = nil;
_popover = [[UIPopoverController alloc] initWithContentViewController:vc3];
[vc3 release];
[_popover presentPopoverFromRect:_showPopoverButton.frame inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
See if this fixes your problem. If it does, there are other ways to control the popover size, but this is just what I typically do. However, just know that the PopoverController ignores the size of the view in the viewcontroller you load.
How many views (or viewControllers) you have? You might need to implement this orientation logic in all those views??
I am doing an iPad app based on a UISplitViewController. I have a little problem with the toobar button when my app launched in potrait. The button to show the popover is not displayed.
However when I rotate my iPad into landscape and then back to portrait, the button shows !
It looks like the following method is not called on launch (this is were I have the code showing the button):
- (void)splitViewController:(UISplitViewController *)svc willHideViewController:(UIViewController *)aViewController withBarButtonItem:(UIBarButtonItem *)barButtonItem forPopoverController: (UIPopoverController *)pc
This method is not called when the app launches but only when there is a rotation. What is even stranger is that I made a test app using Xcode UISplitViewController template + core data (which is similar to the app I am working on, and is the template I used to make this app). On the test app on which I have not made a single line of code, the button shows when I launch my app in portrait mode and the method above is also called upon launching, as opposed to my other app. Does anyone had a similar problem ?
Finally, it is not very clear from apple documentation whether this method is supposed to be called when a UISplitViewController is first shown:
http://developer.apple.com/library/ios/#documentation/uikit/reference/UISplitViewControllerDelegate_protocol/Reference/Reference.html%23//apple_ref/doc/uid/TP40009454
"Kshitiz" has the right concept. first I set the self.splitviewController.delegate = self in the viewDidLoad method, which it is a bit late to set this delegation. So, I tried to set the delegation in earlier stage which is awakeFromNib method. Then it works well.
So, the problem is after view already loaded by viewDidLoad, then the delegation will not work, it will work some time after some activities (such as rotate the iPad). So the earlier stage than viewDidLoad is awakeFromNib.
Here is the code that works:
- (void) awakeFromNib{
[super awakeFromNib];
self.splitViewController.delegate = self;
}
Have you set a splitviewcontroller delegate?
Generally the problem arises when delegate is not set.
I was having the exact same problem, and Martin Gunnarsson's response led me to the solution.
Before, I was setting the UISplitViewController's delegate property after the delegate view (the detail view) had already been loaded, in viewDidLoad:. By this time, the UISplitViewController had already sent the initial splitViewController:willHideViewController:withBarButtonItem:forPopoverController: message. I simply hadn't set the delegate soon enough.
The solution was to assign the delegate in the main app delegate, in application:DidFinishLaunchingWithOptions:. In this case, my delegate was contained within a navigation controller, so I had to dig one layer deeper to get it.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UISplitViewController *splitViewController = (UISplitViewController *)self.window.rootViewController;
UINavigationController *mainNavigationController = (UINavigationController *)[splitViewController.viewControllers objectAtIndex:1];
HPMainViewController *mainViewController = [mainNavigationController.viewControllers objectAtIndex:0];
splitViewController.delegate = mainViewController;
return YES;
}
This drove me spare as well, the more so since I'm working on two iPad projects with out-of-the-box splitViewController and the first one always shows the 'Master' button while the second one never did. I compared outlets and relationships and delegates until I was cross-eyed, but finally found the answer in the appDelegate. It turned out I had commented out a bit too much in the application:didFinishLaunchingWithOptions:, specifically where the splitViewController.delegate is set.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
UISplitViewController *splitViewController = (UISplitViewController *)self.window.rootViewController;
UINavigationController *navigationController = [splitViewController.viewControllers lastObject];
splitViewController.delegate = (id)navigationController.topViewController;
}
Adding this to the appDelegate saves you from having to subclass the splitViewController.
I also tried connecting the delegate in IB, but for some reason it wouldn't have none of that. Storyboard design flaw, imho.
I was stuck on this for quite sometime. Finally got it to work. The awakeFromNib did not work for me. The didFinishLaunchingWithOptions did. Might be because I am running some query that populates the items in the popover controller.
I'm having the same issue. My view is set up in IB, and it seems this is a timing issue. The split view delegate gets set after the split view has notified about the initial orientation "change". Adding the split view to an outlet in the app delegate made the button appear at portrait startup for me, but when I open the popup it's empty. This can probably be worked around somehow, but I think it's weird that the split view doesn't notify its delegate about the current orientation when it's set.