I have a UITabBarController based application. I want to launch a configuration guide - a series of views - the first time the application is launched. That of course have nothing to do with with the normal tab navigation and I want the configuration views to cover the entire screen.
I have a class that supports the UIApplicationDelegate protocol and I tried to launch my configuration view from the application:didFinishLaunchingWithOptions method with the following code:
UIViewController *vc = [[self.mainViewController storyboard] instantiateViewControllerWithIdentifier:#"StartupWelcomeViewController"];
[self.mainViewController presentModalViewController:vc animated:YES];
(the mainViewController is a reference to the UITabBarController)
Apparently application:didFinishLaunchingWithOptions is called before the viewDidLoad for the tab bar views. If I move my code above to a function that is called after the viewDidLoad it works.
I cannot find a method in the UIApplicationDelegate protocol or the UITabBarController class that is called after the viewDidLoad methods in the tab bar views.
Where is a good place to launch my configuration guide and how do I do it?
(Old question, but for the sake of archives...)
If you want your startup wizard to appear on top of the tab view controller, then the tab view controller should present it; you would do that from somewhere like viewDidLoad. If you don't like having that code in the tab bar controller then put it in e.g. the application delegate and just have the tab bar controller call it.
An arguably cleaner alternative is to have no automatically appearing view in your app, instead orchestrating everything from the application delegate - it checks to see if the configuration wizard has been run and chooses to show either that wizard, or the tab bar controller & thus the related UI. In either case, the instantiation code would retrieve the relevant named object from the storyboard in the manner shown in your question. The startup wizard would call back to the application delegate when it was finished using a very simple delegate protocol you'd design yourself, which would give you the cue needed to show the tab bar presumably by calling the same show-tabs method that'd be invoked whether the tab bar was shown immediately, or shown after configuration completed.
This second approach does mean your storyboard is doing less of the work and your code is doing more. In my experience so far, this seems to happen with a certain inevitability as an application matures and its functionality starts to extend beyond the relatively basic flow options provided by automated storyboard behaviour.
Footnote
You prevent a storyboard from showing any view at startup by turning off the Is Initial View Controller option in XCode's storyboard editor's attributes inspector (Command-Option-4) shown when you select whichever controller is currently used for this purpose. It'll be on the leftmost side of the storyboard editor area and have an arrow pointing to it that "fades in from nowhere" left-to-right. Once you do this you'll get a build warning, which is rather annoying; you may decide to add a dummy, blank view controller and set this as an initial view purely to avoid the warning.
Related
I have a new iMessage Extension project where I tried 2 ways of structuring the navigation stack:
In my storyboard I set the entry point to a UINavigationController that has my MSMessagesAppViewController as the root controller.
Or I set my MSMessagesAppViewController directly as the Entry point in my storyboard. (No UINavigationController that owns it).
For scenario #1 above, the navigation controller works fine, and I can push new screens on the stack. (with the exception of the whole nav bar being hidden in Expanded view, which is a separate issue that I still have to figure out). However, NONE of the delegate methods of my MSMessagesAppViewController get called with this configuration. Such as:
willTransitionToPresentationStyle
didTransitionToPresentationStyle,
willBecomeActiveWithConversation,
didSelectMessage
(none of these get called)
For scenario #2 above, the MSMessagesAppViewController methods DO get called. (because the UINavigationController is not the entry point in the Storyboard).
So my question is: How can I have a UINavigationController be at the root of my iMessage Extension application, so I can perform Push navigation, but at the same time have the methods of MSMessagesAppViewController get called, as described by the Apple API?
Although it doesn't seem to be documented, it looks like message extensions expect the entry point to be a subclass of MSMessagesAppViewController. Those methods aren't delegate methods, they're superclass overrides, so there's no way to arrange for them to go anywhere else. The message extension system could handle the case you describe but-- aparently-- does not.
What I'd try is:
Make the entry point a subclass of MSMessagesAppViewController.
Early in that object's life cycle (maybe in viewDidLoad) create a UINavigationController and add it as a child view controller of your MSMessagesAppViewController subclass. Make it fill the entire screen.
Now-- in effect-- your navigation controller is the root of the extension. It's not really the root since message events like willTransitionToPresentationStyle will still go through the MSMessagesAppViewController subclass. But everything else starts there. It's the root of the UI and the navigation.
In the meantime it might be good to file an enhancement request with Apple. It's reasonable to think that the message extension system would check the root VC of a navigation controller to see if it's the right class, and maybe they'll add that in the future.
I would like to create a "deep link" into my storyboard while preserving the backstack (back button navigation).
Ex:
Given the storyboard below (entry point being the leftmost Navigation controller)
When my application is opened via a remote notification I would like to open the second tab in by tab controller AND be able to navigate back to the item list via the back button.
Please note that I am not asking about how to open the second tab, or how to create such a storyboard but specifically if there is a way to do this with storyboards or will I have to do it by code.
Thanks!
PS: I come from an Android background where one recreates the parent view controller manually or (better) inserts it into the backstack. As far as my research goes there is no such thing in ios. I'm hoping I'm wrong.
Your UINavigationController has a viewControllers property. You can create as many view controllers as you want in an NSArray and assign it to this property and that will be the back stack with the last VC in the array displayed.
The problem is that when a notification arrives, your app could be in any state at all. It could be running, with some other screen showing. It could be suspended, with some other screen showing. Or it might not be running at all, and will now have to be launched from scratch.
Thus, starting in the App Delegate routine that responds here, you will have to deal manually (in code) with the situation if you want to put your app into an appropriate state.
My app has a simple organization, which I've configured in an Interface Builder storyboard (not in code). There is a Navigation View Controller, which has its Root View Controller set to my Main View Controller. My Main View contains a table, where cells segue to a Detail View Controller.
When I suspend the application while looking at the Detail View and then resume it, I'm returned to the Main View, rather than the Detail view. Why might this be?
Details:
I have set Restoration IDs in Interface Builder for the Navigation View Controller, the Main View Controller and the Detail View Controller. I've also tried adding a Restoration ID to the Table View and making the Main View Controller implement UIDataSourceModelAssociation.
My app is returning YES from shouldRestoreApplicationState and both the Main View and the Detail View have encode/decodeRestorableStateWithCoder methods.
I'm testing suspend/resume using the simulator: I run the app, navigate to the Detail View, hit the home button, and then click the stop button in XCode. To resume, I'm running the app again from XCode.
I see the following calls on suspend:
AppDelegate shouldSaveApplicationState
MainViewController encodeRestorableStateWithCoder
DetailViewController encodeRestorableStateWithCoder
And on resume:
AppDelegate shouldRestoreApplicationState
AppDelegate viewControllerWithRestorationIdentifierPath Navigation
AppDelegate viewControllerWithRestorationIdentifierPath Navigation/MainView
MainViewController viewDidLoad
AppDelegate viewControllerWithRestorationIdentifierPath Navigation/DetailView
MainViewController decodeRestorableStateWithCoder
In addition to the wrong view being restored, there's something else odd: Why is the Restoration Identifier Path for the Detail View "Navigation/DetailView" and not "Navigation/MainView/DetailView"? There is no direct relationship between the Navigation View Controller and the Detail View Controller. Their only connection in Interface Builder is via the segue from the Main View.
Have I misconfigured something?
I have tried assigning a Restoration Class to the Detail View. When that restoration code is invoked, it fails because the UIStateRestorationViewControllerStoryboardKey is not set in the coder.
Here's a toy version of my project which replicates the problem: https://github.com/WanderingStar/RestorationTest
I'm trying this with XCode Version 5.0 (5A1413) and iOS Simulator Version 7.0 (463.9.4), in case those are relevant.
The answer turned out to be simple: I was not calling
[super encodeRestorableStateWithCoder:coder];
in the encodeRestorableStateWithCoder:coder method in my View Controllers (and doing the same in decode...) which is what sets the storyboard in the coder.
This tutorial helped me step through each step of the process, and find out where I'd gone wrong:
http://useyourloaf.com/blog/2013/05/21/state-preservation-and-restoration.html
Also, it turns out that "Navigation/DetailView" is what's expected. The Navigation View Controller restores all of the views in its stack and then puts them back into the stack, rather than each view restoring the later views in the stack.
In the iOS App Programming Guide, section "State Preservation and Restoration" there is a convenient checklist for what you have to do to make restoration work.
After looking at your code it seems that you forgot step 3, Assign Restoration Classes. Your classes do not have these properties, and you did not implement viewControllerWithRestorationIdentifierPath in the app delegate.
Assign restoration classes to the appropriate view controllers. (If you do not do this, your app delegate is asked to provide the corresponding view controller at restore time.) See “Restoring Your View Controllers at Launch Time.”
I took a look at your sample and the applicationWillFinishLaunching is missing [self.window makeKeyAndVisible] which is a requirement for state restoration. This will make the split controller immediately collapse and then it will be restored correctly.
There is an issue that if it was preserved in landscape, i.e. separated split view , and then launched in portrait then the path will not be correct. In this case at launch it will first collapse to match the current screen, then it begin restore and first separate, then after restore has finished it will collapse again to match the current screen. During this time you need to implement viewControllerWithRestorationIdentifierPath and use the last string in the path to identify the controller and return it after having captured it from what the storyboard created initially in will finish launching. Then you can clear those properties in didFinish.
I changed navigation in my application from using UITabBarController to u UINavigationController. I.e. former solution (1st version) was based only on the TabBarController - 4 ViewControllers (one simple TableView, one simple custom view and one MapView with many overlays). The second version is based only on the UINavigationController.
In case of TabBarController it was clear and simple, everything worked fine, especially MapView. I mean: the MapView was loaded once (with a significant number of overlays) and when I went to another view and back to the MapView the MapView was still there with its overlays already loaded and displayed (simple check: MapView`s viewDidLoad was called just once per app run, I had some debug messages there).
Now I changed navigation logic to the UINavigationController. Everything works fine for the first look - but: the viewDidLoad (for each view) is called everytime I navigate to the view. It is annoying especially in the case of the MapView - the loading of overlays is performed everytime, it takes some time and it causes app crash in some cases.
OK, my questions:
Is it some kind of "common" behavior of NavigationController?
Can I change this behavior so viewDidLoad will be called just once?
And more - How can I influence the "display sequence" of some view?
I understand the logic is probably more complicated but I appreciate any answer or hint ;)
Some related circumstances:
TabBar and Navigation controllers are not combined.
I use storyboards, segues are designed in the UIB, no manual calling like perfomSegue or prepareForSegue in my code. One button triggers segue to MapView.
I use push segues.
I also tried to use modal segues but without any change of that behavior.
any of viewDidUnload is never called during segues among the views.
No memory warning received.
No memory leaks measured both on simulator and iPhone 4.
I tried to build a very simple temporary project / app that is concerned just about the Nav. Controller and other views without ANY coding, just storyboard. It was the same behavior.
There was an issue that causes app crash when I fast and periodically tapped to navigation button and back button between one view and the MapView. In most cases the app crashed when I tapped the back button on the MapView before it was fully displayed (i.e. its overlays). It was fixed when I added a 1 sec. delay method call in the viewDidDisappeared in the MapView. It is not a fair fix, I know ;)
A UITabBarController and UINavigationController are based on fundamentally different paradigms.
The UITabBarController is intended for the UIViewController on each tab to exist independently of each other and for the user to choose which they want to view. viewDidLoad only gets called once for each UIViewController because it is intended that each tab still exists in memory even as the user switches to a different tab.
The UINavigationController is a stack of UIViewControllers where each is related to the one above and beneath itself. The top UIViewController in the stack is always the one that is visible to the user. When a UIViewController is pushed to the stack, its viewDidLoad gets called because it is being loaded into memory. When the top UIViewControllergets poped off the stack, it is unloaded from memory, and viewDidUnload gets called on the way out (viewDidUnload is deprecated in iOS6 and won't get called, but the controller will still get dumped from memory). This is why viewDidLoad gets called every time that the user pushes a particular UIViewController onto the UINavigationController stack.
What is the difference beetween calling presentModalViewController and pushViewController, when :
animation is set to NO (even if yes, that's just an animation style that can be changed).
a navigation controller is defined when presenting the modal view, so it can be navigable too, with a call stack, ....
Is this just to be able to go back from the first pushed view ? Woooaaaaaa.....
I guess the difference is elsewhere and deeper. No ?
Ignoring transitions/animations and how things are structured behind the scenes (which aleph_null's alswer provides a good discussion of), the only user-facing difference is the ability to return to the preceding view automatically using the navigation bar.
If you use pushViewController you will automatically get a "Back" button in the navigation bar. If you use presentModalViewController you do not, and generally will have to implement your own controls and/or callbacks to handle dismissing the controller.
Conceptually the modal presentation style is generally used for atomic tasks that you cannot navigate away from (i.e. you either complete the task, or you cancel, and you cannot do anything else within the app until you do one or the other).
If you're wondering why have the difference in the first place, I can't say. Personally I think frameworks that provide a unified API for moving from one controller to another (like cocos2d, or Android) make a lot more sense.
The most important difference is about semantics. Modal view controllers typically indicate that the user has to provide some information or do something. This link explains it more in depth: http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/ModalViewControllers/ModalViewControllers.html
Here's another, less abstract difference they talk about:
"When you present a modal view controller, the system creates a parent-child relationship between the view controller that did the presenting and the view controller that was presented. Specifically, the view controller that did the presenting updates its modalViewController property to point to its presented (child) view controller. Similarly, the presented view controller updates its parentViewController property to point back to the view controller that presented it."
Also see this thread: why "present modal view controller"?
Take a look into the viewControllers in the image
The top 2 viewControllers(login & submit) at the top left are disconnected from the tabBarController & NavigationController
The rest of the viewControllers are embedded in a NavigationController. They somehow belong to the natural flow of the app.
Now you have to ask yourself
Do I need to always show login + submit page every time? It would be pain in the neck for the user to each time go to login even if they logged in last time. These 2 screen really don't fit the natural flow of the screens. So what do we do? We just add them modally using presentViewController
However for the rest of the viewControllers we want to keep them inside 2 navigation so we can easily go back and forth so we use pushViewController
For more information I recommend you to see this video
The image was also picked from this great answer. It's worthy of a look.
This is what my experience says,if you want to manage a hierarchy of views,better go for pushViewController in the navigation controller. It works like a stack of view-controllers in the navigation controller. If however the requirement is just to show a view on executing some actions on the parent view controller then the best way is presenting it modally.
If you need a complex push pop logic always prefer a pushViewController.
UINavigationController are used when you want to have some sort of hierarchal representation of your data (ie drill down). They work using a stack of UIViewController subclasses. Every time you “drill down”, you simply add another view controller to the stack. Then, the “back” logic is simply a matter of popping view controllers off of a stack.
You can check out this link:
http://www.icodeblog.com/2011/10/11/back-to-basics-an-introduction-to-view-controllers/