Warum wird viewWillAppear nicht aufgerufen, wenn eine App aus dem Hintergrund zurückkommt?

279

Ich schreibe eine App und muss die Ansicht ändern, wenn der Benutzer die App ansieht, während er telefoniert.

Ich habe die folgende Methode implementiert:

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

Es wird jedoch nicht aufgerufen, wenn die App wieder in den Vordergrund zurückkehrt.

Ich weiß, dass ich Folgendes implementieren kann:

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

aber ich will das nicht tun. Ich würde viel lieber alle meine Layoutinformationen in die viewWillAppear: -Methode einfügen und diese alle möglichen Szenarien behandeln lassen.

Ich habe sogar versucht, viewWillAppear: von applicationWillEnterForeground: aufzurufen, aber ich kann anscheinend nicht genau bestimmen, welcher der aktuelle Ansichts-Controller zu diesem Zeitpunkt ist.

Kennt jemand den richtigen Weg, um damit umzugehen? Ich bin sicher, ich vermisse eine offensichtliche Lösung.

Philip Walton
quelle
1
Sie sollten verwenden, applicationWillEnterForeground:um festzustellen, wann Ihre Anwendung wieder in den aktiven Status eingetreten ist.
Sudo rm -rf
Ich sagte, ich habe das in meiner Frage versucht. Bitte beziehen Sie sich oben. Können Sie eine Möglichkeit anbieten, aus dem App-Delegaten heraus zu ermitteln, welcher der aktuelle Ansichts-Controller ist?
Philip Walton
Sie können isMemberOfClassoder verwenden isKindOfClass, je nach Ihren Bedürfnissen.
Sudo rm -rf
@sudo rm -rf Wie würde das dann funktionieren? Wie wird er isKindOfClass anrufen?
Occulus
@occulus: Meine Güte, ich habe nur versucht, seine Frage zu beantworten. Sicher ist Ihre Art, es zu tun, der richtige Weg.
Sudo rm -rf

Antworten:

202

Die Methode viewWillAppearsollte im Kontext dessen verwendet werden, was in Ihrer eigenen Anwendung vor sich geht, und nicht im Kontext Ihrer Anwendung, die in den Vordergrund gestellt wird, wenn Sie von einer anderen App zurück zu ihr wechseln.

Mit anderen Worten, wenn jemand eine andere Anwendung ansieht oder einen Anruf entgegennimmt, wechselt er zurück zu Ihrer App, die zuvor im Hintergrund war. Ihr UIViewController, der bereits sichtbar war, als Sie Ihre App verlassen haben, ist sozusagen "egal". Was es betrifft, ist es nie verschwunden und immer noch sichtbar - und so viewWillAppear nicht genannt.

Ich empfehle, sich nicht viewWillAppearselbst anzurufen - es hat eine bestimmte Bedeutung, die Sie nicht untergraben sollten! Ein Refactoring, das Sie durchführen können, um den gleichen Effekt zu erzielen, könnte wie folgt aussehen:

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

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

Dann lösen Sie auch doMyLayoutStuffaus der entsprechenden Benachrichtigung aus:

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

Es gibt übrigens keinen sofort einsatzbereiten Weg, um festzustellen, welcher der 'aktuelle' UIViewController ist. Sie können jedoch Wege finden, um dies zu umgehen, z. B. gibt es delegierte Methoden von UINavigationController, um herauszufinden, wann ein UIViewController darin präsentiert wird. Sie können so etwas verwenden, um den neuesten UIViewController zu verfolgen, der vorgestellt wurde.

Aktualisieren

Wenn Sie Benutzeroberflächen mit den entsprechenden Masken für die automatische Größenänderung für die verschiedenen Bits auslegen, müssen Sie sich manchmal nicht einmal mit dem manuellen Layout Ihrer Benutzeroberfläche befassen - es wird nur ...

Occulus
quelle
101
Danke für diese Lösung. Ich füge tatsächlich den Beobachter für UIApplicationDidBecomeActiveNotification hinzu und es funktioniert sehr gut.
Wayne Liu
2
Dies ist sicherlich die richtige Antwort. Bemerkenswert ist jedoch, dass ich als Antwort auf "Es gibt keinen sofort einsatzbereiten Weg, um festzustellen, welcher der 'aktuelle' UIViewController ist" dies meiner Meinung nach self.navigationController.topViewControllereffektiv zur Verfügung stelle , oder zumindest den oben auf dem Stapel, der der wäre Aktueller, wenn dieser Code auf den Haupt-Thread in einem Ansichts-Controller ausgelöst wird. (Könnte falsch sein, habe nicht viel damit gespielt, scheint aber zu funktionieren.)
Matthew Frederick
appDelegate.rootViewControllerwird auch funktionieren, aber es könnte ein zurückgeben UINavigationController, und dann brauchen Sie, .topViewControllerwie @MatthewFrederick sagt.
Samson
7
UIApplicationDidBecomeActiveNotification ist falsch (trotz aller Befürworter). Beim App-Start (und nur beim App-Start!) Wird diese Benachrichtigung anders aufgerufen - sie wird zusätzlich zu viewWillAppear aufgerufen. Mit dieser Antwort wird sie also zweimal aufgerufen. Apple hat es unnötig schwierig gemacht, dies richtig zu machen - die Dokumente fehlen noch (Stand 2013!).
Adam
1
Die Lösung, die ich gefunden habe, bestand darin, eine Klasse mit einer statischen Variablen zu verwenden ('static BOOL enterBackground;'). Dann füge ich Setter und Getter für Klassenmethoden hinzu. In applicationDidEnterBackground setze ich die Variable auf true. Dann überprüfe ich in applicationDidBecomeActive den statischen Bool und wenn es wahr ist, "doMyLayoutStuff" und setze die Variable auf 'NO' zurück. Dies verhindert: viewWillAppear mit applicationDidBecomeActive-Kollision und stellt auch sicher, dass die Anwendung nicht glaubt, dass sie aus dem Hintergrund eingegeben wurde, wenn sie aufgrund von Speicherdruck beendet wird.
Vejmartin
195

Schnell

Kurze Antwort

Verwenden Sie NotificationCenterlieber einen Beobachter als 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
}

Lange Antwort

Verwenden Sie einen NotificationCenterBeobachter anstelle von, um herauszufinden, wann eine App aus dem Hintergrund zurückkommt viewWillAppear. Hier ist ein Beispielprojekt, das zeigt, welche Ereignisse wann auftreten. (Dies ist eine Anpassung dieser Objective-C-Antwort .)

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

}

Beim ersten Start der App lautet die Ausgabereihenfolge:

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

Nachdem Sie die Home-Taste gedrückt und die App wieder in den Vordergrund gebracht haben, lautet die Ausgabereihenfolge:

will enter foreground
did become active 

Also , wenn Sie wurden ursprünglich versucht , zu verwenden , viewWillAppeardann UIApplication.willEnterForegroundNotificationist wahrscheinlich das, was Sie wollen.

Hinweis

Ab iOS 9 müssen Sie den Beobachter nicht mehr entfernen. In der Dokumentation heißt es:

Wenn Ihre App auf iOS 9.0 und höher oder auf macOS 10.11 und höher ausgerichtet ist, müssen Sie die Registrierung eines Beobachters in seiner deallocMethode nicht aufheben .

Suragch
quelle
6
In Swift 4.2 lautet der Benachrichtigungsname jetzt UIApplication.willEnterForegroundNotification und UIApplication.didBecomeActiveNotification
hordurh
140

Verwenden Sie das Notification Center in der viewDidLoad:Methode Ihres ViewControllers, um eine Methode aufzurufen und von dort aus das zu tun, was Sie in Ihrer viewWillAppear:Methode tun sollten . viewWillAppear:Direkt anrufen ist keine gute 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
quelle
9
Könnte eine gute Idee sein, den Beobachter deallocdann methodisch zu entfernen .
AncAinu
2
viewDidLoad ist nicht die beste Methode, um sich selbst als Beobachter hinzuzufügen. Wenn ja, entfernen Sie den Beobachter in viewDidUnload
Injectios
Was ist die beste Methode, um sich selbst als Beobachter hinzuzufügen?
Piotr Wasilewicz
Der Viewcontroller kann nicht nur eine Benachrichtigung beobachten, dh UIApplicationWillEnterForegroundNotification. Warum beides hören?
Zulkarnain Shah
34

viewWillAppear:animated:Eine meiner Meinung nach verwirrendste Methode in den iOS-SDKs wird in einer solchen Situation, dh beim Wechseln von Anwendungen, niemals aufgerufen. Diese Methode wird nur gemäß der Beziehung zwischen der Ansicht des Ansichtscontrollers und dem Fenster der Anwendung aufgerufen , dh die Nachricht wird nur dann an einen Ansichtscontroller gesendet, wenn ihre Ansicht im Fenster der Anwendung und nicht auf dem Bildschirm angezeigt wird.

Wenn Ihre Anwendung in den Hintergrund tritt, sind die obersten Ansichten des Anwendungsfensters für den Benutzer offensichtlich nicht mehr sichtbar. In der Perspektive Ihres Anwendungsfensters sind sie jedoch immer noch die obersten Ansichten und daher nicht aus dem Fenster verschwunden. Diese Ansichten verschwanden vielmehr, weil das Anwendungsfenster verschwand. Sie sind nicht verschwunden, weil sie vom Fenster verschwunden sind .

Wenn der Benutzer zu Ihrer Anwendung zurückkehrt, werden sie offensichtlich auf dem Bildschirm angezeigt, da das Fenster erneut angezeigt wird. Aber aus der Sicht des Fensters sind sie überhaupt nicht verschwunden. Daher erhalten die View Controller die viewWillAppear:animatedNachricht nie .

MHC
quelle
2
Darüber hinaus war -viewWillDisappear: animiert: Ein praktischer Ort zum Speichern des Status, da er beim Beenden der App aufgerufen wird. Es wird jedoch nicht aufgerufen, wenn die App im Hintergrund ausgeführt wird, und eine App im Hintergrund kann ohne Vorwarnung beendet werden.
tc.
6
Eine andere wirklich schlecht benannte Methode ist viewDidUnload. Sie würden denken, es war das Gegenteil von viewDidLoad, aber nein; Es wird nur aufgerufen, wenn eine Situation mit wenig Arbeitsspeicher aufgetreten ist, die zum Entladen der Ansicht geführt hat, und nicht jedes Mal, wenn die Ansicht zum Zeitpunkt der Freigabe tatsächlich entladen wird.
Occulus
Ich stimme @occulus absolut zu. viewWillAppear hat seine Entschuldigung, weil das (Art) Multitasking nicht vorhanden war, aber viewDidUnload definitiv einen besseren Namen haben könnte.
MHC
Für mich wird viewDidDisappear aufgerufen, wenn die App auf iOS7 im Hintergrund ausgeführt wird. Kann ich eine Bestätigung bekommen?
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
quelle
3

Ich versuche nur, es so einfach wie möglich zu machen, siehe Code unten:

- (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
quelle