In an AppDelegate, how is the main UIWindow instantiated? - ios

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.

Related

UIWindow in SceneDelegate

Recently, I encountered a problem, which I have posted here, in stackoverflow. Apparently, the error was because a random UIWindow variable was used to display UI (and not the one in SceneDelegate).
Here's my understanding - Every time a scene is created (by user or the app), a UISceneSession is created to manage a UIScene, which can contain one or more UIWindows, each of which has views, which are displayed with the help of a view controller. Each UIScene will get a UISceneDelegate object to handle its lifecycle.
The error in my previous post makes sense now - since a random UIWindow object was used instead of the one in SceneDelegate.
But I still don't completely understand why a UIWindow was declared in the SceneDelegate by Xcode. There's a UIWindow object in UIWindowSceneDelegate (super class of my current SceneDelegate) and the documentation clearly explains that its a window associated with the scene.
Why isn't that used and instead a new variable is created by Xcode while creating the project? If I need another window, then can I just add another UIWindow object to the current SceneDelegate? Is that a good practice?
#available (iOS 13.0, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
// Remaining methods
}
I'm new to swift and iOS app development - any help to fill in these blanks would be greatly appreciated.
UIWindowSceneDelegate is not the superclass of your scene delegate. It is a protocol. It isn't an actual thing. It is merely a set of instructions telling the compiler what it would mean to adopt this protocol. Your SceneDelegate class does adopt the UIWindowSceneDelegate protocol. Therefore it must declare a window property of type UIWindow? — and it does so.
Moreover, this property is the scene delegate's window; without it, you would have no window and the screen would be black. You could in fact set this property to a window that you make yourself; but you don't have to, and so you probably don't bother. You don't have to because the framework does it automatically for you as part of the scene-creation process.

Setting root VC programmatically

I'm a Swift beginner, so take it easy on me. I'll be specific in my question.
I deleted the storyboard (I want to learn how to build UI programmatically using Swift).
The below code is placed in the AppDelegate.swift
I also have ViewController.swift in the project explorer.
Firstly, is there a way to use anything other then UINavigationController..? Or having a UINavigationController is a must requirement..?
If not a must, how can I just refer it to a ScrollView for example..?
2ndly, with further research into Apple's own guide, they stated I can also use window?.isHidden = false... Is there a difference between using the former line, and window?.makeKeyAndVisible()..?
Sorry if my question doesn't make sense programmatically, like I said, I'm a beginner, but I'm determined to understand why I write the code I write or copy.
Thank you.
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
window?.rootViewController = UINavigationController (rootViewController: ViewController())
return true
}
...
}
A window needs a view controller. It doesn't have to be a UINavigation Controller, but it does need to be a view controller. Not all apps start with a navigation controller. In fact, you can see this yourself by looking at the storyboard that Xcode provides for you in new projects. It's just a ViewController. Can you use a ScrollView instead? If you put the scrollview (which is a UIView) into a UIViewController, sure you can.
Hiding a window just makes it appear and disappear. Making it the key window means that it will be the window that receives events. You might not think that it is important in iOS apps, but iOS apps can have more than one window. In the case of starting an application, it's a good idea to define the window that will be visible and be key, not just the one that will be visible.
Firstly, is there a way to use anything other then UINavigationController..? Or having a UINavigationController is a must requirement..?
You can use any UIViewController subclass as the root view controller. e.g. UITabBarController, UIPageViewController, UIViewController, UITableViewController...
how can I just refer it to a ScrollView for example..?
You can't set a UIScrollView as the root view controller. You can, however, add the UIScrollView as a subview of the UIWindow. I don't recommend you to do this though, because using VCs will make your code more manageable, with different classes managing different views.
Is there a difference between using the former line, and window?.makeKeyAndVisible()..?
Yes, if you look at the docs of makeKeyAndVisible:
This is a convenience method to show the current window and position it in front of all other windows at the same level or lower. If you only want to show the window, change its
isHidden property to false.
So yeah, calling makeKeyAndVisible will make the window the "key window".
According to here, key window behaves like this:
The key window receives keyboard and other non-touch related events. Only one window at a time may be the key window.

How does Xcode sets rootViewController implicitly via storyboard?

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.

iOS self.window - when is it created?

when you start your app using single view template, and you add the NSLog(#"self.window = %#", self.window); in your first line of the AppDelegate.m's application: didFinishLaunchingWithOptions: method, you can see that self.window exists in your app.
However, when you start your app using empty template, and tried to log the self.window to the console, the result returns null. Even if you add storyboard and a view controller, and set its view controller as the initial view controller, and attempt to log the self.window, the result is the same - its value is set to null.
And note that whichever way you take, you can find you declare #property (strong, nonatomic) UIWindow *window; in AppDelegate.h by default. So I wonder why in the first case, you can see that self.window is initialized and set the value but not in the latter case. Also, if self.window is already declared and initialized in the first case but NOT in the second case, how can I find the initialization code?
It looks like in both cases, the #property declaration is same - and in both cases, as I mentioned, I tried to log the value of self.window in the FIRST LINE of the AppDelegate.m's application: didFinishLaunchingWithOptions: method.
So anything that I'm missing? I don't know why those two cases act differently despite me not finding any differences in both code and storyboard.
I use iOS 7 and Xcode 5. Thanks.
OK, when you create a project with a Storyboard or Nib then the project settings will tell the project that the storyboard/nib is the "Main Interface".
This triggers the application to load that interface on start up. This is why the self.window is created in these cases.
When you create an empty application there is no interface to set as the main interface.
You then need to create the window yourself like this...
-(void)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
UIViewController *someController = [UIViewController... //create your initial controller
[self.window addSubview:navigationController.view];
[self.window makeKeyAndVisible];
}
Something like this anyway. It's been a while.
Alternatively, if you create an empty application and then add a nib file that you want to use as the initial nib then you can select it in the project settings.
In the Target in General. In the section "Deployment Info" select the "Main Interface" from the nibs in your project. This will then load that nib when the application starts.
Xcode declares UIWindow as IBOutlet Object in Appdelegate and xcode itself hooks or connect it with the window of default ViewController.nib(Created by Xcode when you create non empty project).There is no need to initialise any object if you have declared it as Iboutlet and connected it with any UIController in nib.
Now in empty project if you want to create window declare it as outlet and connect it with window in exist in nib and make your AppDelegate as Files OWner.

How to pass a managedObjectContext from the appDelegate to the first ViewController when their is a navigation controller between the two views

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...];

Resources