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.
Related
In iOS 13, the UIWindowSceneDelegate object is not in the responder chain (I verified this by printing the responder chain). But the template code Xcode provides make the scene delegate class inherits from UIResponder. If I make the scene delegate class inherits from NSObject, the code still compiles and runs without problem.
So what's the point of making scene delegate class conforms to UIResponder?
I noticed this too while having trouble with NSToolbarItem actions and after investigating with Hopper I figured out the reason is although UIWindow's nextResponder is UIWindowScene, it does not have a nextResponder override that forwards to its delegate (our SceneDelegate class that conforms to UIWindowSceneDelegate), it's superclass - UIScene's next is the UIApplication which has next the app delegate.
My guess is requiring a scene delegate conforming class to have as next responder the application syntactically is tricky, and even if it is achieved in ObjC it is likely even harder or impossible in Swift. Maybe Apple just decided it wasn't worth the hassle. With Catalyst, any NSToolbarItem for the window scene's toolbar can just have their target set to self - the scene delegate, rather than search the responder chain, even the system items can have their target changed during toolbarWillAddItem like in the sample. It would have been nice if they had at least documented somewhere a warning that the window scene delegate isn't in the responder chain, especially because as you say it is a subclass of UIResponder.
If you would like it to be in the chain then I created a workaround (see code below). First create a subclass of UIWindowScene with a nextResponder method returning self.delegate. Second, in the scene delegate add a nextResponder that returns UIApplication.sharedApplication (which will forward to the app delegate). Finally in the Scene Manifest (in Info.plist) add a row under the default configuration and choose Class Name from the drop down and enter your subclass's name.
I suppose this could be useful for an action that needs access to the window, because once an action reaches the app delegate it is harder to figure out which scene window it came from. However as I said, in this case what is the point in searching the chain at all.
MyWindowScene.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#interface MyWindowScene : UIWindowScene
#end
NS_ASSUME_NONNULL_END
MyWindowScene.m
#import "MyWindowScene.h"
#implementation MyWindowScene
- (UIResponder *)nextResponder{
return self.delegate;
}
#end
SceneDelegate.m
#implementation SceneDelegate
...
- (UIResponder *)nextResponder{
return UIApplication.sharedApplication;
}
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.
There is already a question like this that was answered, however, I was not satisfied with the answer: Why does AppDelegate inherit from UIResponder?.
I'd like more specifics, as I am trying to understand the architecture of an iOS app.
As far as I know, UIResponder is for things that "respond" to external forces (i.e. touch events, motion events, etc.).
How come the initialization of an application needs to be a UIResponder? Maybe I'm not understanding what the instance of AppDelegate does.
The application delegate class inherits from UIResponder.
This is so that the delegate instance can participate in the responder chain and so handle application-level actions.
Edit:
As of iOS 5 there is a step 6: The app delegate gets the final word on
events. Beginning with iOS 5 the app delegate inherits from
UIResponder and no longer from NSObject as it did before.
In short: first the view, if the view has a view controller then that,
next the superview until the top of the hierarchy, the window. From
there to the application and finally to the app delegate.
The addition of the app delegate as potential responder is a welcome
addition since we rarely – if ever – subclass UIApplication or
UIWindow. But we always have our own app delegate if only to create
the window and add the rootViewController in the
application:didFinishLaunching… delegate method. So this happens to be
the de facto best place for a responder to fall back to if there is
none in the entire view hierarchy.
Taken from:
https://www.cocoanetics.com/2012/09/the-amazing-responder-chain/
I have 2 UIWindows, when I show both UIWindows the latest shown UIWindow always is above the 1st shown UIWindow. Each UIWindow is an own UIWindow, so no Subview.
Does anyone know how i can place a UIWindow behind another UIWindow?
While I suspect there's probably a cleaner, more maintainable way to achieve what you want, you should be able to manipulate the order windows appear using the windowLevel property - although to be honest the last time I tried to do this was on iOS 5 so maybe that's changed. But generally you'd do something like:
windowToPlaceInFront.windowLevel = windowToPutInBack.windowLevel + 1.0f;
You cannot have multiple UIWindow inside an app.
From Apple documentation (https://developer.apple.com/library/ios/documentation/uikit/reference/UIWindow_Class/UIWindowClassReference/UIWindowClassReference.html):
Unless an app can display content on an external device screen, an app has only one window.
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.