我正在编写一个应用程序,如果用户在打电话时正在查看应用程序,我需要更改视图。
我已经实现了以下方法:
- (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:,但我似乎无法确定此时哪个是当前视图控制器。
有人知道处理这个问题的正确方法吗?我确定我缺少一个明显的解决方案。
applicationWillEnterForeground:
来确定您的应用程序何时重新进入活动状态。
isMemberOfClass
或 isKindOfClass
。
迅速
简短的回答
使用 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
。这是一个示例项目,显示了哪些事件何时发生。 (这是对 this Objective-C answer 的改编。)
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 及更高版本开始,您无需移除观察者。 documentation 指出:
如果您的应用面向 iOS 9.0 及更高版本或 macOS 10.11 及更高版本,则无需在其 dealloc 方法中取消注册观察者。
方法 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 的“手动”布局 - 它只需处理......
self.navigationController.topViewController
有效地提供了它,或者至少是堆栈顶部的那个,它如果此代码在视图控制器的主线程上触发,则将是当前代码。 (可能是错的,没有玩过很多,但似乎工作。)
在 ViewController 的 viewDidLoad:
方法中使用 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");
}
dealloc
方法中删除观察者可能是一个好主意。
viewWillAppear:animated:
,我认为 iOS SDK 中最令人困惑的方法之一,永远不会在这种情况下调用,即应用程序切换。该方法仅根据视图控制器的视图与应用程序窗口之间的关系调用,即只有当视图控制器的视图出现在应用程序的窗口而不是屏幕上时,消息才会发送到视图控制器.
当您的应用程序进入后台时,显然应用程序窗口的最顶层视图不再对用户可见。然而,在您的应用程序窗口的角度来看,它们仍然是最顶层的视图,因此它们并没有从窗口中消失。相反,这些视图消失了,因为应用程序窗口消失了。他们没有消失,因为他们从窗户上消失了。
因此,当用户切换回您的应用程序时,它们显然似乎出现在屏幕上,因为窗口再次出现。但从窗户的角度来看,它们并没有消失。因此,视图控制器永远不会收到 viewWillAppear:animated
消息。
斯威夫特 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
}
只是想让它尽可能简单,请参见下面的代码:
- (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");
}
使用 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!")
}
}