ChatGPT解决这个技术问题 Extra ChatGPT

为什么当应用程序从后台返回时不会调用 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: 方法中,并让它处理所有可能的情况。

我什至尝试从 applicationWillEnterForeground: 调用 viewWillAppear:,但我似乎无法确定此时哪个是当前视图控制器。

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

您应该使用 applicationWillEnterForeground: 来确定您的应用程序何时重新进入活动状态。
我说我在我的问题中尝试过。请参考上文。您能否提供一种方法来确定应用程序委托中的当前视图控制器?
您可以根据需要使用 isMemberOfClassisKindOfClass
@sudo rm -rf 那将如何工作?他将如何调用 isKindOfClass?
@occulus:天知道,我只是想回答他的问题。当然,你的做法就是要走的路。

S
Suragch

迅速

简短的回答

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


在 swift 4.2 中,通知名称现在是 UIApplication.willEnterForegroundNotification 和 UIApplication.didBecomeActiveNotification
o
occulus

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


感谢您提供此解决方案。我实际上为 UIApplicationDidBecomeActiveNotification 添加了观察者,它工作得很好。
这当然是正确的答案。然而,值得注意的是,为了回应“没有开箱即用的方法来判断哪个是‘当前’UIViewController”,我相信self.navigationController.topViewController有效地提供了它,或者至少是堆栈顶部的那个,它如果此代码在视图控制器的主线程上触发,则将是当前代码。 (可能是错的,没有玩过很多,但似乎工作。)
UIApplicationDidBecomeActiveNotification 不正确(尽管所有人都支持它)。在应用程序启动时(并且仅在应用程序启动时!)此通知的调用方式有所不同 - 除了 viewWillAppear 之外,它还被调用,因此使用此答案,您将调用它两次。 Apple 让这件事变得不必要地困难 - 文档仍然丢失(截至 2013 年!)。
我想出的解决方案是使用带有静态变量的类('static BOOL enterBackground;'然后我添加类方法setter和getter。在applicationDidEnterBackground中,我将变量设置为true。然后在applicationDidBecomeActive中,我检查静态bool ,如果是真的,我“doMyLayoutStuff”并将变量重置为“NO”。这可以防止:viewWillAppear 与 applicationDidBecomeActive 发生冲突,并且还确保应用程序不会认为如果由于内存压力而终止,它不会从后台进入。
感谢您的解释。我确实认为这是 Apple 的笨拙之处,因为视图控制器显然应该关心它是在不同的上下文和不同的时间重新显示回来的。我觉得您可以采取任何愚蠢或错误的行为,并尝试将其合理化,就好像它应该是“预期的行为”一样。在这种情况下,解决方案总感觉像是一种变通方法。我不得不处理这种废话很长一段时间,因为视图控制器通常需要在用户返回时刷新,无论它是后台还是不同的视图控制器
R
Rajan Balana

在 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 方法中删除观察者可能是一个好主意。
viewDidLoad 不是将 self 添加为观察者的最佳方法,如果是,请在 viewDidUnload 中删除观察者
将自己添加为观察者的最佳方法是什么?
视图控制器不能只观察一个通知,即 UIApplicationWillEnterForegroundNotification。为什么要两个都听?
您可以使用其中任何一个,而不需要同时收听通知。我刚刚展示了这两个选项。
M
MHC

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

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

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


此外, -viewWillDisappear:animated: 曾经是一个方便的保存状态的地方,因为它是在应用退出时调用的。但是,当应用程序处于后台时不会调用它,并且可以在没有警告的情况下终止后台应用程序。
另一个名字很糟糕的方法是 viewDidUnload。你会认为它与 viewDidLoad 相反,但不是;它仅在内存不足导致视图卸载时调用,而不是每次在 dealloc 时间实际卸载视图时调用。
我完全同意@occulus。 viewWillAppear 有它的借口,因为(某种)多任务处理不存在,但 viewDidUnload 肯定可以有一个更好的名字。
对我来说,当应用程序在 iOS7 上运行时调用 viewDidDisappear 。我可以得到确认吗?
A
Ali A. Jalil

斯威夫特 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
}

R
Rajan Balana

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

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


}

P
Peter Kreinz

使用 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!")
    }   
}

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅