当应用程序从后台返回时,为什么viewWillAppear不被调用?

我正在写一个应用程序,如果用户在打电话时正在看应用程序,我需要改变视图。

我实现了以下方法:

- (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:方法中,并让它处理所有可能的场景。

我甚至尝试调用viewWillAppear: from applicationWillEnterForeground:,但我似乎无法确定在那一点上哪个是当前视图控制器。

有人知道正确的处理方法吗?我肯定我错过了一个显而易见的解决方案。

159280 次浏览

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的“手动”布局-它只是处理…

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

当你的应用程序进入后台时,显然应用程序窗口的最顶层视图对用户来说不再可见。然而,在应用程序窗口的透视图中,它们仍然是最顶层的视图,因此它们并没有从窗口中消失。相反,这些视图消失是因为应用程序窗口消失了。他们没有消失,因为他们消失在窗口。

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

使用ViewController的viewDidLoad:方法中的通知中心来调用一个方法,并从那里完成你应该在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");
}

只是想让它尽可能简单,请参阅下面的代码:

- (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");




}

斯威夫特

简短的回答

使用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

在按下home键,然后将应用程序带回前台后,输出顺序为:

will enter foreground
did become active

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

请注意

从iOS 9及以后版本开始,你不需要移除观察者。文档声明:

如果你的应用程序的目标是iOS 9.0及更高版本或macOS 10.11及更高版本,你 不需要在它的dealloc方法中注销一个观察者

Swift 4.2 / 5

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


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

使用SwiftUI会更简单:

var body: some View {
Text("Hello World")
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)) { _ in
print("Moving to background!")
}
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
print("Moving back to foreground!")
}
}