¿Por qué viewWillAppear no recibe llamadas cuando una aplicación vuelve del fondo?


Estoy escribiendo una aplicación y necesito cambiar la vista si el usuario está mirando 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. Preferiría poner toda mi información de diseño en el método viewWillAppear:, y dejar que maneje todos los escenarios posibles.

Incluso he intentado llamar viewWillAppear: desde applicationWillEnterForeground:, pero parece que no puedo identificar cuál es el controlador de vista actual en ese punto.

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

Author: Philip Walton, 2011-03-11

5 answers

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 ella desde otra aplicación.

En otras palabras, si alguien mira otra aplicación o recibe una llamada telefónica, entonces vuelve a su aplicación que era anterior en backgrounded, su UIViewController que ya estaba visible cuando dejó su aplicación' no le importa ' por así decirlo -- en cuanto a lo que es preocupado, nunca ha desaparecido y todavía es visible {y así viewWillAppear no se llama.

Recomiendo no llamar al viewWillAppear usted mismo has ¡tiene un significado específico que no debe subvertir! Una refactorización que puede 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 se activa doMyLayoutStuff desde la notificación apropiada:

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

Por cierto, no hay una forma de saber cuál es el controlador UIViewController 'actual'. Pero usted puede encontrar maneras alrededor que, por ejemplo, hay métodos delegados de UINavigationController para averiguar cuándo se presenta un UIViewController. Usted podría usar tal cosa para rastrear el último UIViewController que se ha presentado.

Actualización

Si diseñas las interfaces de usuario con las máscaras de autoresizing apropiadas en los diversos bits, a veces ni siquiera necesitas lidiar con el diseño 'manual' de tu interfaz de usuario, simplemente se trata...

 176
Author: occulus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-11 14:51:28

Swift

Respuesta Corta

Use un observador NotificationCenter en lugar de 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
}

Respuesta Larga

Para saber cuándo una aplicación regresa del fondo, use un observador NotificationCenter en lugar de viewWillAppear. Aquí hay un proyecto de ejemplo que muestra qué eventos ocurren 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: 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")
    }

}

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 empujar el botón de inicio y luego llevar la aplicación de nuevo al primer plano, el orden de salida es:

will enter foreground
did become active 

Así que si originalmente estabas tratando de usar viewWillAppear entonces UIApplicationWillEnterForeground es probablemente lo que quieres.

Nota

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

Si tu app está dirigida a iOS 9.0 y versiones posteriores o a macOS 10.11 y versiones posteriores, no es necesario anular el registro de un observador en su método dealloc.

 142
Author: Suragch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-21 05:42:43

Use Notification Center en el método viewDidLoad: de su ViewController para llamar a un método y desde allí haga lo que se suponía que debía hacer en su método viewWillAppear:. Llamar a viewWillAppear: directamente no es una buena opción.

- (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");
}
 137
Author: Manju,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-23 06:22:15

viewWillAppear:animated:, uno de los métodos más confusos en los SDK de iOS en mi opinión, nunca se invoca en tal situación, es decir, el cambio de aplicación. Ese método solo se invoca de acuerdo con la relación entre la vista del controlador de vista y la ventana de la aplicación, es decir, el mensaje se envía a un controlador de vista solo si su vista aparece en la ventana de la aplicación, no en la pantalla.

Cuando su aplicación pasa a segundo plano, obviamente las vistas superiores de la aplicación las ventanas ya no son visibles para el usuario. En la perspectiva de la ventana de su aplicación, sin embargo, siguen siendo las vistas superiores y, por lo tanto, no desaparecieron de la ventana. Más bien, esas vistas desaparecieron porque la ventana de la aplicación desapareció. No desaparecieron porque desaparecieron de la ventana.

Por lo tanto, cuando el usuario cambia de nuevo a su aplicación, obviamente parecen aparecer en la pantalla, porque la ventana aparece de nuevo. Pero desde el perspectiva de la ventana, no han desaparecido en absoluto. Por lo tanto, los controladores de vista nunca reciben el mensaje viewWillAppear:animated.

 32
Author: MHC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-03-11 20:54:30

Solo trato de hacerlo lo más fácil posible ver el código a continuación:

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


}
 3
Author: ConfusedDeer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-23 06:26:46