¿Por qué no se llama a viewWillAppear cuando una aplicación vuelve del fondo?
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.
Rápido
Respuesta corta
Utilice un NotificationCenter
observador 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 NotificationCenter
observador 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.willEnterForegroundNotification
probablemente 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
dealloc
método.
El método viewWillAppear
debe 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 viewWillAppear
no se llama.
Recomiendo no llamarlo viewWillAppear
usted 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 doMyLayoutStuff
desde 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...