It seems that when the UISplitViewController property presentsWithGesture is YES, the UISplitViewControllerDelegate methods aren't called when the master viewController is shown/hidden using swipe gestures. In particular, splitViewController:willShowViewController:invalidatingBarButtonItem: and splitViewController:willHideViewController:withBarButtonItem:forPopoverController: aren't being called.
How are the rest of you managing updating state for your view controllers when using your splitViewController with gestures?
The willHide/willShow methods are for when the master view controller is hidden/shown as the result of an orientation change, and as their parameters suggest, are primarily so that you can add/remove a bar button item for showing the master view controller in a popover. (A split view controller's master popover has a different appearance from other popovers, but it's still a UIPopoverController.)
I still see splitViewController:popoverController:willPresentViewController: being called when I swipe the master view in. And that popover controller's delegate gets notified when the popover gets dismissed.
Related
If you want to dismiss a popover -- for example, from a button within the popover's contentViewController you must --
Create a reference to the popover to be held by view controller which creates it
Create a notification from the contentViewController to let the owning view controller know that it should be dismissed, or alternately create a delegate for the same purpose
Send the notification or delegate message when the popover is ready to be dismissed
Call dismissPopover:animated when the notification or delegate method is called
Meanwhile, from a UIViewController you can access the modal view controller, the parent view controller, the navigation controller, the split view controller, the tab bar controller, the search display controller, the child view controllers, the presenting view controllers, and the presented view controllers.
Is there a better approach to do this from popover's contentViewController?
Unfortunately, you'll have to create a weak property reference to said UIPopoverController as there's no way to access it from within the content view controller.
I was surprised how UIViewControllers can access the modal view controller, the parent view controller, the navigation controller, the split view controller, the tab bar controller, the search display controller, and as of iOS 5, the child view controllers as well as presenting and presented controllers...but not the popover controller (granted popovers aren't UIViewControllers but still).
Technically, there's a private, undocumented method to retrieve the popoverController that the UIViewController is in...I have no idea why they never made it public given that it should be exactly the same as any of the above controllers.
Though even in the private, undocumented world, there's no equivalent to dismissModalViewcontrollerAnimated:. You'll still have to get that reference then dismiss it that way.
Another way to solve this is to create an abstract view controller (for all your view controllers) that adds an NSNotification observer to a method such as -(void)closePopoverIfNecessary:(NSNotification*)notification and have child classes optionally implement the method to close their popover(s) if open. Then from within the popover's controller you fire the notification to close it. You could also pass other info via the notification (userInfo) if needed.
This way there's no need for the parent references.
By my count, the only two instances when viewWillAppear is called is when you initialize your view controller, or when you pop off the view controller that's on top of it on the navigation stack (ie pushing the back button on the viewcontroller ahead of it). Are there any other instances when viewWillAppar is called? I don't believe it's called when the app becomes active. Interested to hear some responses on this.
viewwillappear method is called as and when the view controller's view is added to the window. ( if the view is already in the window and is hidden by another view, this method is called when the view is once again revealed). The method is a notification to the view controller that the view is about to become visible. You can override this method to make any customizations with presenting the view.
This will also be called anytime addSubView is called, with your view.
I have two UIViewControllers, vc1 and vc2.
I want to switch between them. But before loading the view of the new view controller, I want to destroy/release/remove (I'm not sure abt the correct word to use here) the previous viewcontroller.
For example, when I am switching to vc2 from vc1 ,I want to destroy vc1 completely, so that when I return to vc1 from vc2, vc1 will be loaded from scratch (i.e. viewDidLoad will be executed).
Which type of view switching should I opt for?
presentModal...
addSubview.
I am not using a navigation controller.
Currently I am using the presentModal... method, but when I use dismissModalViewcontroller on the newly presented view controller, it doesn't show up a new instance of the previous view controller. Instead, it shows the already running instance of it.
I want the viewDidLoad method of the previous view controller to run when I dismiss the newly presented view controller.
What exactly needs to happen in viewDidLoad?
You also have viewWillAppear available to you, so it could be that you could move the required functionality there and still use the modal presentation.
See this answer. You can do this with or without animation.
Animate change of view controllers without using navigation controller stack, subviews or modal controllers?
I am trying to change the master view controller (that appears in the popover in portrait, and on the left side in landscape) in a UISplitViewController. I'd like to switch between the master view controller being one of two different UIViewControllers (depending on an action taken elsewhere in my application).
I am using the following:
mySplitViewController.viewControllers = [NSArray arrayWithObjects:newMasterController, detailController, nil];
This correctly changes the master viewcontroller as you would expect in landscape mode. However it does not seem to change the viewcontroller used for the popover in portrait mode.
I notice that the barbuttonitem to show this master view controller is just calling showMasterInPopover: on the splitviewcontroller, and so would expect it to work in portrait mode as well, but it does not.
In fact, I can set the master view controller, see the new viewController correctly in landscape mode, rotate back to portrait, and the viewcontroller used in the popover is still the old one.
Is this a bug?
In case anyone is looking for a solution to this issue (or a word-around), I had a similar issue and worked it out here: Changing the master view in Split View does not update the popover in portrait
Basically, I used a navigation controller for the master view controller, and pushed/poped view controllers within that navigation controller, in order to change view controllers in the master view whilst still displaying the correct viewcontroller in portrait orientation, in the popup view.
UPDATED: please read final update at bottom! Original answer + update below may not be useful!
We have just had exactly the same problem. Sometimes I wonder if Apple actually test the classes they write with anything resembling realistic use cases, because UISplitViewController is not their finest moment.
The problem is that when you replace the master view controller in the split view, the code inside UISplitViewController doesn't update its popover controller's contentViewController property. The result is that the popover controller still has a handle on an out of date view controller, resulting in old UIs appearing, or even memory faults, when in portrait mode.
Here is our workaround.
I will assume that you have a UISplitViewControllerDelegate conforming class which stores the popoverController as a class property (see the standard sample code for UISplitViewController).
At the point at which you set the new master view controller, you also need to update the contentViewController, as follows:
mySplitViewController.viewControllers
= [NSArray arrayWithObjects:newMasterController, detailController, nil];
// in the cases where the uisplitview has already shown a popovercontroller,
// we force the popovercontroller to update its content view controller.
// This ensures any old content view in popover actually gets released by
// the popovercontroller.
if (popoverController) {
[popoverController setContentViewController:theMasterViewController
animated:NO];
}
You also must set the popover's contentViewController when your UISplitViewControllerDelegate gets informed that the popover controller is going to present a view controller:
- (void)splitViewController:(UISplitViewController*)svc
popoverController:(UIPopoverController*)pc
willPresentViewController:(UIViewController *)aViewController
{
// set the popoverController property - as per Apple's sample code
self.popoverController = pc;
// THE LINE BELOW IS THE NEW LINE!
[popoverController setContentViewController:aViewController animated:NO];
Yes, I know the above code looks insane, and you're wondering why apple couldn't just set the content view controller themselves. But they apparently don't, and this is the fix.
UPDATE
The above scheme, with setting the content view, turns out not to work after all. For example, if you set the content view to be a uinavigationcontroller, later on you get passed the root view inside the nav controller, instead of the nav controller itself. UISplitViewController just doesn't handle changing the master view in any workable way, as far as I can see.
The workaround I currently have is to install a UINavigationController as the master view, and change the root view controller of that nav controller. So I get to change the master view 'by the back door', in a way.
UPDATE 2
I give up. The approach in the first update above is flawed too; I get problems upon rotation still. Basically, it seems that if you use UISplitViewController, you shouldn't attempt any changing of the master view controller (even if you're switching the master view when the master view, e.g. as a popover, has been hidden again). Fiddling with the contents of a UINavigationController in the master view (while the master view is showing) appears like it will be ok, but anything beyond that leads to problem after problem.
Technical note: I believe the problems stem from an apparent weakness in Apple's handling of UIs: namely, Apple code will call release on UIViews and controller once hidden or removed from view, but will then later, if the containing viewcontroller is shown again, send deferred messages like viewDidDisappear to the released views/controllers (which at that point may have been deallocated).
I've made a custom view to which I add a custom button. This custom view goes as a subview to yet another view (Kal calendar for iphone) that I push into a navigation controller. Now the button in my custom view is connected to an IBAction in which I am not able to call upon the self.navigationController to pop the current view from.
I've tried the [[sender superview] navigationController] for a hierarchy of superview calls but it doesn't work that way either.
Any ideas please?
Thanks
A view typically has no knowledge of its view controller (and consequently, to its or its superview's view controller's navigation controller) because this would introduce a tight coupling between view and view controller that is usually unwanted and unnecessary.
The easiest way to solve your problem would be to have the view controller instead of the view handle the button's action. If that doesn't work for your design, consider designing a delegate protocol for your custom view and make your view controller the custom view's delegate. The view would then call the delegate method in the button's action method. This latter method is used a lot throughout Cocoa, e.g. by UIScrollView (UIScrollViewDelegate) and UITableView (UITableViewDelegate).