Xcode with the project file and Info.plist locates the main.storyboard and finds out the initial ViewController. But I was hoping to see some boiler plate code in AppDelegate for the ViewController.
AppDelegate looks blank: (No reference to ViewController)
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
return YES;
}
...
#end
StoryBoard is xml:
Question
Does Xcode what magic code to refer storyboard xml and find the controller ?
After the compilation, main() brings up AppDelegate which should either have reference to ViewController directly or have a proxy storyboard object to get to the ViewController.
What am I missing ?
I think my book (which you already found) explains it fully. main calls UIApplicationMain() and it follows certain rules. If you have a main storyboard designated in your Info.plist — and you do — then:
UIApplication pulls the initial view controller instance (the one with the Entry Point arrow) out of the storyboard. That's the ViewController instance.
It also instantiates the UIApplication class.
It also instantiates the app delegate class (specified in the UIApplicationMain call), and assigns it to the UIApplication's delegate property.
It makes a window and assigns it to the app delegate's window property, then assigns that view controller to the window's rootViewController property.
Then it shows the window (and calls applicationDidFinishLaunching... on the app delegate`).
That completely explains the launch process.
A picture is worth a thousand words.. However, here's a short description: "It's in your project's settings/info.plist".
In the picture below "Main" is the name of my initial storyboard. Also, initial view controllers have a flag in the interface builder that says "Is Initial View Controller" to tell it that's the view controller you wish to run first.
P.S. Clicking on the images enlarges them :)
The short answer is that the dynamic nature of Objective-C allows UIApplicationMain() to bootstrap your application solely from the textual information stored in your application's info.plist file and your main NIB or main Storyboard. There's no need for the compiler to generate boilerplate code.
The longer answer:
UIApplicationMain does the following things to through the Objective-C runtime:
In all cases, UIApplicationMain() instantiates the NSPrincipalClass key in your plist, typically UIApplication but can be a custom subclass.
In an old-style NIB-less application, UIApplicationMain() will instantiate the AppDelegate class that was specified in main.m. It will wire the AppDelegate to UIApplication, and then hand off to the AppDelegate to create the app window, root view controller, etc.
In a NIB-based application, UIApplicationMain does not know the name of the AppDelegate. Instead, it loads your main NIB file NSMainNibFile, as specified in the plist. The NIB is responsible for instantiating your AppDelegate and wiring to UIApplication, along with creating your main window and root view controller.
In a Storyboard application, UIApplicationMain instantiates the NSPrincipalClass and the app delegate as specified in main.m, just like a NIB-less application. It then loads the main storyboard, which knows who the initial view controller is. It creates a window, instantiates the initial view controller, and assigns it as the root view controller for the window.
That's the reason behind it :-
and if you remove that mark You'll get a blank screen.
Related
At the launching phrase of an App, which controller would it use as the rootviewcontroller for it's window (while they both exist)? the one declared in AppDelegate or the one used in Storyboard?
If nothing is declared in AppDelegate, would the App create a UIWindow for it's .window property by default and make the entry viewController in the designated storyboard it's rootViewController?
If nothing added in applicationDidLuanch: delegate Method for creating a new window, so your app create your UIWindow and make the root is the root in your storyboard file, but if you want to create custom starting point, add another storyboard or even a single .xib file as your staring point and let your appDelegate know your starting point.
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.
A snippet of the default code in a Master-Detail Xcode project
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController; // *** here ***
MasterViewController *controller = (MasterViewController *)navigationController.topViewController;
controller.managedObjectContext = self.managedObjectContext;
return YES;
}
AppDelegate.h
#property (strong, nonatomic) UIWindow *window;
I am aware that #synthesize just sets the accessor methods, and no initialization happens automagically. But how does window have a non-nil rootViewController if it is never explicitly initialized? Is this just Xcode init'ing behind the scenes?
From my book:
If you choose the Storyboard option as you specify a template, the process works a little differently. The app is given a main storyboard, pointed to by the Info.plist key “Main storyboard file base name” (UIMainStoryboardFile). After UIApplicationMain instantiates the app delegate class, it asks the app delegate for the value of its window property; if that value is nil, the window is created and assigned to the app delegate’s window property. The storyboard’s initial view controller is then instantiated and assigned to the window’s rootViewController property, with the result that its view is placed in the window as its root view; the window is then sent the makeKeyAndVisible message. All of that is done behind the scenes by UIApplicationMain, with no visible code whatever. That is why, in a storyboard template, the application:didFinishLaunchingWithOptions: implementation is empty.
From the UIWindow documentation:
Note: When you use storyboards and the Xcode app templates to create an app, a window is created for you.
If you don't use storyboards, the window is explicitly created, though all the standard project templates do this out of the box. You'll see a line similar to this in the app delegate:
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
Using storyboards, the window is created behind the scenes when the main storyboard is loaded (see the View Controller Programming Guide for more info).
From Apple's docs (in "Using View Controllers in Your App"):
The Main Storyboard Initializes Your App’s User Interface
The main storyboard is defined in the app’s Information property list file. If a main storyboard is declared in this file, then when your app launches, iOS performs the following steps:
It instantiates a window for you.
It loads the main storyboard and instantiates its initial view controller.
It assigns the new view controller to the window’s rootViewController property and then makes the window visible on the screen.
The above answers only who sets the window variable without answering the main questions: "But how does window have a non-nil rootViewController if it is never explicitly initialized? Is this just Xcode init'ing behind the scenes?" and seem to suggest that there is magic afoot. Not a satisfactory answer for me, and so with a little digging, all becomes clear.
The generated code defines AppDelegate as
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
...
}
When you search the project, there is no other reference to window, so apparently it should remain nil, but actually is set to the correct value (by the methods outlined above). The "magic" is that AppDelegate conforms to the UIApplicationDelegate which includes an declaration:
optional public var window: UIWindow? { get set }
Part of conforming to the UIApplicationDelegate is the redeclaration of the public variable window. When the underlying Application references the variable window in the protocol, it is actually linked to the variable window in our class. When the calling Application updates that variable window in the protocol, it is actually updating our variable window. So when we need to access the value in our program it is ready and waiting.
This is not Xcode magic, but an integral part of the Swift language. When using protocols we can employ the same techniques in our own Swift programs. This is just the same as our implementations of various functions in our classes which we do all the time: e.g. UIApplicationDelegate defines
optional public func applicationDidEnterBackground(_ application: UIApplication)
so we can write our own implementation which is then "magically" called!
For completeness, note the #UIApplicationMain tag on the class. This defines the entry point for the application and is what makes everything work together. The actual class name is irrelevant, and can be given any name you require, as long as it is of type UIResponder and conforms to the UIApplicationDelegate.
In your Storyboard, there is a little arrow you can drag around:
If you were using xibs/nibs instead, the 'Main Interface' field would be filled out.
In the end, yep, it's iOS/Xcode magic.
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.
I've been beating my head against the keyboard for a better of 3 days now in researching and trying to figure out how i can solve the following problem.
I have a story board that looks like this:
Initial app launch arrow -> to a Navigation Controller -> to the Main View Controller.
My appDelegate is creating the managedObjectContext and it populates some entities with data (for testing purpose only atm, it will be removed once I'm ready to integrate with an outside source). This work fine. Now my problem is that I don't know how i can pass the MOC from the appDelegate to my first ViewController because the Navigation controller is in the way. My current code in the appDidFinish Method looks like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Pass the managed object context to the root view controller
MainMenuViewController *rootView = (MainMenuViewController *)self.window.rootViewController;
rootView.managedObjectContext = self.managedObjectContext;
//My actual Core data setup and adding data to it, it works I've tested it.
return YES;
}
Now my code works when I change where the app launch arrow in the storyboard to point to my mainMenuViewController, but than I do however lose my navigation bar in all my views. I also know how to pass the MOC from my mainMenu to another view via the - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)senderbut I just can't seem to figure out how to do the initial pass from the appDelegate to the MainViewController because of the darn navigation controller being in-between the two.
I've already searched numerous threads on this site (and others) and i've only found the solution for a "Tabbed application" and others want me to serialize the object, but for my purposes I can't do that. (only a few views will receive the MOC and other will be passes data that one view has created and altered to be tailored for specific purposes in the unique views)
Any help to this nub in iOS and Objective-C is greatly appreciated. Thank you in advance.
EDIT: THe error i get is "terminating app due to uncaught exception ... [UINavigationController setManagedObjectContext] unrecognized selector sent to instance...
If you create a new application from the "Master-Detail" app template in Xcode 4.3 (and 4.2 as well, I think), with the "Use Storyboard" and "Use Core Data" options checked, you'll find the following in AppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
MasterViewController *controller = (MasterViewController *)navigationController.topViewController;
controller.managedObjectContext = self.managedObjectContext;
return YES;
}
This seems to be exactly what you're looking for. The key bit is that you can query a navigation controller for its view controllers.
If the NSManagedObjectContext was setup in AppDelegate, you don't pass it; rather, you create a reference to it:
AppDelegate *appDel = [UIApplication sharedApplication].delegate;
NSManagedObjectContext *context = appDel.managedObjectContext;
The type of object you reference it from is irrelevant, as is any other kind of object between the receiving object and AppDelegate.
In addition to my first answer, which is essentially the same as the accepted answer—except that, with mine, you can use it with or without any template (and not just the split view template)—I'm providing an answer that even more directly answers your question, and that is how to pass a reference to your context manager object to the destination view controller subsequent to a segue.
This answer, like my first one (and unlike the accepted answer), assumes that you at least know where the managed object context is in your app, and that you have the basic skills necessary to create a reference to it (unlike the accepted answer, which assumes you can't use Find... in Xcode nor can you remember how to assign a value to a pointer, such as:
id moc = [reference to the managed object context object that you can find]
Since you seem to respond better to answers that involve a template, try the prepareForSegue method override provided in the UIViewController subclass in the Single Application View template. Inside, you will note a couple of comments left there by Apple.
In short, you will create a reference to the destination view controller like this:
SecondVC *vc2 = segue.destinationController;
Then, you set the value of the pointer reference to the managed object context variable in the destination view controller to the value of the point of reference to it wherever the instance was created:
vc2.mbo = AppDelegate.mbo;
I provided a really thorough example (with a video demonstration) of creating segues without storyboards (i.e., programmatically) at:
Set segue identifier programmatically
By the way, if you're having difficulty with understanding the use of the AppDelegate reference in my answer, I'll explain it:
Apple puts its Core Data managed object context reference in AppDelegate.h/.m whenever you create a new project using any Xcode template, and also check the Use Code Data checkbox during template setup. To use that reference throughout the app (i.e., within other .m files, and so you don't in advertently create multiple instances of it), you create global, application-wide reference to AppDelegate—which happens to be a delegate of UIApplication, which is a singleton, making AppDelegate a singleton, too—by merely adding this to every implementation file in which you intend to reference the managed object context:
import "AppDelegate.h"
define AppDelegate ((AppDelegate *)[[UIApplication sharedApplication] delegate])
Now, you access the managed object context object anywhere these two lines are added:
[AppDelegate.mbo...];