Pourquoi viewWillAppear n'est-il pas appelé lorsqu'une application revient de l'arrière-plan?

280

J'écris une application et je dois changer la vue si l'utilisateur regarde l'application tout en parlant au téléphone.

J'ai implémenté la méthode suivante:

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

Mais elle n'est pas appelée lorsque l'application revient au premier plan.

Je sais que je peux implémenter:

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

mais je ne veux pas faire ça. Je préfère de loin mettre toutes mes informations de mise en page dans la méthode viewWillAppear: et laisser cela gérer tous les scénarios possibles.

J'ai même essayé d'appeler viewWillAppear: from applicationWillEnterForeground :, mais je n'arrive pas à identifier qui est le contrôleur de vue actuel à ce moment-là.

Quelqu'un connaît-il la bonne façon de gérer cela? Je suis sûr que je manque une solution évidente.

Philip Walton
la source
1
Vous devez utiliser applicationWillEnterForeground:pour déterminer quand votre application est à nouveau entrée dans l'état actif.
sudo rm -rf
J'ai dit que j'essayais cela dans ma question. Veuillez vous référer ci-dessus. Pouvez-vous proposer un moyen de déterminer quel est le contrôleur de vue actuel à partir du délégué d'application?
Philip Walton
Vous pouvez utiliser isMemberOfClassou isKindOfClass, selon vos besoins.
sudo rm -rf
@sudo rm -rf Comment cela fonctionnerait-il alors? Sur quoi va-t-il appeler isKindOfClass?
occulus
@occulus: Dieu sait, j'essayais juste de répondre à sa question. Pour sûr, votre façon de le faire est la voie à suivre.
sudo rm -rf

Réponses:

202

La méthode viewWillAppeardoit être prise dans le contexte de ce qui se passe dans votre propre application, et non dans le contexte de votre application placée au premier plan lorsque vous y revenez à partir d'une autre application.

En d'autres termes, si quelqu'un regarde une autre application ou prend un appel téléphonique, puis revient à votre application qui était auparavant en arrière-plan, votre UIViewController qui était déjà visible lorsque vous avez quitté votre application ne s'en soucie pas pour ainsi dire - en ce qui le concerne, il n'a jamais disparu et il est toujours visible - et ainsi viewWillAppear ne s'appelle pas.

Je vous déconseille d'appeler viewWillAppearvous-même - cela a une signification spécifique que vous ne devriez pas renverser! Une refactorisation que vous pouvez effectuer pour obtenir le même effet peut être la suivante:

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

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

Ensuite, vous déclenchez également à doMyLayoutStuffpartir de la notification appropriée:

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

Il n'y a aucun moyen prêt à l'emploi de dire quel est le UIViewController «actuel» au fait. Mais vous pouvez trouver des moyens de contourner cela, par exemple, il existe des méthodes déléguées de UINavigationController pour savoir quand un UIViewController y est présenté. Vous pouvez utiliser une telle chose pour suivre le dernier UIViewController qui a été présenté.

Mettre à jour

Si vous mettez en page les interfaces utilisateur avec les masques de redimensionnement automatique appropriés sur les différents bits, parfois vous n'avez même pas besoin de gérer la mise en page `` manuelle '' de votre interface utilisateur - elle est simplement traitée ...

occulus
la source
101
Merci pour cette solution. J'ajoute en fait l'observateur pour UIApplicationDidBecomeActiveNotification et cela fonctionne très bien.
Wayne Liu
2
C'est certainement la bonne réponse. Cependant, en réponse à "il n'y a pas de moyen prêt à l'emploi de savoir quel est le" courant "UIViewController", je crois qu'il self.navigationController.topViewControllerle fournit effectivement, ou du moins celui sur le dessus de la pile, qui serait le en cours si ce code est en train de tirer sur le thread principal dans un contrôleur de vue. (Cela pourrait être faux, je n'ai pas beaucoup joué avec, mais cela semble fonctionner.)
Matthew Frederick
appDelegate.rootViewControllerfonctionnera aussi, mais il pourrait renvoyer un UINavigationController, et vous en aurez besoin .topViewControllercomme le dit @MatthewFrederick.
samson
7
UIApplicationDidBecomeActiveNotification est incorrect (malgré toutes les personnes qui y ont voté). Au démarrage de l'application (et uniquement au démarrage de l'application!), Cette notification est appelée différemment - elle est appelée en plus de viewWillAppear, donc avec cette réponse, vous la recevrez deux fois. Apple a rendu la tâche inutilement difficile à obtenir - les documents sont toujours manquants (en 2013!).
Adam
1
La solution que j'ai trouvée a été d'utiliser une classe avec une variable statique ('static BOOL enterBackground;' puis j'ajoute des setters et des getters de méthodes de classe. Dans applicationDidEnterBackground, j'ai défini la variable sur true. Puis dans applicationDidBecomeActive, je vérifie le bool statique , et si c'est vrai, je "doMyLayoutStuff" et réinitialise la variable sur 'NO'. Cela empêche: viewWillAppear avec applicationDidBecomeActive, et s'assure également que l'application ne pense pas qu'elle est entrée de l'arrière-plan si elle est terminée en raison de la pression de la mémoire.
vejmartin
197

Rapide

Réponse courte

Utilisez NotificationCenterplutôt un observateur que 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
}

Longue réponse

Pour savoir quand une application revient de l'arrière-plan, utilisez NotificationCenterplutôt un observateur que viewWillAppear. Voici un exemple de projet qui montre quels événements se produisent quand. (Ceci est une adaptation de cette réponse 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")
    }

}

Au premier démarrage de l'application, l'ordre de sortie est le suivant:

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

Après avoir appuyé sur le bouton d'accueil, puis remis l'application au premier plan, l'ordre de sortie est le suivant:

will enter foreground
did become active 

Donc , si vous essayez d'utiliser à l' origine viewWillAppearpuis UIApplication.willEnterForegroundNotificationest probablement ce que vous voulez.

Remarque

Depuis iOS 9 et versions ultérieures, vous n'avez pas besoin de supprimer l'observateur. La documentation indique:

Si votre application cible iOS 9.0 et versions ultérieures ou macOS 10.11 et versions ultérieures, vous n'avez pas besoin de désinscrire un observateur dans sa deallocméthode.

Suragch
la source
6
Dans swift 4.2, le nom de la notification est désormais UIApplication.willEnterForegroundNotification et UIApplication.didBecomeActiveNotification
hordurh
140

Utilisez Notification Center dans la viewDidLoad:méthode de votre ViewController pour appeler une méthode et à partir de là, faites ce que vous étiez censé faire dans votre viewWillAppear:méthode. Appeler viewWillAppear:directement n'est pas une bonne 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");
}
Manju
la source
9
Cela pourrait être une bonne idée de supprimer l'observateur dans la deallocméthode.
AncAinu
2
viewDidLoad n'est pas la meilleure méthode pour s'ajouter en tant qu'observateur, si c'est le cas, supprimez l'observateur dans viewDidUnload
Injectios
quelle est la meilleure méthode pour ajouter soi-même un observateur?
Piotr Wasilewicz
Le viewcontroller ne peut-il pas observer une seule notification, c'est-à-dire UIApplicationWillEnterForegroundNotification. Pourquoi écouter les deux?
zulkarnain shah
34

viewWillAppear:animated:, l'une des méthodes les plus déroutantes des SDK iOS à mon avis, ne doit jamais être invoquée dans une telle situation, c'est-à-dire le changement d'application. Cette méthode n'est invoquée qu'en fonction de la relation entre la vue du contrôleur de vue et la fenêtre de l'application , c'est-à-dire que le message n'est envoyé à un contrôleur de vue que si sa vue apparaît sur la fenêtre de l'application, pas sur l'écran.

Lorsque votre application passe en arrière-plan, les vues les plus hautes de la fenêtre d'application ne sont évidemment plus visibles pour l'utilisateur. Dans la perspective de votre fenêtre d'application, cependant, ce sont toujours les vues les plus hautes et donc elles n'ont pas disparu de la fenêtre. Ces vues ont plutôt disparu car la fenêtre de l'application a disparu. Ils n'ont pas disparu parce qu'ils ont disparu de la fenêtre.

Par conséquent, lorsque l'utilisateur revient à votre application, elles semblent évidemment apparaître à l'écran, car la fenêtre apparaît à nouveau. Mais du point de vue de la fenêtre, ils n'ont pas du tout disparu. Par conséquent, les contrôleurs de vue ne reçoivent jamais le viewWillAppear:animatedmessage.

MHC
la source
2
De plus, -viewWillDisappear: animated: était un endroit pratique pour enregistrer l'état car il est appelé à la sortie de l'application. Cependant, elle n'est pas appelée lorsque l'application est en arrière-plan et une application en arrière-plan peut être supprimée sans avertissement.
tc.
6
ViewDidUnload est une autre méthode très mal nommée. On pourrait penser que c'était l'opposé de viewDidLoad, mais non; il n'est appelé que lorsqu'il y a eu une situation de mémoire insuffisante qui a provoqué le déchargement de la vue, et pas à chaque fois que la vue est réellement déchargée au moment de la libération.
occulus
Je suis absolument d'accord avec @occulus. viewWillAppear a son excuse parce que le (genre de) multitâche n'était pas là, mais viewDidUnload pourrait certainement avoir un meilleur nom.
MHC
Pour moi, viewDidDisappear EST appelé lorsque l'application est en arrière-plan sur iOS7. Puis-je obtenir une confirmation?
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
}
aviran
la source
3

En essayant de le rendre aussi simple que possible, voir le code ci-dessous:

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


}
ConfusedDeer
la source