当应用从后台返回时,为什么viewWillAppear没有被调用?


279

我正在编写一个应用程序,并且如果用户在通话中正在查看该应用程序,则需要更改视图。

我实现了以下方法:

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    NSLog(@"viewWillAppear:");
    _sv.frame = CGRectMake(0.0, 0.0, 320.0, self.view.bounds.size.height);
}

但是当应用程序返回到前台时不会被调用。

我知道我可以实现:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarFrameChanged:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];

但我不想这样做。我宁愿将所有布局信息都放在viewWillAppear:方法中,然后让它处理所有可能的情况。

我什至尝试从applicationWillEnterForeground:调用viewWillAppear:,但是我似乎无法查明当时的当前视图控制器。

有人知道处理此问题的正确方法吗?我确定我缺少一个明显的解决方案。


1
您应该applicationWillEnterForeground:用来确定应用程序何时重新进入活动状态。
sudo rm -rf

我说过我在尝试这个问题。请参考上面。您能否提供一种方法来确定应用程序委托中当前的视图控制器是哪个?
菲利普·沃尔顿

您可以根据需要使用isMemberOfClassisKindOfClass
sudo rm -rf

@sudo rm -rf那怎么办呢?他要在isKindOfClass上调用什么?
occulus 2011年

@occulus:天哪,我只是想回答他的问题。当然,您要做的事情就是走这条路。
sudo rm -rf

Answers:


202

该方法viewWillAppear应在您自己的应用程序中发生的情况下使用,而不是在从另一个应用程序切换回该应用程序时将其放置在前台的情况下使用。

换句话说,如果有人在看另一个应用程序或打了电话,然后切换回了背景较早的应用程序,那么当您离开应用程序时,已经可见的UIViewController可以“忽略”了-就其而言,它从未消失并且仍然可见-因此viewWillAppear没有被调用。

我建议您不要viewWillAppear自欺欺人,它具有特定的含义,您不应该破坏!您可以执行以下重构来达到相同的效果:

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [self doMyLayoutStuff:self];
}

- (void)doMyLayoutStuff:(id)sender {
    // stuff
}

然后,您还doMyLayoutStuff从相应的通知中触发:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(doMyLayoutStuff:) name:UIApplicationDidChangeStatusBarFrameNotification object:self];

顺便说一句,没有开箱即用的方式来判断哪个是“当前” UIViewController。但是您可以找到解决该问题的方法,例如,有一些UINavigationController的委托方法可以找出其中何时显示UIViewController的方法。您可以使用这样的东西来跟踪已呈现的最新UIViewController。

更新资料

如果您在各个位上使用适当的自动调整大小的掩码来布局UI,有时甚至不需要处理UI中的“手动”布局-它就可以解决...


101
感谢您的解决方案。实际上,我为UIApplicationDidBecomeActiveNotification添加了观察者,它的工作原理非常好。
韦恩·刘

2
这当然是正确的答案。但是,值得注意的是,为了响应“没有开箱即用的方式来判断哪个是“当前” UIViewController”,我相信self.navigationController.topViewController有效地提供了它,或者至少提供了堆栈顶部的那个,即当前代码(如果此代码正在视图容器中的主线程上触发)。(可能是错误的,没有玩太多,但似乎可以用。)
马修·弗雷德里克

appDelegate.rootViewController也可以使用,但是它可能返回UINavigationController,然后您将需要.topViewController@MatthewFrederick所说的。
samson

7
UIApplicationDidBecomeActiveNotification不正确(尽管所有人都反对它)。在应用程序启动时(并且仅在应用程序启动时!),此通知的调用方式有所不同 -除viewWillAppear之外还被调用,因此使用此答案,您将获得两次调用。Apple使得这种正确处理变得不必要地困难-文档仍然缺失(截至2013年!)。
亚当

1
我想出的解决方案是使用带有静态变量的类(“ static BOOL enterBackground;”,然后添加类方法的setter和getters。在applicationDidEnterBackground中,将变量设置为true。然后在applicationDidBecomeActive中,检查静态bool ,如果为true,则“ doMyLayoutStuff”并将变量重置为“ NO”,这可以防止:viewWillAppear与applicationDidBecomeActive冲突,并且还可以确保应用程序不认为由于内存压力而终止从后台进入。
vejmartin 2014年

195

迅速

简短答案

使用NotificationCenter观察者而不是viewWillAppear

override func viewDidLoad() {
    super.viewDidLoad()

    // set observer for UIApplication.willEnterForegroundNotification
    NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)

}

// my selector that was defined above
@objc func willEnterForeground() {
    // do stuff
}

长答案

要了解应用何时从后台返回,请使用NotificationCenter观察者而不是viewWillAppear。这是一个示例项目,显示哪些事件在何时发生。(这是对Objective-C答案的改编。)

import UIKit
class ViewController: UIViewController {

    // MARK: - Overrides

    override func viewDidLoad() {
        super.viewDidLoad()
        print("view did load")

        // add notification observers
        NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)

    }

    override func viewWillAppear(_ animated: Bool) {
        print("view will appear")
    }

    override func viewDidAppear(_ animated: Bool) {
        print("view did appear")
    }

    // MARK: - Notification oberserver methods

    @objc func didBecomeActive() {
        print("did become active")
    }

    @objc func willEnterForeground() {
        print("will enter foreground")
    }

}

首次启动应用程序时,输出顺序为:

view did load
view will appear
did become active
view did appear

按下主屏幕按钮,然后将应用返回到前台后,输出顺序为:

will enter foreground
did become active 

因此,如果您最初尝试使用,viewWillAppear那么UIApplication.willEnterForegroundNotification可能就是您想要的。

注意

从iOS 9及更高版本开始,您无需删除观察者。该文档指出:

如果您的应用程序针对iOS 9.0和更高版本或macOS 10.11和更高版本,则无需在其dealloc方法中注销观察者。


6
Swift

140

使用viewDidLoad:ViewController方法中的Notification Center 来调用方法,然后从那里执行您应在viewWillAppear:方法中执行的操作。viewWillAppear:直接呼叫不是一个好的选择。

- (void)viewDidLoad
{
    [super viewDidLoad];
    NSLog(@"view did load");

    [[NSNotificationCenter defaultCenter] addObserver:self 
        selector:@selector(applicationIsActive:) 
        name:UIApplicationDidBecomeActiveNotification 
        object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self 
        selector:@selector(applicationEnteredForeground:) 
        name:UIApplicationWillEnterForegroundNotification
        object:nil];
}

- (void)applicationIsActive:(NSNotification *)notification {
    NSLog(@"Application Did Become Active");
}

- (void)applicationEnteredForeground:(NSNotification *)notification {
    NSLog(@"Application Entered Foreground");
}

9
dealloc然后删除方法中的观察者可能是一个好主意。
AncAinu 2014年

2
viewDidLoad不是将自身添加为观察者的最佳方法,如果是这样,请在viewDidUnload中移除观察者
Injectios

自我添加观察者的最佳方法是什么?
Piotr Wasilewicz

Viewcontroller不能仅观察一个通知,即UIApplicationWillEnterForegroundNotification。为什么都听呢?
zulkarnain shah

34

viewWillAppear:animated:,我认为iOS SDK中最令人困惑的方法之一,在这种情况下(即应用程序切换)永远不会被调用。仅根据视图控制器的视图与应用程序窗口之间的关系来调用该方法,即,仅当其视图出现在应用程序的窗口而不是屏幕上时,才将消息发送到视图控制器。

当您的应用程序进入后台时,用户显然不再看到应用程序窗口的最顶层视图。但是,从您的应用程序窗口的角度来看,它们仍然是最顶层的视图,因此它们并未从窗口中消失。而是,这些视图消失了,因为应用程序窗口消失了。他们没有消失,因为他们窗户消失了。

因此,当用户切换回您的应用程序时,他们显然似乎出现在屏幕上,因为该窗口再次出现。但是从窗口的角度来看,它们根本没有消失。因此,视图控制器永远不会收到viewWillAppear:animated消息。


2
此外,-viewWillDisappear:animated:以前是保存状态的便捷位置,因为它是在应用程序退出时调用的。不过,当应用程序后台运行时,不会调用它,并且可以在不发出警告的情况下终止后台运行的应用程序。
tc。

6
另一个名称不正确的方法是viewDidUnload。您可能以为它与viewDidLoad相反,但不是;仅在内存不足导致视图卸载的情况下才调用它,而不是在解除分配时每次实际卸载视图时才调用它。
occulus 2011年

我绝对同意@occulus。viewWillAppear有其借口,因为不存在(某种)多任务处理,但是viewDidUnload绝对可以有一个更好的名称。
MHC

对我而言,当应用程序在iOS7上后台运行时,会调用viewDidDisappear IS。我可以得到确认吗?
Mike Kogan 2014年

4

迅捷4.2 / 5

override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground),
                                           name: Notification.Name.UIApplicationWillEnterForeground,
                                           object: nil)
}

@objc func willEnterForeground() {
   // do what's needed
}

3

只是尝试使其尽可能简单,请参见下面的代码:

- (void)viewDidLoad
{
   [self appWillEnterForeground]; //register For Application Will enterForeground
}


- (id)appWillEnterForeground{ //Application will enter foreground.

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(allFunctions)
                                                 name:UIApplicationWillEnterForegroundNotification
                                               object:nil];
    return self;
}


-(void) allFunctions{ //call any functions that need to be run when application will enter foreground 
    NSLog(@"calling all functions...application just came back from foreground");


}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.