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

时间:2019-08-14 04:10:42

标签: ios ios13 application-lifecycle

我有一个支持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模拟器/设备上使用。

我需要进行哪些更改?

1 个答案:

答案 0 :(得分:9)

在iOS 13下支持场景时,不再调用许多UIApplicationDelegate生命周期方法。 UISceneDelegate中现在有相应的生命周期方法。这意味着需要在iOS 13下收听UIScene.didEnterBackgroundNotification通知。您可以在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方法。


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

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

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

简要介绍:有关如何获取UIViewController或UIView的UIScene的详细信息,请参见this answer。 (这并不像您希望的那么简单。)

这需要更新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
}

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

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

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

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

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: https://stackoverflow.com/a/56589151/1226963
        if (notification.object != self.scene) {
            return; // not my scene
        }  // else my own scene
    } // else iOS 12

    // Do stuff
}

如果您想使用viewDidAppear并简化didEnterBackground

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

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
}