Why does viewWillAppear not get called when an app comes back from the background?

I’m writing an app and I need to change the view if the user is looking at the app while talking on the phone.

I’ve implemented the following method:

  • ios 8 UITableViewCell detail text not correctly updating
  • UIViewController viewWillAppear not called when adding as subView
  • viewWillAppear, viewDidAppear not being called, not firing
  • UIViewController viewDidLoad vs. viewWillAppear: What is the proper division of labor?
  • NSNotificationCenter with respect to ViewWillAppear and ViewWillDisapper
  • About viewController's “viewDidLoad” and “viewWillAppear” methods
  • - (void)viewWillAppear:(BOOL)animated {
        [super viewWillAppear:animated];
        NSLog(@"viewWillAppear:");
        _sv.frame = CGRectMake(0.0, 0.0, 320.0, self.view.bounds.size.height);
    }
    

    But it’s not being called when the app returns to the foreground.

    I know that I can implement:

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarFrameChanged:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
    

    but I don’t want to do this. I’d much rather put all my layout information in the viewWillAppear: method, and let that handle all possible scenarios.

    I’ve even tried to call viewWillAppear: from applicationWillEnterForeground:, but I can’t seem to pinpoint which is the current view controller at that point.

    Does anybody know the proper way to deal with this? I’m sure I’m missing an obvious solution.

    5 Solutions Collect From Internet About “Why does viewWillAppear not get called when an app comes back from the background?”

    The method viewWillAppear should be taken in the context of what is going on in your own application, and not in the context of your application being placed in the foreground when you switch back to it from another app.

    In other words, if someone looks at another application or takes a phone call, then switches back to your app which was earlier on backgrounded, your UIViewController which was already visible when you left your app ‘doesn’t care’ so to speak — as far as it is concerned, it’s never disappeared and it’s still visible — and so viewWillAppear isn’t called.

    I recommend against calling the viewWillAppear yourself — it has a specific meaning which you shouldn’t subvert! A refactoring you can do to achieve the same effect might be as follows:

    - (void)viewWillAppear:(BOOL)animated {
        [super viewWillAppear:animated];
        [self doMyLayoutStuff:self];
    }
    
    - (void)doMyLayoutStuff:(id)sender {
        // stuff
    }
    

    Then also you trigger doMyLayoutStuff from the appropriate notification:

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(doMyLayoutStuff:) name:UIApplicationDidChangeStatusBarFrameNotification object:self];
    

    There’s no out of the box way to tell which is the ‘current’ UIViewController by the way. But you can find ways around that, e.g. there are delegate methods of UINavigationController for finding out when a UIViewController is presented therein. You could use such a thing to track the latest UIViewController which has been presented.

    Update

    If you layout out UIs with the appropriate autoresizing masks on the various bits, sometimes you don’t even need to deal with the ‘manual’ laying out of your UI – it just gets dealt with…

    Use Notification Center in the viewDidLoad: method of your ViewController to call a method and from there do what you were supposed to do in your viewWillAppear: method. Calling viewWillAppear: directly is not a good option.

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

    Swift

    Short answer

    Use a NotificationCenter observer rather than viewWillAppear.

    override func viewDidLoad() {
        super.viewDidLoad()
    
        // set observer for UIApplicationWillEnterForeground
        NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: .UIApplicationWillEnterForeground, object: nil)
    }
    
    // my selector that was defined above
    @objc func willEnterForeground() {
        // do stuff
    }
    

    Long answer

    To find out when an app comes back from the background, use a NotificationCenter observer rather than viewWillAppear. Here is a sample project that shows which events happen when. (This is an adaptation of 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: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
            NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: NSNotification.Name.UIApplicationWillEnterForeground, 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")
        }
    
    }
    

    On first starting the app, the output order is:

    view did load
    view will appear
    did become active
    view did appear
    

    After pushing the home button and then bringing the app back to the foreground, the output order is:

    will enter foreground
    did become active 
    

    So if you were originally trying to use viewWillAppear then UIApplicationWillEnterForeground is probably what you want.

    viewWillAppear:animated:, one of the most confusing methods in the iOS SDKs in my opinion, is never be invoked in such a situation, i.e., application switching. That method is only invoked according to the relationship between the view controller’s view and the application’s window, i.e., the message is sent to a view controller only if its view appears on the application’s window, not on the screen.

    When your application goes background, obviously the topmost views of the application window are no longer visible to the user. In your application window’s perspective, however, they are still the topmost views and therefore they did not disappear from the window. Rather, those views disappeared because the application window disappeared. They did not disappeared because they disappeared from the window.

    Therefore, when the user switches back to your application, they obviously seem to appear on the screen, because the window appears again. But from the window’s perspective, they haven’t disappeared at all. Therefore the view controllers never get the viewWillAppear:animated message.

    Just trying to make it as easy as possible see code below:

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