视图控制器在 iOS 12 中响应应用程序委托通知,但在 iOS 13 中不响应

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

我有一个应用程序支持 iOS 12。我正在添加对 iOS 13 的支持。我有一个视图控制器需要在应用程序进入后台时执行快速操作。

在 iOS 13 之前就足够简单了。添加一行,例如:

NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)

viewDidLoad 或者 init.

然后添加didEnterBackground方法:

@objc func didEnterBackground() {
    // Do my background stuff
}

iOS 12 及更早版本都很好。

但现在有了 iOS 13 中的场景支持,当 运行 和 iOS 13 时我的通知不会被调用。它仍然适用于 iOS 12 simulator/device.

我需要做哪些改变?

支持场景时iOS 13、UIApplicationDelegate lifecycle methods are no longer called. There are now corresponding lifecycle methods in the UISceneDelegate. This means there is a need to listen to the UIScene.didEnterBackgroundNotification notification under iOS 13. You can find more details in the documentation at the Managing Your App's Life Cycle页面很多。

您需要将通知观察者代码更新为:

if #available(iOS 13.0, *) {
    NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackground), name: UIScene.didEnterBackgroundNotification, object: nil)
} else {
    NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
}

这允许您的视图控制器(或视图)根据 运行 所处的 iOS 版本来监听正确的事件。

根据 iOS 的版本,为两个事件调用相同的 didEnterBackground 方法。


但是如果您的应用支持多个 windows。

如果您的应用程序的用户打开了多个 windows 您的应用程序,那么即使给定的视图控制器仍在前景或者如果一直在背景中。

在可能的情况下,您只希望刚刚放入后台的 window 响应事件,您需要添加一个额外的检查。通知的object属性会告诉你具体是哪个场景刚刚进入后台。因此代码需要检查通知的 window 场景是否与视图控制器(或视图)关联。

简要说明:有关如何获取 UIViewController 或 UIView 的 UIScene 的详细信息,请参阅 。 (这并不像你希望的那么简单)。

这需要对 didEnterBackground 方法进行如下更新:

@objc func didEnterBackground(_ notification: NSNotification) {
    if #available(iOS 13.0, *) {
        // This requires the extension found at: 
        if let winScene = notification.object as? UIWindowScene, winScene === self.scene {
            return; // not my scene man, I'm outta here
        } // else this is my scene, handle it
    } // else iOS 12 and we need to handle the app going to the background

    // Do my background stuff
}

有一种方法可以使这更简单一些。使用NotificationCenter 注册时,您可以将自己的window 场景指定为object 参数的参数。那么didEnterBackground方法只会为你自己的window场景调用。

这样做的诀窍是在注册通知时获得自己的 window 场景。由于你只能在 viewDidAppear 至少被调用一次后才能获得视图控制器的场景,你不能使用任何 initviewDidLoad 甚至 viewWillAppear。那些都太早了。

由于 viewDidAppear 可以被多次调用,您最终将每次都调用 addObserver,这是一个问题,因为这样您的处理程序将针对单个事件被多次调用。所以一种想法是注销 viewDidDisappear 中的观察者。但是现在这有一个问题,如果其他视图控制器正在覆盖它,则您的视图控制器不会被调用。所以技巧是在 viewDidAppear 中添加观察者,但只是第一次为视图控制器的特定实例调用它。

如果您可以等到 viewDidAppear,那么首先您需要在 class 中添加一个 属性 以跟踪它是否已被查看。

var beenViewed = false

然后添加viewDidAppear:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    if !beenViewed {
        beenViewed = true

        if #available(iOS 13.0, *) {
            // Only be notified of my own window scene
            NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackground), name: UIScene.didEnterBackgroundNotification, object: self.view.window?.windowScene)
        } else {
            NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
        }
    }
}

然后您的 didEnterBackground 可以再次成为旧的简单版本:

@objc func didEnterBackground() {
    // Do my background stuff
}

对于Objective-C,代码如下:

viewDidAppear之前注册通知:

if (@available(iOS 13.0, *)) {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground:) name:UISceneDidEnterBackgroundNotification object:nil];
} else {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
}

越复杂didEnterBackground:

- (void)didEnterBackground:(NSNotification *)notification {
    if (@available(iOS 13.0, *)) {
        // This requires the extension found at: 
        if (notification.object != self.scene) {
            return; // not my scene
        }  // else my own scene
    } // else iOS 12

    // Do stuff
}

如果你想使用 viewDidAppear 并且有一个更简单的 didEnterBackground:

将实例变量添加到您的 class:

BOOL beenViewed;

然后添加viewDidAppear:

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];

    if (!beenViewed) {
        beenViewed = YES;

        if (@available(iOS 13.0, *)) {
            // Only be notified of my own window scene
            [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground) name:UISceneDidEnterBackgroundNotification object:self.view.window.windowScene];
        } else {
            [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
        }
    }
}

更简单的didEnterBackground:

- (void)didEnterBackground {
    // Do stuff
}