How does AppDelegate become UIApplication's delegate? - ios

I'm just trying to understand the general architecture of UIApplication. My understanding of using a delegate works something like following:
protocol MyDelegate {
func someProtocolMethod()
}
class SomeClass {
var delegate: MyDelegate!
init(){
}
func someClassMethod(){
self.delegate.someProtocolMethod()
}
}
class ClassConformingToDelegate: NSObject, MyDelegate {
let someClass: SomeClass
override init(){
someClass = SomeClass()
super.init()
someClass.delegate = self // self has to be assigned so that SomeClass's delegate property knows what the conforming class is
}
func someProtocolMethod(){}
}
In a similar fashion, AppDelegate conforms to UIApplicationDelegate by having a number of protocol methods implemented.
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
}
UIApplication declares the delegate as following in its class:
unowned(unsafe) var delegate: UIApplicationDelegate?
But, in order for this delegate to know that AppDelegate.swift is the true delegate, UIApplication has to be instantiated and AppDelegate.swift be assigned to the instance, similar to the example above. So something like the following should happen within AppDelegate.swift:
let application = UIApplication()
application.delegate = self
But, how is this step omitted and AppDelegate still works?

The answer to this question varies a little depending on which version of Xcode/Swift/iOS you are talking about, but the essential process is the same.
If you create a project in Xcode that uses the UIKit AppDelegate lifecycle then you will see the line #main at the start of the AppDelegate.swift file.
This tells the compiler that this file contains the UIApplicationDelegate implementation. The compiler then synthesises a main function for you that performs all of the required setup, including creating an instance of the AppDelegate and assigning it to the UIApplication instance.
In earlier versions of Swift you would see #UIApplicationMain that does essentially the same thing.
You can omit the #main/#UIApplicationMain and create your own main that does all of the required work, but this generally isn't required.
With SwiftUI you now have the option of using SwiftUI lifecycle rather than UIKit lifecycle when you create the project. In this case you have an App struct. This file still contains the #main and is used to launch your app's view hierarchy.

Related

Correct way of removing SceneDelegate from iOS 11+ project - What code is necessary in application(_: didFinishLaunchingWithOptions)

When creating a new iOS project in Xcode 12 a UISceneDelegate is added automatically. Since my App should be available on iOS 11+ (which does not support UISceneDelegate) I had to remove it.
Removing the UISceneDelegate from info.plist and from the AppDelegate was no problem of course. However I wonder if I have to add any code to application(_: didFinishLaunchingWithOptions) In most tutorial I found this method is simply left empty, only var window: UIWindow? had to be added. Other sources show that some setup code has to be added.
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
/* Manual Setup */
// let window = UIWindow(frame: UIScreen.main.bounds)
// window.rootViewController = ViewController() // Your initial view controller.
// window.makeKeyAndVisible()
// self.window = window
return true
}
}
In my tests everything works fine without any additional setup code. The rootViewController is loaded automatically from Storyboard and everything works fine.
Is this just coincident, is this some Xcode magic happening in the background (adding rootVC automatically if code is missing), or is my code (without setup) broken and will eventually fail at some very bad moment
You only need to make sure
1- UISceneDelegate is deleted
2- Key UIApplicationSceneManifest is removed from Info.plist
3- These methods are removed from AppDelegate
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
4- Add var window: UIWindow? to AppDelegate
5- Make sure entry point vc is selected in Main.storyboard
After that Don't worry of anything and leave didFinishLaunchingWithOptions empty as if there is no change happened in Xcode 11

Error when modifying UIViewController to UIcollectionViewContorller in ViewController? [Swift]

It seems that I lack the basics of swift, so I am studying from the beginning while watching YouTube.
youtube example : https://www.youtube.com/watch?v=vI7m5RTYNng
At 5:40~7:40 seconds of the video, I get an error in class ViewController: UIViewController where I change UIViewController to UIcollectionViewContorller,
But there is an error. There is an error in the video. I looked at it and followed the video exactly, but in the video the error was resolved and I did not. I think the code is the same, why doesn't the error get resolved?
Error name
[UICollectionViewController loadView] instantiated view controller with identifier "UIViewController-BYZ-38-t0r" from storyboard "Main", but didn't get a UICollectionView.'
I thought it was an error because I didn't create anything on the storyboard, but there was nothing on the storyboard in the video.
ViewController
import UIKit
class ViewController: UICollectionViewController {
override func viewDidLoad() {
super.viewDidLoad()
collectionView.backgroundColor = .yellow
}
}
AppDelegate
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let layout = UICollectionViewFlowLayout()
window?.rootViewController = ViewController(collectionViewLayout: layout)
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
}
I searched hard, but I don't understand well because I lack basic knowledge about swift.
Thank you
It seems like you are using Xcode 11+. The video is kind of outdated now, as it's using Xcode 10 or before. Since Xcode 11, the App project template will create a SceneDelegate.swift for you. You should set the rootVC of the window in the SceneDelegate instead:
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
let layout = UICollectionViewFlowLayout()
window?.rootViewController = ViewController(collectionViewLayout: layout)
}
...
Here, the window variable will be assigned by UIKit the actual UIWindow instance. On the other hand, the window in AppDelegate.swift, which you probably declared yourself (after Xcode 11 they moved the window property from AppDelegate to SceneDelegate), doesn't get this special treatment.

App delegate must implement the window property if it wants to use a main storyboard file

I've already tried all solutions I've been able to find in stackoverflow and none of them work.
This is my current AppDelegate.swift file, I think that I'm implementing the window property, some way or another:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
window = UIWindow()
window?.makeKeyAndVisible()
let mainVC = UIViewController()
window?.rootViewController = mainVC
return true
}
// MARK: UISceneSession Lifecycle
#available(iOS 13.0, *)
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
window = UIWindow()
window?.makeKeyAndVisible()
let mainVC = UIViewController()
window?.rootViewController = mainVC
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
#available(iOS 13.0, *)
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
window = UIWindow()
window?.makeKeyAndVisible()
let mainVC = UIViewController()
window?.rootViewController = mainVC
}
}
What can be happening so the warning still appears and my app keeps on showing a black screen?
I guide you step by step.
1st remove SceneDelegate file from the project.
Add var window: UIWindow? to AppDelegate.
Remove below func from AppDelegate.
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
Remove Application Scene Manifest key from Info.plist file.
Extra
Add below delegate methods in AppDelegate file, below didFinishLaunchingWithOptions
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
I just added var window: UIWindow? into the AppDelegate class, the issue got fixed.
Incase you are developing an Objective C app and face this error, follow same steps as mentioned above with only change in step 1.
Add #property (nonatomic, retain) IBOutlet UIWindow window; to AppDelegate.h
When you create a new project in Xcode 11.4 and above, you create a project directory structure with SceneDelegate files.
if you try to run a project you get an error.
Step 1 : Delete SceneDelegate.h & SceneDelegate.m
Step 2: In AppDelegate.h create window property.
#property (strong, nonatomic) UIWindow *window;
Step 3: In the main Storyboard set the storyboard id for ViewController.
and add a set window in didFinishLaunchingWithOptions method of appdelegate.
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *vc = [sb instantiateViewControllerWithIdentifier:#"ViewController"];
vc.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = vc;
[self.window makeKeyAndVisible];
return YES;
For Swift: Use below code
#UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey:
Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
}
Note: Don't add window inside didFinishLaunchingWithOptions method for swift.
objective-c ios swift

Why do we override variables which were initially nil instead of simply setting them?

For example, in MainViewController which is a subclass of UIViewcontroller, I overrode the nib name variable and returned a string to satisfy the argument however in the appDelegate I was able to simply assign an object to the rootViewController variable without using the term override. I understand the keyword override means to rewrite a method which is inherited by the subclass however I'm not understanding it in this sense where we're using variables.
AppDelegate.swift code:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var mainViewController: MainViewController?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
//so as this function fires we need to put forth some effort.....from inside this function instantiate a view controller
let mainViewController = MainViewController()
//put the view of the ViewController on screen
//mainViewController.show(mainViewController, sender: self)
window?.backgroundColor = UIColor.purple
window?.rootViewController = mainViewController
//set the property to point to the viewController
self.mainViewController = mainViewController
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}
MainViewController.swift code:
import Foundation
import UIKit
class MainViewController: UIViewController {
override var nibName: String? {
return "MainViewController"
}
}
nibName is a read-only property - checking the public UIViewController interface shows:
open var nibName: String? { get }
It doesn't make sense for the nibName to be able to change during the lifecycle of your UIViewController instance, so having it as a read-only property ensures it's simply a form of configuration.

The app delegate must implement the window property if it wants to use a main storyboard file swift

I have just developed an app, but when running in the simulator the debugger console says:
The app delegate must implement the window property if it wants to use
a main storyboard file.
I have an app delegate file. What does the message mean, and how can I get my app working?
Make sure you have the following property declaration in your AppDelegate class:
var window: UIWindow?
If you run your project on earlier than iOS 13.0, in that case you will face the problem. Because of iOS 13 and later, app launch differently than earlier versions.
In iOS 13 and later, use UISceneDelegate objects to respond to life-cycle events in a scene-based app
In iOS 12 and earlier, use the UIApplicationDelegate object to respond to life-cycle events.
When you launch the app in iOS 12 and earlier then UIApplicationMain class expect a window property in your AppDelegate class as like SceneDelegate has. So your problem will be solved if you add the following line in your AppDelegate class.
var window: UIWindow?
For Objective-C
#property (strong, nonatomic) UIWindow *window;
You can find more here App's Life Cycle.
Just in case anyone comes across this again and is programming in Objective-C make sure you have this line of code in your AppDelegate.h file:
#property (strong, nonatomic) UIWindow *window;
I have received this error, when I created new project in XCode 11. I have not used SwiftUI. Here are the steps, I have considered to fix this.
Deleted Application Scene Manifest entry from Info.plist
Deleted SceneDelegate.swift file
Deleted all scene related methods in AppDelegate.swift class
added var window: UIWindow? property in AppDelegate.swift class
After these steps, I am able to run the app on version prior to iOS 13.
[EDIT]
Finally, your AppDelegate.swift file should look something like the following.
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
}
Add the following window declaration in Appdelegate file
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window:UIWindow?
...
Implementation of this property is required if your app’s Info.plist file contains the UIMainStoryboardFile key.
The default value of this synthesized property is nil, which causes the app to create a generic UIWindow object and assign it to the property. If you want to provide a custom window for your app, you must implement the getter method of this property and use it to create and return your custom window.
I had the same issue, just add var window: UIWindow? as the debug error says.
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
You can check your app delegate class:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
// MARK: UISceneSession Lifecycle
#available(iOS 13.0, *)
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
#available(iOS 13.0, *)
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
}
Error: The app delegate must implement the window property if it wants to use a main storyboard file
Swift 5 & Xcode 11
Make sure that SceneDelegate contains UIWindow property
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
//...
}
Setting in Info.plist
Application Scene Manifest > Enable Mutliple Windows > false.
This solved the problem for me.
Long ago answered, but to help understand the questions above about why simply adding the window property solves the problem, note that the app delegate conforms to the UIApplicationDelegate protocol which defines a property, #property (nullable, nonatomic, strong) UIWindow *window; that classes need to provide to specify the window to use when presenting a storyboard. Failure to provide that is causing the Xcode log warnings.
For Swift:
var window: UIWindow?
For Objective-C:
#property (strong, nonatomic) UIWindow *window;
In addition, you might have an Application Scene Manifest entry in your Info.plist file - if you do not use scenes, but only a window and view controllers (eg because you want to test something in the old UI setup), you should remove that entry to be able to see your views.

Resources