View controller container sporadically misses viewWillAppear in child controllers - ios

I have a root view controller with subview to be wrappers for views of child view controllers. The basic idea is my root controller has a left and right view controller, both present on screen (similar to a splitviewcontroller). On load a modal view pops up over the root view controller and asks for details. The modal view then contacts a server, and is dismissed after getting a response. The root controller then adds the child view controllers with the following code:
[self addViewController:self.leftViewController];
[self addViewController:self.rightViewController];
[self addView:self.rightViewController.view ToWrapper:self.rightViewWrapper];
[self addView:self.leftViewController.view ToWrapper:self.leftViewWrapper];
Where add view controller is:
[self addChildViewController:controller];
[controller didMoveToParentViewController:self];
and addViewToWrapper just adds the view controller's view to the relevant subview of the rootViewController as follows:
[[viewWrapper.contentView subviews] makeObjectsPerformSelector:#selector(removeFromSuperview)];
newSubview.frame = viewWrapper.contentView.bounds;
newSubview.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
[viewWrapper.contentView addSubview:newSubview];
99% of the time this works fine. Both views appear instantly and viewWillAppear fires in both child controllers. However, occasionally the screen stays white for a brief period and viewWillAppear doesn't fire in the right view controller (EDIT: and the left). All of the other view lifecycle methods fire, just not viewWillAppear.
Unfortunately, I can't give code for the whole class as it is complex and proprietary. But are there any clues in this description for this intermittent behaviour?

Some thoughts on this:
ONE
It looks like your containment methods are not being called properly. They should look like this for each viewController:
[self addChildViewController:controller];
[self.view addSubview:controller.view];
[controller didMoveToParentViewController:self];
The view should be added to the superview in between the addChild and didMove calls.
TWO
To my knowledge, there cannot be more than one presentation or dismissal occurring at a time. I.e. If you are trying to present (add) your child viewControllers at the same time as the modal is being dismissed, you will see an error in the console log and your "add" operation will not occur.
My recommendation would be to create a delegate protocol on the modal viewcontroller. And in the completion block of dismissViewControllerAnimated:completion:, call your delegate method:
[self dismissViewControllerAnimated:YES completion:^{
if ([weakSelf.delegate respondsToSelector:#selector(settingsViewControllerDidDismiss:)])
{
[weakSelf.delegate settingsViewControllerDidDismiss:self];
}
}];
And in your rootViewController, you would begin adding its children inside of settingsViewControllerDidDismiss or whatever you decide to call that method. The point is that the "add" operation begins AFTER the dismissal operation.
Hope this helps.

Related

How to prevent flickering when showing a view controller in another view controller

I want to show a view only one time when a main view controller is shown for the first time.
-(void) viewDidAppear:(BOOL)animated
{
if (!self.isMainViewShowedBefore)
{
self. self.isMainViewShowedBefore = YES;
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController: temporaryViewController];
[self presentViewController:navigationController animated:NO completion:NULL];
}
}
It works but the main screen appears for a short time before the temporary view controller is shown.
I tried to add above code in viewWillAppear but I got below error
'NSInternalInconsistencyException', reason: 'Attempting to begin a
modal transition from to
while a transition is already in
progress. Wait for viewDidAppear/viewDidDisappear to know the current
transition has completed'
How can I hide the main controller before the temporary view controller is shown?
Add temporaryViewController.view as a subview of self.view (your main view controller)
[self.view addSubview:temporaryViewController.view];
You got the error in viewWillAppear because you can't do two animations together in iOS, it might get your app crashed but most of the time iOS is nice to us, it just gives us a warning.
Now my main concern, why would you add this main view controller if you don't want to use it at all? and this is not flickering, this is a normal behaviour, if you want the UINavigationController to appear, simply sow it instead of this "main view controller".
If you have more hidden logic that would prevent you from doing what I said, please tell me.

Getting black screen when trying to remove ViewController from display in iOS

I have an iOS application using storyboards where I display a view controller that I create from an .xib file to the user. This view controller accepts some user input, but I then have to dismiss it and return to the main application. I am able to display the view controller, which also has a button that calls a method to dismiss the view controller. My problem is that after the user presses the button to go back to the main application, the entire screen goes black. Here is my code for the button from the .xib view controller that is trying to remove itself from the display:
- (IBAction)myButtonAction:(id)sender {
[self.view removeFromSuperview];
}
Here is the code from my main application's view controller which calls the .xib view Controller in the first place:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
_nextView = [[NextLandscapeViewController alloc] initWithNibName:#"NextLandscapeViewController" bundle:nil];
[_nextView setDelegate:(id)self];
NextNavigationController *navigationController = [[NextNavigationController alloc] initWithRootViewController:_nextView];
[self presentViewController:navigationController animated:YES completion:nil];
}
NextNavigationController is a subclass of UINavigationController which I do for the purpose of loading _nextView in landscape mode instead of portrait mode. This part is working fine. My concern now is dismissing this viewController after the user is finished working with it, and return back to the calling view controller in the main application.
Is there any reason why my screen is black? How I can resolve this issue?
Don't use removeFromSuperview, use [self dismissViewControllerAnimated:YES completion:nil]; Just like you use a pop to undo a push, you use dismissViewController to undo a presentViewController. The reason you get a black screen is because presenting a view controller removes the view of the presenting view controller from the window's hierarchy. So, when you remove the view from the superview, there's nothing underneath but the window.

Unable to display view when I call it from a separate ViewController in iOS

I have a view controller in my application where on my screen I have a UIView that the user is required to tap on. When they do that, I want to call another viewController's view, and display it on the screen for the user. Unfortunately, I am having trouble displaying the view.
The name of my viewController that I am making the call from is called "MainViewController", and the ViewController whose view I wish to display is called, "NextViewController"
Here is my code from where I make the call:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"I was touched.");
_nextView = [[NextViewController alloc] init]; //this code is not being called
[self.view addSubview:_nextView.view]; //neither is this being called
}
Where _nextView is a property that I declare in the .h file of MainViewController.
This method is being called, but for some reason because I am able to see the log statements print to the output, but for some reason I am unable to call the lines after that. What am I doing wrong?
You shouldn't add the view of another view controller to your view without making that view controller a child view controller. If you just want a view, then set one up in a xib file and add it to your view as a subview. If you want to use a view controller, then you should present it modally, and dismiss it when you're done. This kind of situation where you want to gather some info from the user to use in your app, is an appropriate place to use a modal view controller. MainViewController should set itself as the delegate of NextViewController, and NextViewController should define a delegate protocol to send the data back to MainViewController.
To present it modally, do this:
_nextView = [[NextViewController alloc] initWithNibName:#"your nib name here" bundle:nil];
[self presentViewController:_nextView animated:YES completion:nil];
Are you using a Navigation Controller? Or Storyboards? One way of displaying another view controller would be like this:
[self presentViewController:_nextView animated:YES completion:^{
}];
A couple of things:
- If your NSLog gets called, then so do the other two lines you say do not.
- I assume you mean you want to display the other view controller on screen, not display the other view controller's view on the first view controller. These are two very different things, the second of which you wouldn't want to do.

How to dismiss view controllers at any time (even during transitions) or when it is safe to dismiss a view controller ?

I have an iOS app that has a connection to a server. If we get disconnected, I want to be able to dismiss the top view controllers to get back to a "connecting to server" view controller. The problem is that a disconnection can occur at any time, including during a transition between view controllers.
The view controller hierarchy is like so:
ConnectingToServerViewController
SignInViewController
MainAppViewController
Other view controllers
When a disconnection is detected I want the view hierarchy to collapse back to:
ConnectingToServerViewController
So when a disconnection is detected, this method is called on the ConnectingToServerViewController to dismiss anything that it has presented and go back to attempting to connect to server:
- (void)restartSession
{
if (self.presentedViewController) {
[self dismissViewControllerAnimated:NO completion:nil];
}
}
However, if I try to dismiss while a view transition is occurring, I get errors such as
*** Assertion failure in -[UIWindowController transition:fromViewController:toViewController:target:didEndSelector:], /SourceCache/UIKit/UIKit-2380.17/UIWindowController.m:211
attempt to dismiss modal view controller whose view does not currently appear. self = <YYYYYViewController: 0x2089c8a0> modalViewController = <XXXXXViewController: 0x208e6610>
attempt to dismiss modal view controller whose view does not currently appear. self = <WWWWWWViewController: 0x1fd9e990> modalViewController = <YYYYYViewController: 0x2089c8a0>
The first of which will crash the app, the second will just not dismiss anything and continue to show the current presented view controller.
Thoughts:
delays won't work since we don't know when to start the delay
is there a way to track when view transitions complete?
should all view controllers override willAppear, didAppear and alert the app when it is safe to dismiss?
perhaps instead of dismiss, I should just set a new root view controller?
I've made sure that all overridden view(will|did)(dis)?appear methods call the appropriate super method.
Any solution that requires all view controllers to override view(did|will)appear methods to track state sounds like it could cause issues if we forget to set the base class for a new view controller.
Do something like this. Try this out once,
UIViewController *controller = self.presentingViewController; //THIS LINE IS IMP
[self dismissViewControllerAnimated:YES
completion:^{
[controller presentViewController:adminViewController animated:YES completion:nil];
adminViewController.view.superview.frame = CGRectMake(1024/2 - 400, 768/2 - 280, 800 , 560);//it's important to do this after
[adminViewController release];
}];
One way that has worked for me is to assign a new view controller to the root view controller. That way, views in the old hierarchy can animate and transition to their hearts content while we have new controllers.
eg
- (void)restartSession
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
ConnectingToServerViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"ConnectingToServerViewController"];
vc.modalPresentationStyle = UIModalPresentationFullScreen;
[UIApplication sharedApplication].delegate.window.rootViewController = vc;
}
I'm not sure if I'm aware of all the downsides to this though. Perhaps the old view controllers will never get freed because of a dangling strong reference? We're no longer reusing ConnectingToServerViewController, we have to recreate that each time.
I based the code on what I saw in this answer for Managing and dismissing Multiple View Controllers in iOS.
It seems like you are trying to dismiss the view controller when it is not currently on screen. To check if it is on screen you could use:
if (self.presentedViewController.view.window)
{
[self dismissViewControllerAnimated:NO completion:nil];
}
else
{
self.presentedViewController = nil;
}
I will answer in order.
is there a way to track when view transitions complete?
You could try with the UINavigationControllerDelegate (if you are using one of those). Other approach could be using a custom animator.
should all view controllers override willAppear, didAppear and alert the app when it is safe to dismiss?
That's an option. You are free to do it if you want. Another option is not to do that. I think that container view controllers such as navigation controller has better approaches.
I should just set a new root view controller?
I would suggest to do the opposite. I would set the SignInViewController / MainAppViewController as the root flow, and present modally ConnectingToServerViewController on demand. In my opinion that's a healthier approach.
Hope it helps.

ViewDidAppear not called in main view when modal view is presented over a popover

I'm trying to use a popover as an intermediary menu between my main view and a modal view controller. I can successfully present the Modal view controller from the popover by using the following code:
UIStoryboard *storyboardiPad = [UIStoryboard storyboardWithName:#"MainStoryboard_iPad" bundle:nil];
cbwEditControlPanel *editCP = [storyboardiPad instantiateViewControllerWithIdentifier:#"EditCP"];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:editCP];
[nav setToolbarHidden:NO];
[nav setModalPresentationStyle:UIModalPresentationFullScreen];
[nav setModalTransitionStyle:UIModalTransitionStyleCoverVertical];
[self presentViewController:nav animated:YES completion:nil];
self.modalInPopover = NO;
The problem I'm running into is that when the EditCP modal view controller is dismissed, the main view controller never updates. I have a pagecontroller on the main view that should be updated to reflect the number of pages as set in the EditCP modal view controller, but for some reason the modal view controller being called from the popover prevents the main view controller from updating the pagecontroller. I've even tried calling the main view's "View Will Appear" method from the popover or modal view when they are dismissed, but even if the 'viewWillAppear' method is called the pageController will not update!
Any ideas what is preventing the pageController from updating? I even passed a reference to the pagecontroller to the modal view and tried to update it there, but it seems that from the time the popover is presented until it is dismissed, I cannot update the number of pages on the PageController.
Thank you!
So this is an old question but I also came across a similar problem recently when using a popover. My solution was to use an unwind segue to trigger my parent view to perform some action. In my case my parent view contains contact information and the popover contains a list of cites. All I wanted to do was to have the parent view update with the new city once the user selected it from the popover. So in my parent view I create my unwind function as follows:
In the .h:
- (IBAction)unwindToContactTVC:(UIStoryboardSegue *)unwindSegue;
In the .m:
- (IBAction)unwindToContactTVC:(UIStoryboardSegue *)unwindSegue
{
[self updateTableForOffice];
}
In the above .m file is where you would have the logic to do whatever it is you want to in the parent view. To connect this unwind segue go to the child view in the storyboard and control drag from the view icon to the exit icon. You should see a pop up with the name of your unwind segue.
Finally, give that unwind segue a name and then in the child controller in the viewWillDisappear() function call the segue as follows:
- (void)viewWillDisappear:(BOOL)animated
{
[self performSegueWithIdentifier:#"unwind-to-contact-tvc" sender:self];
}
I hope that helps. If someone has a better solution let me know.
Well, I half solved the problem. The only way to get an update function when the popover disappeared was to stop using Storyboards and programmatically present the popover, using the main view as the delegate. I then was able to update correctly inside the popoverControllerDidDismissPopover method.
However, I am still interested in finding a way to update the pageControl when the modal is dismissed, before the popover is dismissed.

Resources