In my modal view controller I have a button handling method that includes
[self dismissModalViewControllerAnimated: YES];
In the presenting view controller I override dismissModalViewControllerAnimated: as follows:
-(void) dismissModalViewControllerAnimated: (BOOL)animated
{
NSLog(#"dismiss");
[super dismissModalViewControllerAnimated: animated];
}
When the button is touched, the button handling method gets called, but the dismissModalViewControllerAnimated: override does not seem to get called: the NSLog(#"dismiss"); statement isn't called, and a breakpoint inside the method doesn't get hit.
I tried
[[self presentingViewController] dismissModalViewControllerAnimated: YES];
but that didn't work either. However, the modal view controller does get dismissed.
Any idea what might be going wrong?
This is normally handled by declaring your presenting view controller as a delegate for your modal view controller. The modal VC then called a delegate method in the presenting VC to dismiss the modal transition it created.
Example:
Modal VC.h:
#protocol ModalViewControllerDelegate
-(void)dismissMyModalViewController;
#end
Modal VC.m:
// When you want to dismiss the Modal VC
[delegate dismissMyModalViewController];
Presenting VC.h:
// Make sure to #import ModalVC.h
#property (nonatomic, retain) id <ModalViewControllerDelegate> delegate;
Presenting VC.m:
-(void)dismissMyModalViewController {
[self dismissModalViewControllerAnimated:YES];
}
from Programming iOS 6, by Matt Neuburg:
On the iPad, when the presented view controller’s modalPresentationStyle is UIModalPresentationCurrentContext, a decision has to be made as to what view controller should be the presented view controller’s presentingViewController. This will determine what view will be replaced by the presented view controller’s view. This decision involves another UIViewController property, definesPresentationContext (a BOOL). Starting with the view controller to which presentViewController:animated:completion: was sent, we walk up the chain of parent view controllers, looking for one whose definesPresentationContext property is YES. If we find one, that’s the one; it will be the presentingViewController, and its view will be replaced by the presented view controller’s view. If we don’t find one, things work as if the presented view controller’s modalPresentationStyle had been UIModalPresentationFullScreen.
TL;DR
1. set definesPresentationContext to true on the desired presentingViewController
2. set modalPresentationStyle to UIModalPresentationCurrentContext on the desired presentedViewController
The code that presented the modal view controller was contained in a UIViewController, which was in turn contained in a UINavigationController. When I called
[[self presentingViewController] dismissModalViewControllerAnimated: YES];
or
[self dismissModalViewControllerAnimated: YES];
the dismissal message was being sent to the UINavigationController object.
Related
I need to do a custom presentation animation and when i set both these setTransitioningDelegate and modalPresentationStyle=UIModalPresentationCustom
The animation is perfect with unless the viewDidAppear and viewDidDisappear is not called in the presenting viewcontroller.This is same for Apple sample code in https://developer.apple.com/library/ios/samplecode/LookInside/Introduction/Intro.html
[overlay setTransitioningDelegate:[self transitioningDelegate]];
overlay.modalPresentationStyle=UIModalPresentationCustom;
[self presentViewController:overlay animated:YES completion:NULL];
Why the methods are called when no modalPresentationStyle is given?
This is the correct behaviour as presenting a new view controller only hides the presenting view controller. It doesn't add the view to the hierarchy when the presented view controller is dismissed, and it doesn't remove the presenting view controller view from the hierarchy when the view controller that is presented is presented.
Short story; it hides the view of the presenting view controller instead of removing it. Therefore the methods aren't invoked.
I am presenting a view controller modally using this code :
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
SubmitAYoNViewController *ivc = [storyboard instantiateViewControllerWithIdentifier:#"SubmitAYoN"];
[ivc setModalPresentationStyle:UIModalPresentationFullScreen];
[self presentViewController:ivc animated:YES completion:nil];
Then in my SubmitAYoNViewController I have this:
NSLog(#"%#",self.parentViewController);
if([self.parentViewController isKindOfClass:[YesOrNoViewController class]]) {
NSLog(#"do something");
}
self.parentVeiwController is NULL. Why is that?
EDIT : I want to access a method from my parent view controller, then one that the SubmitAYoN was opened from.
SOLUTION : I used delegates instead. As per answers below, the presentViewController does not create a parent-child relationship.
If you want the presenting view controller, then use self.presentingViewController. self.parentViewController returns the controllers parent which will be nil when it is not a child of another controller.
So use:
NSLog(#"%#",self.presentingViewController);
if([self.presentingViewController isKindOfClass:[YesOrNoViewController class]]) {
NSLog(#"do something");
}
From the documentation for presentViewController:
This method sets the presentedViewController property to the specified
view controller, resizes that view controller's view based on the
presentation style and then adds the view to the view hierarchy.
So no mention of the controller being made a child as it is the view that is added to the existing view hierarchy.
You would have been ok prior to iOS5. The documention for property parentViewController states:
Prior to iOS 5.0, if a view did not have a parent view controller and
was being presented, the presenting view controller would be returned.
On iOS 5, this behavior no longer occurs. Instead, use the
presentingViewController property to access the presenting view
controller.
self.parentViewController is something you presented from this viewcontroller or self.navigationgtionController
So when you are presenting a viewcontroller where you hold an instance of the `viewcontroller' will have self.parentViewController.
I'm experiencing a memory leak (the UINavigationController and its root View Controller are both being leaked) when presenting and dismissing a UINavigationController in a subview. My method of presentation of the navigation controller seems a bit non-standard, so I was hoping someone in the SO community might be able to help.
1. Presentation
The Navigation Controller is presented as follows:
-(void) presentSubNavigationControllerWithRootViewControllerIdentifier:(NSString *)rootViewControllerIdentifier inStoryboardWithName:(NSString *)storyboardName {
// grab the root view controller from a storyboard
UIStoryboard * storyboard = [UIStoryboard storyboardWithName:storyboardName bundle:nil];
UIViewController * rootViewController = [storyboard instantiateViewControllerWithIdentifier:rootViewControllerIdentifier];
// instantiate the navigation controller
UINavigationController * nc = [[UINavigationController alloc] initWithRootViewController:rootViewController];
// perform some layout configuration that should be inconsequential to memory management (right?)
[nc setNavigationBarHidden:YES];
[nc setEdgesForExtendedLayout:UIRectEdgeLeft | UIRectEdgeRight | UIRectEdgeBottom];
nc.view.frame = _navControllerParentView.bounds;
// install the navigation controller (_navControllerParentView is a persisted IBOutlet)
[_navControllerParentView addSubview:nc.view];
// strong reference for easy access
[self setSubNavigationController:nc];
}
At this point, my expectation is that the only "owner" of the navigation controller is the parent view controller (in this case, self). However, when dismissing the navigation controller as shown below, it is not deallocated (and as a result its rootViewController is also leaked, and so on down the ownership tree).
2. Dismissal
Dismissal is pretty simple, but it seems not to be sufficient for proper memory management:
-(void) dismissSubNavigationController {
// prevent an orphan view from remaining in the view hierarchy
[_subNavigationController.view removeFromSuperview];
// release our reference to the navigation controller
[self setSubNavigationController:nil];
}
Surely something else is "retaining" the navigation controller as it is not deallocated. I don't think it could possibly be the root view controller retaining it, could it?
Some research has suggested that retainCount is meaningless, but FWIW I've determined that it remains at 2 after dismissal, where I would expect it to be zero.
Is there an entirely different / better method of presenting the subNavigationController? Maybe defining the navigation controller in the storyboard would have greater benefit than simply eliminating the need for a few lines of code?
It is best practice when adding a controller's view as a subview of another controller's view, that you make that added view's controller a child view controller; that is, the controller whose view your adding it to, should implement the custom container controller api. An easy way to set this up is to use a container view in the storyboard which gives you an embedded controller automatically (you can select that controller and, in the edit menu, choose embed in Navigation controller to get the UI you're trying to make). Normally, this embedded view controller would be added right after the parent controller's view is loaded, but you can suppress that by implementing shouldPerformSegueWithIdentifier:sender:. I created a simple test app with this storyboard,
The code in ViewController to suppress the initial presentation, and the button methods to subsequently present and dismiss it is below,
#implementation ViewController
-(BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
if ([identifier isEqualToString:#"Embed"]) { // The embed segue in IB was given this identifier. This method is not called when calling performSegueWithIdentifier:sender: in code (as in the button method below)
return NO;
}else{
return YES;
}
}
- (IBAction)showEmbed:(UIButton *)sender {
[self performSegueWithIdentifier:#"Embed" sender:self];
}
- (IBAction)dismissEmbed:(UIButton *)sender {
[[self.childViewControllers.firstObject view] removeFromSuperview];
[self.childViewControllers.firstObject willMoveToParentViewController:nil];
[self.childViewControllers.firstObject removeFromParentViewController];
}
#end
The navigation controller and any of its child view controllers are properly deallocated when the Dismiss button is touched.
The navigationController property on a UIViewController is retain/strong, which is presumably the other strong reference.
So try popping all view controllers from the navigation controller and see if that works.
I am receiving 2 errors while a dismissal is happening in my app.
Warning: Attempt to dismiss from view controller <MyNavigationController> while a presentation or dismiss is in progress!
&
Unbalanced calls to begin/end appearance transitions for <MainViewController>.
I've searched around and everywhere says that there is usually a conflicting dismissal happening where a button is calling the transition both programmatically and through the storyboard. I, however, am receiving these errors when using the normal back button that comes with nav controllers. I don't do anything with the button at all.
The only thing I can link to the errors is that my nav controller is autorotating while trying to dismiss the view controller. If I remove autorotate or set the orientation of both view controllers to be the same then I don't get the error. Problem is, I need one of the view controllers to be portrait and the other to be landscape...
This is how I set the orientation
NavController.m:
- (NSUInteger)supportedInterfaceOrientations {
return self.topViewController.supportedInterfaceOrientations;
}
- (BOOL)shouldAutorotate {
return YES;
}
MainViewController.m:
- (NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskPortrait;
}
OtherViewController.m:
- (NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskLandscapeRight;
}
I've noticed that for some reason it doesn't autorotate when going to my "OtherViewController", but it apparently tries to autorotate while returning to the "MainViewController", thus causing the crash.
Since it may be relevant, this is how I load my OtherViewController:
[self performSegueWithIdentifier:titles[indexPath.row] sender:nil];
I have a CollectionViewController that calls a push segue I have set up in the storyboard. Titles is an NSArray of the different segue titles connected to the MainViewController.
Here is the flow of what's going on in my app:
MainViewController : LoadView
MainViewController : ViewWillAppear
MainViewController : ViewDidAppear
//This is where I choose to load the OtherViewController
OtherViewController : LoadView
MainViewController : ViewWillDisappear
OtherViewController : ViewWillAppear
MainViewController : ViewDidDisappear
OtherViewController : ViewDidAppear
//This is where I select the "Back" button
Warning: Attempt to dismiss from view controller NavController while a presentation or dismiss is in progress!
Unbalanced calls to begin/end appearance transitions for MainViewController.
MainViewController : ViewWillDisappear
MainViewController : ViewDidDisappear
For a detailed analysis we need more code, especially where the view controller is dismissed.
As for your error message the dismissal call, whichever you use, is called in the middle of some other workflow of showing or dismissing a view controller. That could be when you present or dismiss a modal view controller or when you push or pop one on the navigation stack in
loadView
viewDidLoad
viewWillAppear
viewDidAppear
viewWillDisappear
viewDidDisappear
This list is certainly incomplete but that should be the most common methods that are called within the process.
So share some more code with us.
Without getting into too much details, you need to find a way to do one after the other. The tricky part is that there is no delegate that tells you when a view was dismissed after a segue. However, after a segue, when viewWillAppear is called, the previous view was dismissed.
Maybe try to rotate the view from the code, and do so in the viewWillAppear?
I have a view controller B in a parent view controller A. Both views are showing the same time.
In view controller B, I'm trying to present a new view controller using the following method:
- (void) buttonClicked:(id)sender
{
MyViewcontroller *vc = [[MyViewcontroller alloc] init];
[self presentViewController:vc animated:YES completion:nil];
}
The view controller appears correctly in iOS6 and I dismiss MyViewController by using the following method:
- (IBAction)backButtonPressed:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
But there is a difference between iOS 5.1 and iOS 6.
QN1: Upon dismissal, view controller A & B viewDidAppear are not invoked. Is it supposed to be triggered?
QN2: I can't get MyViewController to show up in iOS 5.1.1. unless I add view controller B as a child container to A:
[self addChildViewController:vcB];
[self.view addSubview:vcB.view];
By adding the child controller, I can get MyViewController to show and view controller A&B viewDidAppear will be called when it gets dismissed. viewDidAppear also gets called when using iOS6.
I'm not sure what is going on here.
Answer 1 : viewDidAppear will not be called when you dismiss a modal view.
Answer 2 : if you are presenting "MyViewController" from "View-controller B" then View-controller B's view should be in view hierarchy.
From here you can get more information. How to Presenting View Controllers from Other View Controllers