306

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

我已经实现了以下方法:

- (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:,但我似乎无法确定此时哪个是当前视图控制器。

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

4

7 回答 7

232

迅速

简短的回答

使用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方法中取消注册观察者。

于 2015-12-30T12:27:42.047 回答
213

该方法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 的“手动”布局 - 它只需处理......

于 2011-03-11T20:43:26.120 回答
141

在 ViewController 的方法中使用 Notification CenterviewDidLoad:来调用方法,然后从那里执行您应该在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");
}
于 2013-08-28T05:21:40.363 回答
35

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

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

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

于 2011-03-11T20:54:30.167 回答
7

斯威夫特 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
}
于 2019-09-15T10:24:52.640 回答
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");


}
于 2015-07-17T03:55:37.527 回答
1

使用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!")
    }   
}
于 2020-07-09T11:20:05.343 回答