I have an interesting problem that has me stumped. I have a view that is displayed on top of another view (using addView). The second view does not fill the entire screen on the iPhone. Both views are managed by view controllers. The second view controller then presents a modal view controller that fills the screen (specifically MFMessageComposeViewController).
All of this works great the first time. However, when I dismiss the modal view controller the second time, the dealloc method on the presenting view controller (the one that presents the modal) gets called by __delayedPerformDealloc.
The containing view controller has retained the inner view controller, so I can't figure out what list the inner view controller got on that caused it to be dealloc'd.
Has anyone else seen this?
Tools: Xcode 4.5.2, iOS 6, iPhone 5.
This turned out to be a bonehead move by me. The controller in question was being passed as a delegate and was assigned to a member variable in the init method but was released in the dealloc method. D'oh!
Related
If I have multiple view controllers being presented and dismissed in any order, can I be sure that iOS calls viewWillAppear methods in the right order (i.e. order of appearance)?
I cannot find any specific information about this in the documentation.
I think this is all you need to know about viewWillAppear from the docs:
This method is called before the view controller's view is about to be added to a view hierarchy and before any animations are configured for showing the view. You can override this method to perform custom tasks associated with displaying the view. For example, you might use this method to change the orientation or style of the status bar to coordinate with the orientation or style of the view being presented. If you override this method, you must call super at some point in your implementation.
Only thing that comes to mind that might not be absolutely clear is that this callback is called on the presenting view controller when presented view controller is going to be dismissed (so presenting view controller is going to appear again).
Therefore if A is a root, A.viewWillAppear will be called before it will appear on the screen. Then, if A presents B, just before B becomes visible, B.viewWillAppear will be called. And when B is being dismissed, A.viewWillAppear will get called again, since its view will appear again.
viewWillAppear() is called the first time the view is displayed and it is also called when the view is displayed again, so it can be called multiple times during the life of the view controller object.
It’s called when the view is about to appear as a result of the user tapping the back button, closing dialog, or when the view controller’s tab is selected in a tab bar controller, or a variety of other reasons. Make sure to call super.viewWillAppear() at some point in the implementation
I've been memory profiling an app I am working on and noticed that the detail view controller in my application is 'abandoned' (still resident in memory, but a valid reference still exists) until a new view controller is pushed onto the navigation stack. I have tested this on-device and in-simulator and experience the same issue.
I created a sample project here and a video demonstrating the phenomenon here. In dealloc of the detail view controller I put a log message of when the view controller is destroyed. This doesn't execute until after the new view controller is placed onto the navigation stack.
Doing a memory profile, the view controller isn't deallocated until a private method inside UISplitViewController is called, _willShowCollapsedDetailViewController:inTargetController::
Is there something I am missing? Is this a bug? If not, how do I ensure that the detail view controller is properly deallocated when popped off the navigation stack?
This problem exists in iOS 8 and 9.
When collapsed, _willShowCollapsedDetailViewController retains the detail controller via _setPreservedDetailController so it can be moved to the secondary position when separating the controllers in a rotation to landscape during separateSecondaryViewControllerFromPrimaryViewController. Otherwise the right side of the split view would be empty grey and sad looking. So what you are experiencing is when it preserves the new one, the previous is released and dealloced.
in the viewDidLoad method i have this code.
When the application is running its not go to the another viewController, its gives me an error:
Warning: Attempt to present <CompleteCountryViewController: 0x7fb971779be0> on <ViewController: 0x7fb97176f3e0> whose view is not in the window hierarchy!
What can i do, that when the application running its will go to another viewController?
You should not present a view controller in the viewDidLoad method of another controller because you cannot show a view controller (present modally or push) when a transition is already occurring (push, pop, present, dismiss).
My suggestion is that you move the code in your code sample to the viewDidAppear: method. At this point, you know for sure that the transition has completed.
You seem to have a slight misunderstanding of the lifecycle of UIViewController if you want to modally present a view controller inside the viewDidLoad of another one.
viewDidLoad gets called in one view controller after it has been instantiated and its view components have been loaded (thus the name). The view of that view controller is about to be displayed, so it doesn't make much sense to instantiate another view controller at this point and present it on the first one.
Let me give you an example with two view controller A and B.
You instantiate A and its viewDidLoad gets called. So, A is about to be displayed! What you are doing in your code now is to instantiate B at this very point and show it on A. iOS doesn't like that and will give you your error.
I had an issue where I was attempting to present a modal view controller within the viewDidLoad method. The solution for me was to move this call to the viewDidAppear: method.
View controller's view is not in the window's view hierarchy at the point that it has been loaded (when the viewDidLoad message is sent), but it is in the window hierarchy after it has been presented (when the viewDidAppear: message is sent).
I have a custom container view controller: ContainerVC. Its job is to present one of two content view controllers: ContentPortraitVC or ContentLandscapeVC, depending on the current orientation (though it doesn't matter why the container chooses its view, I presume). ContentPortraitVC, at some point pops up ContentModalDetailVC.
So there are two different methods of displaying new content at work here:
the parent-and-child relationship (instigated via addChildViewController and removed via removeFromParentViewController),
the presenting-and-presented relationship (instigated via presentViewController and removed via dismissViewController).
If the ContainerVC adds the ContentPortraitVC, which then presents the ContentModalDetailVC, and then the ContainerVC decides to switch to the ContentLandscapeVC, the ContentModalDetailVC stays visible (why is it not removed when its parent is removed?)
But then, when the ContentPortraitVC is asked to remove the ContentModalDetailVC, nothing happens. The modal display stays put. What is going on?
When you use addChildViewController to add the ContentPortraitVC:
a. The ContentPortraitVC gets its parentViewController property set.
b. You then (as per the Apple documentation) have to manually display the ContentPortraitVC's view. If you follow the documentation you do this by adding it as a child of the ControllerVC's top level view.
The ContentPortraitVC then calls presentViewController to display ContentModalDetailVC.
a. This sets its presentingViewController property (in the debugger this is shown as the _parentModalViewController ivar -- note the ivar is different from the property), and sets the presentedModalViewController property of the ContentPortraitVC (who's ivar is _childModalViewcontroller).
b. Views wise, on iPhone, the ContentModalDetailVC's view will completely replace the views from ContentPortraitVC and ContainerVC, so only the modal view controller's view will be visible. (on iPad, it layers the new UI over the top, but as a sibling of the ControllerVC's view, which in turn is the parent of ContentPortraitVC's view).
So now, you transition from ContentPortraitVC to ContentLandscapeVC.
a. IOS does a bit of magic. It knows that the thing you are removing (ContentPortraitVC) has a presentedViewController currently active, so it changes its parent. It sets the value to nil on ContentPortraitVC, takes the child (the ContentModalDetailVC) and sets its parent to the new view (ContentLandscapeVC). So now the view controller that presented the modal view is no longer its presenting view controller. It is as if ContentLandscapeVC presented it in the first place!
b. In terms of views, you follow the Apple docs to change over the view from ContentPortraitVC to ContentLandscapeVC. But you are simply changing the subviews of ControllerVC's view. On iPhone, the modal view controller is still the only thing being displayed, so making the change doesn't do anything on screen. On iPad, it does (though you probably won't see it, as the modal view is usually full screen).
Now you come to dismiss the modal view. Presumably you do this in ContentPortraitVC, but it no longer has any reference to the thing it presented. So calling [self dismissViewController... does nothing, because ContentPortraitVC is no longer presenting anything, responsibility for that has been passed on to ContentLandscapeVC.
So that's what happens and why. Here's what to do about it.
You can rewire the delegate manually when you change from ContentPortraitVC to ContentLandscapeVC, so the latter is the one that tries to dismiss the modal controller.
You can have the modal controller dismiss itself with [self dismissModalControllerAnimated:YES completion:nil]. I'm going to ask and answer another question on why that works (how does IOS know which to dismiss?), if that seems strange.
You can have the ControllerVC be the one that pops up the modal view and be responsible for removing it.
If you inspect presentingViewController on ContentModalDetailVC, you will see that it is actually presented by ContainerVC and not ContentPortraitVC.
To fix this, you just need to set definesPresentationContext (or use the "Defines Context" checkbox in Interface Builder) on ContentPortraitVC.
This will tell ContentPortraitVC to handle the modal presentation instead of passing up the responder chain to the next view controller that defines presentation context (your root view controller by default).
You will probably want ContentLandscapeVC to define context as well to avoid the same issue.
With both child controllers defining their own presentation context, when ContainerVC decides to swap children, any modal modal will be removed from the new hierarchy along with the child that presented it. No need to do hacky things to try to dismiss before swapping :)
Edit: I should add that the view controller being presented must have its modalPresentationStyle Set to either currentContext or overCurrentContext,
I have a modal view which is launched from the current view controller, as
[self presentModalViewCOntroller:modalViewController animated:TRUE];
The modal view controller dismisses itself when someone hits a button.
[self dismissModalViewControllerAnimated:TRUE];
A couple of screens later, I attempt to swap the root view within the window. I do this all the time with no trouble. But in a certain case, when switching the one view within the window, the picker delegate method is being called on the modal view controller even thought it was dismissed a while ago.
This is very strange because the modal view controller is usually deallocated when dismissModalViewController is called.
Why is a view from the modal view controller being invoked?
It appears that someone, probably the window still has a reference. Are you supposed to do something else in addition to dismissModalViewController?
Thanks
DismissModalViewController should be enough. It does seem like you have a problem with some reference hanging around that you don't intend. Without seeing more code, I can't point to anything specific.