【问题标题】:Why does viewWillAppear not get called when an app comes back from the background?为什么当应用从后台返回时不会调用 viewWillAppear?
【发布时间】:2011-07-13 18:31:42
【问题描述】:

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

我已经实现了以下方法:

- (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:天知道,我只是想回答他的问题。当然,你的方式就是要走的路。

标签: ios objective-c iphone viewwillappear


【解决方案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!")
    }   
}

【讨论】:

    【解决方案2】:

    Swift 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】:

      斯威夫特

      简答

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

      按下home键后,再将app拉回前台,输出顺序为:

      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
      【解决方案4】:

      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 中,我检查静态布尔值,如果是真的,我“doMyLayoutStuff”并将变量重置为“NO”。这可以防止:viewWillAppear 与 applicationDidBecomeActive 冲突,并确保应用程序不认为它是从后台如果由于内存压力而终止。
      • 感谢您的解释。我确实认为这是 Apple 的笨拙之处,因为视图控制器显然应该关心它是在不同的上下文和不同的时间重新显示回来的。我觉得您可以采取任何愚蠢或错误的行为,并尝试将其合理化,就好像它应该是“预期的行为”一样。在这种情况下,解决方案总感觉像是一种变通方法。我不得不处理这种废话很长一段时间,因为视图控制器通常需要在用户返回时刷新,无论它是后台还是不同的视图控制器
      【解决方案5】:

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

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

      【讨论】:

        【解决方案6】:

        在您的 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");
        }
        

        【讨论】:

        • 然后在dealloc 方法中删除观察者可能是个好主意。
        • viewDidLoad 不是将 self 添加为观察者的最佳方法,如果是,请在 viewDidUnload 中删除观察者
        • 将自己添加为观察者的最佳方法是什么?
        • 视图控制器不能只观察一个通知,即 UIApplicationWillEnterForegroundNotification。为什么要两个都听?
        • 您可以使用其中任何一个,而不需要同时收听通知。我刚刚展示了这两个选项。
        【解决方案7】:

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

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

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

        【讨论】:

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