App delegate methods aren't being called in iOS 13
Asked Answered
P

3

80

I am using Xcode 11 and building an app for iOS 13. In a new project I created in Xcode some of the UIApplicationDelegate methods were missing so I added them back into the app delegate file. The new template for a "Single View App" project was missing the methods. The problem is that none of the delegate methods are getting called except -application:didFinishLaunchingWithOptions:. Here is my app delegate:

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSLog(@"application:didFinishLaunchingWithOptions:");
    return YES;
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
    NSLog(@"applicationDidEnterBackground:");
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
    NSLog(@"applicationWillEnterForeground:");
}
#pragma mark - UISceneSession lifecycle

- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {
    return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
}

- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions {
}

@end
Prognosticate answered 8/6, 2019 at 17:42 Comment(0)
P
203

iOS 13 has a new way of sending app lifecycle events. Instead of coming through the UIApplicationDelegate they come through the UIWindowSceneDelegate which is a UISceneDelegate sub-protocol. UISceneDelegate has the important delegate methods.

This change is to support multiple windows in iOS 13. There's more information in WWDC 2019 session 212 "Introducing Multiple Windows on iPad". The technical information starts at around 14:30 and is presented by a man with very sparkly high-tops. The shorter session 258 Architecting Your App for Multiple Windows also has a great introduction to what's changed.

Here's how it works: If you have an "Application Scene Manifest" in your Info.plist and your app delegate has a configurationForConnectingSceneSession method, the UIApplication won't send background and foreground lifecycle messages to your app delegate. That means the code in these methods won't run:

  • applicationDidBecomeActive
  • applicationWillResignActive
  • applicationDidEnterBackground
  • applicationWillEnterForeground

The app delegate will still receive the willFinishLaunchingWithOptions: and didFinishLaunchingWithOptions: method calls so any code in those methods will work as before.

If you want the old behaviour back you need to

  1. Delete the "Application Scene Manifest" entry from the app's Info.plist
  2. Comment or delete the application:configurationForConnectingSceneSession:options: method (or the Swift application(_:configurationForConnecting:options:)function)
  3. Add the window property back to your app delegate (@property (strong, nonatomic) UIWindow *window;)

Alternatively, open the SceneDelegate file that Xcode made and use the new lifecycle methods in there:

- (void)sceneDidBecomeActive:(UIScene *)scene {
}
- (void)sceneWillResignActive:(UIScene *)scene {
}
... etc

It's possible to use the new UIScene lifecycle stuff without adopting multiple window support by setting "Enable Multiple Windows" ("UIApplicationSupportsMultipleScenes") to "NO" in the Info.plist (this is the default for new projects). This way you can start adopting the new API in smaller steps.

You can see that the scene delegate method names are a close match for the app delegate ones. One confusing thing is that the app delegate methods aren't deprecated so you won't get a warning if you have both app delegate and scene delegate methods in place but only one will be called.

Other things that UISceneDelegate takes over are user activities (continueUserActivity: etc), state restoration (stateRestorationActivityForScene: etc), status bar questions and opening URLs. (I'm not sure if these replace the app delegate methods). It also has analogous notifications for the lifecycle events (like UISceneWillDeactivateNotification).

From the WWDC Session, some images for you:

The function equivalents for Swift:

enter image description here

The class responsibilities:

enter image description here

Prognosticate answered 8/6, 2019 at 17:42 Comment(10)
does that mean I have to add a SceneDelegate.h file to my project? I'm having the same issue but still can't figure out how to make it work.Manichaeism
@Manichaeism this advice is really for new projects created with Xcode 11. If your project was created with an older Xcode it should still workPrognosticate
Thanks but it doesn't at all. The funny part is, it works perfectly when I'm not debugging my app.Manichaeism
@Manichaeism That's strange. Open a question here on SO and give more details. It sounds like buggy behaviour.Prognosticate
Yeah, it sure is, but to be honest Im just creating a new project and adding all my source code files to it. I've been trying to fix it for way too long now and even explained the situation in Apple forums and no one knows what is going on. Typical XCode buggy behavior.Manichaeism
The old projects crash randomly for app store builds with storyboard related issues.Rebellious
Also "Architecting Your App for Multiple Windows" (developer.apple.com/videos/play/wwdc2019/258)Ozonolysis
I can confirm that for my older project that does not use multiple windows AppDelegate behavior appears to have changed. In particular stopping and starting music through AppDelegate appears to no longer work properly.Leishaleishmania
I don't have a Application Scene Manifest entry in my .plist and I have the same problem as @Miguel. I created a new project and passed all my files from the other project there too... But I can't remember if there was a SceneDelegate and that I deleted it... Is there any project setting that I could change to force old behavior?Kesterson
well actually my methods applicationWillResignActive and applicationDidBecomeActive do work.. just func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool that is not getting called on a universal link invocation... the app opens and no breakpoint thereKesterson
Z
7

This thread helped me:

View controller responds to app delegate notifications in iOS 12 but not in iOS 13

Objective C:

if (@available(iOS 13.0, *)) {
    [[NSNotificationCenter defaultCenter] addObserver:self 
          selector:@selector(appWillResignActive:) 
          name:UISceneWillDeactivateNotification object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self 
          selector:@selector(appDidBecomeActive:) 
          name:UISceneDidActivateNotification object:nil];

}
else {
    [[NSNotificationCenter defaultCenter] addObserver:self 
          selector:@selector(appWillResignActive:) 
          name:UIApplicationWillResignActiveNotification object:nil];


    [[NSNotificationCenter defaultCenter]addObserver:self
          selector:@selector(appDidBecomeActive:)
          name:UIApplicationDidBecomeActiveNotification
                                              object:nil];
}
Zweig answered 6/11, 2019 at 8:55 Comment(0)
E
7

Application and scene lifecycle is not the same thing!

enter image description here

In my opinion, disabling calls of application state change methods (as well as sending application state change notifications on change the state of each scene) is a mistake, even though there was an understandable intention to force programmers to adapt to the new scenes lifecycle.

Here is a scene delegate template restoring the expected calls of application state change methods of the application delegate:

@available(iOS 13.0, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    
    func sceneWillResignActive(_ scene: UIScene) {
        
        if !UIApplication.shared.connectedScenes.contains(where: { $0.activationState == .foregroundActive && $0 != scene }) {
            UIApplication.shared.delegate?.applicationWillResignActive?(.shared)
        }
    }
    
    
    func sceneDidEnterBackground(_ scene: UIScene) {
        
        if !UIApplication.shared.connectedScenes.contains(where: { $0.activationState == .foregroundActive || $0.activationState == .foregroundInactive }) {
            UIApplication.shared.delegate?.applicationDidEnterBackground?(.shared)
        }
    }
    
    
    func sceneWillEnterForeground(_ scene: UIScene) {
        
        if !UIApplication.shared.connectedScenes.contains(where: { $0.activationState == .foregroundActive || $0.activationState == .foregroundInactive }) {
            UIApplication.shared.delegate?.applicationWillEnterForeground?(.shared)
        }
    }
    
    
    func sceneDidBecomeActive(_ scene: UIScene) {
        
        if !UIApplication.shared.connectedScenes.contains(where: { $0.activationState == .foregroundActive && $0 != scene }) {
            UIApplication.shared.delegate?.applicationDidBecomeActive?(.shared)
        }
    }
}

SceneDelegate.swift

Erickericka answered 26/7, 2020 at 4:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.