Perché viewWillAppear non viene chiamato quando un'app torna dallo sfondo?


280

Sto scrivendo un'app e devo cambiare la visualizzazione se l'utente sta guardando l'app mentre parla al telefono.

Ho implementato il seguente metodo:

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

Ma non viene chiamato quando l'app torna in primo piano.

So che posso implementare:

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

ma non voglio farlo. Preferirei piuttosto mettere tutte le mie informazioni di layout nel metodo viewWillAppear: e lasciare che gestiscano tutti i possibili scenari.

Ho anche provato a chiamare viewWillAppear: da applicationWillEnterForeground :, ma non riesco a individuare quale sia il controller di visualizzazione corrente a quel punto.

Qualcuno conosce il modo corretto di affrontare questo? Sono sicuro che mi manca una soluzione ovvia.


1
Dovresti utilizzare applicationWillEnterForeground:per determinare quando l'applicazione è rientrata nello stato attivo.
sudo rm -rf

Ho detto che lo stavo provando nella mia domanda. Si prega di fare riferimento sopra. Puoi offrire un modo per determinare qual è l'attuale controller di visualizzazione all'interno del delegato dell'app?
Philip Walton,

Puoi usare isMemberOfClasso isKindOfClass, a seconda delle tue esigenze.
sudo rm -rf

@sudo rm -rf Come funzionerebbe allora? Come chiamerà isKindOfClass?
occulus

@occulus: Dio lo sa, stavo solo cercando di rispondere alla sua domanda. Sicuramente il tuo modo di farlo è la strada da percorrere.
sudo rm -rf

Risposte:


202

Il metodo viewWillAppeardovrebbe essere adottato nel contesto di ciò che sta accadendo nella propria applicazione e non nel contesto in cui l'applicazione viene posizionata in primo piano quando si ritorna ad essa da un'altra app.

In altre parole, se qualcuno guarda un'altra applicazione o risponde a una telefonata, quindi torna alla tua app che in precedenza era in background, il tuo UIViewController che era già visibile quando hai lasciato l'app 'non importa' per così dire - per quanto riguarda, non è mai scomparso ed è ancora visibile - e quindi viewWillAppearnon viene chiamato.

Sconsiglio di chiamare viewWillAppearte stesso - ha un significato specifico che non dovresti sovvertire! Un refactoring che puoi fare per ottenere lo stesso effetto potrebbe essere il seguente:

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

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

Quindi si attiva anche doMyLayoutStuffdalla notifica appropriata:

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

A proposito, non esiste un modo per dire quale sia l'UIViewController "attuale". Ma puoi trovare modi per aggirare questo, ad esempio ci sono metodi delegati di UINavigationController per scoprire quando un UIViewController è presentato al suo interno. È possibile utilizzare una cosa del genere per tenere traccia dell'ultimo UIViewController che è stato presentato.

Aggiornare

Se imposti le interfacce utente con le appropriate maschere di ridimensionamento automatico sui vari bit, a volte non hai nemmeno bisogno di occuparti del layout "manuale" della tua interfaccia utente - viene solo trattato ...


101
Grazie per questa soluzione In realtà aggiungo l'osservatore per UIApplicationDidBecomeActiveNotification e funziona molto bene.
Wayne Liu,

2
Questa è sicuramente la risposta corretta. Da notare, tuttavia, in risposta a "non c'è un modo per dire quale sia l'attuale" UIViewController "", credo che self.navigationController.topViewControllerlo fornisca effettivamente, o almeno quello in cima allo stack, che sarebbe il quello attuale se questo codice viene attivato sul thread principale in un controller di visualizzazione. (Potrebbe essere sbagliato, non ci ho giocato molto, ma sembra funzionare.)
Matthew Frederick,

appDelegate.rootViewControllerfunzionerà anche, ma potrebbe restituire un UINavigationControllere quindi sarà necessario .topViewControllercome dice @MatthewFrederick.
samson,

7
UIApplicationDidBecomeActiveNotification non è corretto (nonostante tutte le persone che lo hanno votato). All'avvio dell'app (e solo all'avvio dell'app!) Questa notifica viene chiamata in modo diverso : viene chiamata oltre a viewWillAppear, quindi con questa risposta la richiamerai due volte. Apple ha reso inutilmente difficile farlo bene - i documenti mancano ancora (dal 2013!).
Adam,

1
La soluzione che mi è venuta in mente è stata quella di utilizzare una classe con una variabile statica ("static BOOL enterBackground;", quindi aggiungo setter e getter di metodi di classe. In applicationDidEnterBackground, ho impostato la variabile su true. Poi in applicationDidBecomeActive, ho controllato il bool statico e, se è vero, io "doMyLayoutStuff" e reimposto la variabile su "NO", in modo da impedire: viewWillAppear con applicationDidBecomeActive collision e inoltre assicurarsi che l'applicazione non ritenga che sia entrata in background se terminata a causa della pressione della memoria.
Vejmartin,

197

veloce

Risposta breve

Usa un NotificationCenterosservatore piuttosto che 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
}

Risposta lunga

Per scoprire quando un'app torna dallo sfondo, usa un NotificationCenterosservatore anziché viewWillAppear. Ecco un esempio di progetto che mostra quali eventi accadono quando. (Questo è un adattamento di questa risposta dell'Obiettivo-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 primo avvio dell'app, l'ordine di output è:

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

Dopo aver premuto il pulsante Home e riportato l'app in primo piano, l'ordine di output è:

will enter foreground
did become active 

Quindi, se stavi originariamente cercando di utilizzare viewWillAppear allora UIApplication.willEnterForegroundNotificationè probabilmente quello che volete.

Nota

A partire da iOS 9 e versioni successive, non è necessario rimuovere l'osservatore. La documentazione afferma:

Se la tua app ha come target iOS 9.0 e versioni successive o macOS 10.11 e versioni successive, non è necessario annullare la registrazione di un osservatore nel suo deallocmetodo.


6
In rapido 4.2 il nome della notifica è ora UIApplication.willEnterForegroundNotification e UIApplication.didBecomeActiveNotification
hordurh

140

Usa il Centro notifiche nel viewDidLoad:metodo di ViewController per chiamare un metodo e da lì fai quello che dovevi fare nel tuo viewWillAppear:metodo. Chiamare viewWillAppear:direttamente non è una buona opzione.

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

9
Potrebbe essere una buona idea rimuovere l'osservatore nel deallocmetodo allora.
AncAinu,

2
viewDidLoad non è il metodo migliore per aggiungere sé come osservatore, in tal caso, rimuovere l'osservatore in viewDidUnload
Injectios

qual è il metodo migliore per aggiungere un osservatore?
Piotr Wasilewicz,

Il viewcontroller non può osservare una sola notifica, ad esempio UIApplicationWillEnterForegroundNotification. Perché ascoltare entrambi?
zulkarnain shah,

34

viewWillAppear:animated:, uno dei metodi più confusi negli SDK di iOS secondo me, non viene mai invocato in una situazione del genere, vale a dire il cambio di applicazione. Tale metodo viene invocato solo in base alla relazione tra la vista del controller di visualizzazione e la finestra dell'applicazione , ovvero il messaggio viene inviato a un controller della vista solo se la sua vista appare sulla finestra dell'applicazione, non sullo schermo.

Quando l'applicazione passa in background, ovviamente le viste più in alto della finestra dell'applicazione non sono più visibili all'utente. Nella prospettiva della finestra dell'applicazione, tuttavia, sono ancora le viste più in alto e quindi non sono scomparse dalla finestra. Piuttosto, quelle viste sono scomparse perché la finestra dell'applicazione è scomparsa. Essi non sono scomparsi, perché sono scomparsi da finestra.

Pertanto, quando l'utente torna alla tua applicazione, sembrano ovviamente apparire sullo schermo, perché la finestra appare di nuovo. Ma dal punto di vista della finestra, non sono affatto scomparsi. Pertanto, i controller di visualizzazione non ricevono mai il viewWillAppear:animatedmessaggio.


2
Inoltre, -viewWillDisappear: animato: era un posto comodo per salvare lo stato poiché è chiamato all'uscita dell'app. Tuttavia, non viene chiamato quando l'app è in background e un'app in background può essere uccisa senza preavviso.
tc.

6
Un altro metodo chiamato male è viewDidUnload. Penseresti che fosse l'opposto di viewDidLoad, ma no; viene chiamato solo in presenza di una situazione di memoria insufficiente che ha causato lo scaricamento della vista e non ogni volta che la vista viene effettivamente scaricata in fase di dealloc.
occulus

Sono assolutamente d'accordo con @occulus. viewWillAppear ha la sua scusa perché il (tipo di) multitasking non c'era, ma viewDidUnload avrebbe sicuramente potuto avere un nome migliore.
MHC

Per me viewDidDisappear viene chiamato quando l'app è in background su iOS7. Posso avere una conferma?
Mike Kogan,

4

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

Sto solo cercando di renderlo il più semplice possibile, vedi il codice qui sotto:

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


}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.