¿Por qué no se llama a viewWillAppear cuando una aplicación vuelve del fondo?

Resuelto Philip Walton asked hace 54 años • 7 respuestas

Estoy escribiendo una aplicación y necesito cambiar la vista si el usuario mira la aplicación mientras habla por teléfono.

He implementado el siguiente método:

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    NSLog(@"viewWillAppear:");
    _sv.frame = CGRectMake(0.0, 0.0, 320.0, self.view.bounds.size.height);
}

Pero no se llama cuando la aplicación vuelve al primer plano.

Sé que puedo implementar:

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

pero no quiero hacer esto. Prefiero poner toda mi información de diseño en el método viewWillAppear: y dejar que eso maneje todos los escenarios posibles.

Incluso intenté llamar a viewWillAppear: desde applicationWillEnterForeground:, pero parece que no puedo determinar cuál es el controlador de vista actual en ese momento.

¿Alguien sabe la forma correcta de lidiar con esto? Estoy seguro de que me falta una solución obvia.

Philip Walton avatar Jan 01 '70 08:01 Philip Walton
Aceptado

Rápido

Respuesta corta

Utilice un NotificationCenterobservador en lugar de 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
}

Respuesta larga

Para saber cuándo una aplicación vuelve desde el fondo, utilice un NotificationCenterobservador en lugar de viewWillAppear. Aquí hay un proyecto de muestra que muestra qué eventos suceden y cuándo. (Esta es una adaptación de esta respuesta de 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")
    }

}

Al iniciar la aplicación por primera vez, el orden de salida es:

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

Después de presionar el botón de inicio y luego volver a poner la aplicación en primer plano, el orden de salida es:

will enter foreground
did become active 

Entonces, si originalmente intentabas usarlo viewWillAppear, UIApplication.willEnterForegroundNotificationprobablemente sea lo que deseas.

Nota

A partir de iOS 9 y posteriores, no es necesario eliminar el observador. La documentación dice:

Si su aplicación está destinada a iOS 9.0 y posteriores o macOS 10.11 y posteriores, no necesita cancelar el registro de un observador en su deallocmétodo.

Suragch avatar Dec 30 '2015 12:12 Suragch

El método viewWillAppeardebe tomarse en el contexto de lo que está sucediendo en su propia aplicación, y no en el contexto de que su aplicación se coloque en primer plano cuando vuelva a acceder a ella desde otra aplicación.

En otras palabras, si alguien mira otra aplicación o atiende una llamada telefónica, luego vuelve a su aplicación que anteriormente estaba en segundo plano, su UIViewController, que ya estaba visible cuando salió de su aplicación, "no le importa", por así decirlo. En lo que a él respecta, nunca ha desaparecido y sigue siendo visible, por lo que viewWillAppearno se llama.

Recomiendo no llamarlo viewWillAppearusted mismo: ¡tiene un significado específico que no debe subvertir! Una refactorización que puedes hacer para lograr el mismo efecto podría ser la siguiente:

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

- (void)doMyLayoutStuff:(id)sender {
    // stuff
}

Luego también activas doMyLayoutStuffdesde la notificación correspondiente:

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

Por cierto, no existe una forma inmediata de saber cuál es el UIViewController "actual". Pero puede encontrar formas de evitarlo, por ejemplo, existen métodos delegados de UINavigationController para saber cuándo se presenta un UIViewController. Podría utilizar algo así para realizar un seguimiento del último UIViewController que se ha presentado.

Actualizar

Si diseña las IU con las máscaras de tamaño automático adecuadas en los distintos bits, a veces ni siquiera necesita ocuparse del diseño "manual" de su IU: simplemente se soluciona...

occulus avatar Mar 11 '2011 20:03 occulus