So I've got a screen that does a check for certain attributes and under defined circumstances will instantly load another view modally in viewDidLoad, without animation, over the currently-loading view (so as not to show the view below). Prior to iOS 8 when this was done, the original view would pause its loading (would not proceed with viewWillAppear, viewDidLayoutSubviews etc.) until the overlaying controller was dismissed. This behaviour I found was appropriate for my needs, as any animation on elements in the original view, could then be done. However, in iOS 8 I'm getting a completely different chain of events. First off, for some reason viewDidLayoutSubviews is being called twice (what's up with that?) but more importantly the view is not liking another controller being popped up at all anytime before viewDidAppear, complaining about unbalanced calls to begin/end appearance transitions. Not only that, but the underlying viewController continues with it's loading (viewWillAppear,viewDidLayoutSubviews etc.) even though it's not being shown which causes all the methods in those events to fire. I appreciate if Apple have updated the way something like this is meant to be achieved, so if the new meta is a completely different process I'm willing to adopt, however, as it is I can't get this to work appropriately.
I'd appreciate any help on how to get this modal view to interject without causing the underlying view to continue it's loading.
Thanks,
Mike
UPDATE: Going to bring some code in. Below is the viewDidLoad of the main viewController that presents the modal VC if need.
-(void) viewDidLoad{
if(hasNotSeenTutorial){
TutVC* vc = [[TutVC alloc] initWithNibName:#"tutNib" bundle:nil]
vc.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self.navigationController presentViewController:vc animated:NO completion:^{
NSLog(#"Has Completed Presentation");
}];
}
}
This is where the issues are. Calling the presentation here in viewDidLoad, causes the presentation of the presenting VC to continue. Prior to iOS 8 the presenting VC if not yet presented, would pause, until the modal VC had been dismissed, it would then complete as usual. This is not the case in iOS 8, as per my original post.
Apple has made its rules stricter with ios 8. To give you an example and I ll drive my point through this:- In my app i used to pop some view controllers off the navigation stack and just after that, push the a new one, but that pop was never seen in ios7, only a push transition appeared to happen (when logically, pop should have been seen and then the push). And in ios 8 this thing changed. Now a push is seen only after the pop is seen and noticed. which breaks the UX rather badly.
I have noticed this strictness in other areas as well but those are not UI/UX related so i wont go into its detail right now.
As far as your situation go, With my experience I can tell you that you ve been doing stuff in a wrong manner. As apple has gone strict your implementation seems to break.
The only solution in my opinion is to shift every check in viewdidAppear.
If you wish to continue the way you were doing for ios7 earlier you might use this check:
if([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
{
// Code for ios 8 implementation
}
else
{
// Code for ios 7 implementation
}
Though i would reccomend you to avoid because wat u are aiming is perfectly achievable.
Also what you are doing can easily cause inconsistency in the navigation stack which can crash the application.
Related
I'm experiencing something really weird :
Create an extremely basic single view project, and add a second view controller to the storyboard, along with a modal segue from the first to the second. Initiate the segue from the view controller and trigger it programmatically with performSegueWithIdentifier:.
In the viewDidLoad of the modally presented view controller, add this log :
NSLog(#"%#", self.presentingViewController);
Now run the app on iOS 7, you should get a log like this one :
<ViewController: 0x7fa8e9530080>
Which is just the reference of the initial view controller of the app, which presented the modal view controller.
Now run the exact same thing on iOS 8, and you will get :
(null)
What's going on here ? Is it a known issue ? Of course I'd expect the exact same behavior on both systems.
Thanks ... Formalizsed as answer.
viewDidLoad should really be used for initialization, At this stage, there is not guarantee that the receiver's controllers view hierarchy has been placed in the navigation tree. If that is your intent, you should override viewWillAppear or viewDidAppear. Whilst it works in earlier versions, the docs clearly state that it should be used for additional initialization. It certainly sounds as though in iOS 8, the receiver's initialization is being performed earlier
I work on an application, where I have a problem with my view[Will/Did]Disappear methods not being fired when returning to the app.
The case is, I have UINavigationController, which has two view controllers pushed on it. When the user presses the home button, the user is logged out. When he later returns to the app, the following (simplified) code is run in my AppDelegate:
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[(UINavigationController *)self.window.rootViewController popToRootViewControllerAnimated:NO];
[self.window.rootViewController presentModalViewController:loginViewController animated:NO];
}
When I pop off the view controllers on my navigation controller stack, I would expect the view[will|did]disappear methods to be called. However, this is not the case, since they are (apparently) not on the screen anymore when iOS are going to fire these methods. It seems that the modal view controller has taken over.
If I do not present the modal view controller, the view[will|did]disappear methods are called as expected.
My question is: If I want the view[will|did]disappear methods to be called, how can I then structure my code? Is there a better place to present my modal loginViewController?
Edit:
In order to show my problems more clearly, I have created a very simple test project here: https://github.com/JohanVase/ModalViewCauseMissingViewDisappearCalls. Please try a couple of times to follow the instructions in the app, and see that I do not get my "resources" released in my viewWillDisappear method.
I finally asked Apple Technical support the same question. They concluded that this was a bug in iOS, so I have filed a bug report to Apple. The same bug seems to appear in iOS 6 and in the latest iOS 7 (Beta 5).
Apple Technical Support suggested the following:
As a workaround, you can move your cleanup code to a separate method
which the AppDelegate would then invoke on the navigation controller's
top view controller, before it pops the entire navigation stack.
However, I think this exposes too much of my details in the view controller, so I chose to implement it using willMoveToParentViewController: instead. This method is called when the view controller is removed from its parent, and it is called properly.
I am just looking for a sanity check here.
I have a screen that the user passes through on the way into the main application. That screen can be navigated back to from almost anywhere in the system.
As it stands I am just presenting ViewControllers without using a NavController to manage them (it does not seem applicable for most of my app, since screens are not necessarily sequential or related to one another).
My question is, if I have presented VC1, then navigate to other screens, and finally want to present VC1 again, I am doing something like:
[self presentViewController:[self.storyboard instantiateViewControllerWithIdentifier:#"VC1"] animated:YES completion:nil];
Is this bad form? Am I leaking memory by creating a bunch of VC1 instances or is there some magic that uses the previously created one?
If it is bad form, how do I get back to the original VC1 to reuse it?
Thanks for any input.
I think you pegged it: It's not a great idea to have multiple instances of the same view controller in memory at the same time. Every time you instantiate a new view controller and present it modally, you'll consume more memory.
The most elegant solution is the iOS 6 unwind segue. But most of us would be unwilling to give up on iOS 5 support quite yet.
If you need to support iOS 5, you could contemplate using navigation controller, but hide the navigation bar if you don't like it in your user interface. Then replace modal segues with push segues and now you can do popToRootViewController whenever you want to return to the main view controller.
I have an app that uses UIViewControllers for everything pretty much. So far I have been using push segueys when there are either button clicks, or some logic after which I have to show a new screen.
Sometimes it works and sometimes I get strange behavior where the next page loads and gets stuck, and its nav bar area does not load.
I use this code:
BusinessController *businessController = [[UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil] instantiateViewControllerWithIdentifier:#"BusinessController"];
[self presentModalViewController:businessController animated:YES];
I have been reading that I should use the modal seguey possibly, but I am not sure which one is better.
Also, I have been reading that I need to embed my controller in a NavigationConroller if I want to to push, but I am not sure what effects that will have on the rest of my app.
Please help me understand what is the right approach here for me.
Thank you!
For the record....
If you're creating segues you ought to use them, either by linking them to a control so that they activate automatically or by calling performSegueWithIdentifier:sender:. (As a general statement, modal controllers are an interruption of program flow while pushed controllers are like a stack you move though back and forth.)
Simplified question: Is there any way to restart the navigationController of an application?. I'm trying to force the application to get his initial appearance.
Long explanation
I've a pet project in iOS and I have a weird problem with the interface that I'd like to solve. I'd like to understand also the mechanics behind this behavior.
I've a simple welcome view, wich shows the splash screen of the application. After that, thread goes to sleep state for 1.5 seconds.
[NSThread sleepForTimeInterval: 1.5];
Then, I'm showing an advertisement view:
AdController *ad = [[AdController alloc] initWithNibName:nil bundle:nil];
[self.navigationController presentModalViewController: ad animated:YES];
[ad release];
And that's all the logic behind. After that, other controllers are pushed without incidence. I want to achieve that, if at any moment the user makes the application go to background (pushing the iPhone/iPad button) then all the controllers must disappear from the stack via pop. In order to get it I'm using applicationDidBecomeActive event from the delegate. The code is the following:
[self.navigationController dismissModalViewControllerAnimated:NO];
[self.navigationController popToRootViewControllerAnimated:YES];
This is driving to some weird visual behaviours. Depending of the moment that the user choose to push de button the transition to the first view is visible. In other cases the ad view is still present, so it is dismissed and then appears the splash screen.
It will be great if there is some way to reset this first controller (splash screen), in order to get all the transitions working as the first time. I've thought about pop it from the navigation controller and the reload another one, allocating again, but it seems a bit complicated.
Is there any simple way to achieve that?
Important Edit: If the user forces repeatedly the application to go background then these exceptions are thrown:
nested pop animation can result in corrupted navigation bar
Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
The easiest way that I've found is to add to the plist file a new row with key "Application does not run in background" and with value YES.
Forces the application to be completely closed and unloaded from memory when the user pushes the button.