Nav Controller crashes on dismiss view controller - ios

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?

Related

iOS presentViewController doesn't invoke viewDidLoad

I'm implementing my own 'back' button. Where onClick, the following code is executed in the ViewController (VC) being dismissed:
Dismiss current VC (VC#1)
Pop current VC (VC#1) off my custom navigationStack
Get the last VC (VC#2) from the navigationStack, and present it using
presentViewController
What happens is the back works visually works - i.e. current VC disappears, previous VC appears. However, the viewDidLoad method is not called. So the screen isn't updated with data updates from viewDidLoad.
[self dismissCurrentViewController:self completion:^{
[TWStatus dismiss];
FHBaseViewController *vcToDisplay = [[FHDataManager sharedInstance] popNavigationStack];
[vcToDisplay.homeVC presentViewController:vcToDisplay animated:NO completion: ^{ }];
}];
Questions:
I was under the impression that viewDidLoad always gets called when presentViuewController is used??
I 'build' the screen using a method called ONLY from viewDidLoad in VC#2. How is iOS displaying the screen without coming into viewDidLoad?
btw, I'm not using storyboards. Any help is appreciated!
My guess is that viewWillAppear is being called but viewDidLoad is not, at least not when you expect it is. viewDidLoad should be called once, but depending on how you're managing the view controllers, viewDidLoad may not be triggered every time your view appears (which happens after loading).
The completion handler is called after the viewDidAppear: method is called on the presented view controller. from presentViewController doc
so put this in your code with a breakpoint on the call to super and verify it is getting called when this transition occurs.
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
}
edit: since you verified that viewWillAppear is getting called, then I would say that it's coming down to how you are managing the view controller life cycle. Even with a standard UINavigationController, viewDidLoad is not called when a view is shown as a result of popping items on the navigation stack. I would move your logic to viewWillAppear if you are dead set on not using UINavigationController
When I make a back button pragmatically I use:
[self.navigationController popViewControllerAnimated:YES];
This will invoke the viewDidLoad method. Use that instead of your current code.

popToRootViewControllerAnimated returning NSArray of "popped" VCs, but not calling viewWillDisappear

I'm currently working on a project which implements a custom navigation controller, whose code is here:
https://gist.github.com/emilevictor/724a6602fedb8100650c
In one of my controllers, which gets pushed to the navigationController via a push segue, I have an action on a button to return to the main screen:
- (IBAction)returnToMainScreen:(id)sender
{
NSArray *returnedControllers = [self.navigationController popToRootViewControllerAnimated:YES];
NSLog(#"Popped to root view controller.");
}
This will return the current view controller and one preceding it in the returnedControllers array.
However, it doesn't change screens, or call any viewDidDisappear functions. Anyone know why?
Make sure you child viewControllers are being added to the parent viewController with the method [UIViewController addChildViewController:]

Reload a tab after dismissing a view

There are two views in a tabbarviewcontroller. And the first tab has its view controller called myViewController which contain its IBOutlet. Pressing the button on the first view will present a view controller. After dismissing the view and back to the tab, the viewWillAppear of myViewController won't be called,but viewWillAppear of tabbarviewcontroller will. I need to reload the information on the first tab. If I use viewWillAppear in tabbarviewcontraller, how do I change the values of these property in myViewController? If anyone has idea ? Thanks.
If you want to call viewWillAppear of the viewController, you can add this to viewWillAppear of the TabBar
for (UIViewController *viewController in tabBarController.viewControllers)
{
[viewController viewWillAppear:YES]
}

viewDidLoad (and loadView) is not fired after the view controller is pushed into navigation controller

viewController's view is not loaded just after that viewController is pushed into navigation controller.
This is my code snippet.
- (void)myMethodInClassA {
// window's root view controller is navigation controller
UINavigationController *naviCtrl = (UINavigationController*)[UIApplication sharedApplication].keyWindow.rootViewController;
MyViewController *myVC = [[MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil];
[naviCtrl pushViewController:myVC animated:NO];
// at this point, myVC's view is NOT loaded
}
When I call myMethodInClassA, myVC's viewDidLoad is called AFTER that method returns. I'd expected that myVC's view is loaded just after navigation controller's pushViewController:animated: is called and before myMethodInClassA returns.
When exactly view controller's view is loaded? Apple's documentation just says it is loaded when it is first accessed. It's a bit ambiguous. why doesn't navigation controller's pushViewController: access view controller's view?
p.s. sorry for initial ambiguous question.
Pushing a view controller (VC) onto a navigation controller's stack makes the VC into a child view controller of the navigation controller (which is a container view controller). Creating such a child-parent relationship is a distinct step which does not cause the child VC's view to be loaded immediately. Rather the container VC loads the view at a later time. I believe there is no explicit specification for what "later" means - usually it will be when the container VC has decided that the time has come to integrate the child VC's view into the container VC's view hierarchy. But basically it simply happens at the discretion of the container VC's implementation.
That being said, anyone can force a VC's view to be loaded by simply accessing the VC's view property. For instance, in your code you could add this line
myVC.view;
which would trigger loadView and then viewDidLoad in MyViewController.
However, in your case if MyViewController needs to react to the event that it has been associated with a container VC, then it would be better to override one (or both?) of the following methods in MyViewController:
- (void) willMoveToParentViewController:(UIViewController*)parent
{
// write your code here
}
- (void) didMoveToParentViewController:(UIViewController*)parent
{
// write your code here
}
You need to be aware, though, that willMoveToParentViewController and didMoveToParentViewController are also invoked when MyViewController is popped from its parent navigation controller's stack. You can detect that this is the case by checking the parent argument for nil.
(Swift 2)
Since this question doesn't have an accepted answer...
What I ended up doing is create a convenience init at the child view controller:
convenience init() {
self.init(nibName: "ChildViewController", bundle: nil)
//initializing the view Controller form specified NIB file
}
and in the parentViewController's viewDidLoad():
let commentsView = CommentsViewController()
self.addChildViewController(commentsView)
self.momentsScrollView.addSubview(commentsView.view)
commentsView.didMoveToParentViewController(self)
As stated above,viewDidLoad gets called once when a view is pushed,you might want to do your stuff in viewWillAppear or viewDidAppear.
Ya if that ViewController will be already pushed in navigationController stack then ViewDidLoad method will not be called again.
First time when you will push that ViewController then viewDidLoad will be called.
So if you need that your some functionality is to be executed every time then implement it in viewWillAppear method because it will be called every-time you push your viewController.
Hope it helps you.
are you pushing the view controller for the first tym?if YES then only viewDidLoad() of the controller will be called and if its already pushed and this is not the first tyn then viewWillAppear () will be called.(or) if you are making a new instance every tym u push it then viewDidLoad() will be called.
I find that I have to call loadViewIfNeeded()
https://developer.apple.com/documentation/uikit/uiviewcontroller/1621446-loadviewifneeded

modal view controller not calling presenting view controller's dismissModalViewControllerAnimated: method

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.

Resources