View Controller在iOS 12中响应应用程序委托通知,但在iOS 13中不响应

rma*_*ddy 3 application-lifecycle ios ios13

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

在iOS 13之前,这非常简单。添加一行,例如:

NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
Run Code Online (Sandbox Code Playgroud)

viewDidLoad或也许init

然后添加didEnterBackground方法:

@objc func didEnterBackground() {
    // Do my background stuff
}
Run Code Online (Sandbox Code Playgroud)

对于iOS 12及更低版本,这一切都很好。

但是现在有了iOS 13的场景支持,在iOS 13上运行时不会调用我的通知。它仍然可以在iOS 12模拟器/设备上使用。

我需要进行哪些更改?

rma*_*ddy 7

在iOS 13下支持场景时,UIApplicationDelegate不再调用许多生命周期方法。现在有相应的生命周期方法UISceneDelegate。这意味着需要UIScene.didEnterBackgroundNotification在iOS 13下收听通知。您可以在“ 管理应用程序的生命周期”页面上的文档中找到更多详细信息。

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

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)
}
Run Code Online (Sandbox Code Playgroud)

这允许您的视图控制器(或视图)侦听正确的事件,具体取决于它运行在哪个iOS版本上。

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


但是,如果您的应用程序支持多个窗口,则会增加复杂性。

如果您的应用程序用户打开了您的应用程序的多个窗口,则即使给定的视图控制器仍在前台或在后台,该视图控制器(或视图)的每个副本也会收到后台事件通知。一直。

在可能的情况下,您只希望仅将一个置于后台的窗口来响应事件,则需要添加额外的检查。object通知的属性将告诉您哪个特定场景刚刚进入背景。因此,代码需要检查以查看通知的窗口场景是否为与视图控制器(或视图)关联的场景。

简短的侧面说明:有关如何获取UIViewController或UIView的UIScene的详细信息,请参见此答案。(这不像您希望的那么简单)。

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

@objc func didEnterBackground(_ notification: NSNotification) {
    if #available(iOS 13.0, *) {
        // This requires the extension found at: https://stackoverflow.com/a/56589151/1226963
        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
}
Run Code Online (Sandbox Code Playgroud)

有一种方法可以使此过程更简单。使用进行注册时NotificationCenter,您可以指定自己的窗口场景作为该参数的object参数。然后didEnterBackground将仅针对您自己的窗口场景调用该方法。

诀窍在于在注册通知时获得自己的窗口场景。由于您只能viewDidAppear在至少调用一次之后才能获得视图控制器的场景,因此不能使用任何initviewDidLoad甚至viewWillAppear。这些还为时过早。

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

如果您可以等到viewDidAppear,则首先需要在类中添加一个属性,以跟踪是否已查看该属性。

var beenViewed = false
Run Code Online (Sandbox Code Playgroud)

然后添加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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

@objc func didEnterBackground() {
    // Do my background stuff
}
Run Code Online (Sandbox Code Playgroud)

对于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];
}
Run Code Online (Sandbox Code Playgroud)

更复杂的didEnterBackground

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

    // Do stuff
}
Run Code Online (Sandbox Code Playgroud)

如果您想使用viewDidAppear并且更简单didEnterBackground

将一个实例变量添加到您的类中:

BOOL beenViewed;
Run Code Online (Sandbox Code Playgroud)

然后添加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];
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

而且更简单didEnterBackground

- (void)didEnterBackground {
    // Do stuff
}
Run Code Online (Sandbox Code Playgroud)