Dlaczego viewWillAppear nie jest wywoływany, gdy aplikacja wraca z tła?

279

Piszę aplikację i muszę zmienić widok, jeśli użytkownik patrzy na aplikację podczas rozmowy przez telefon.

Wdrożyłem następującą metodę:

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

Ale nie jest wywoływane, gdy aplikacja powraca na pierwszy plan.

Wiem, że mogę wdrożyć:

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

ale nie chcę tego robić. Wolę umieścić wszystkie informacje o moim układzie w metodzie viewWillAppear: i pozwolić, aby obsłużyły wszystkie możliwe scenariusze.

Próbowałem nawet wywołać viewWillAppear: from applicationWillEnterForeground :, ale nie wydaje mi się możliwe określenie, który jest obecnie kontrolerem widoku w tym momencie.

Czy ktoś wie, jak sobie z tym poradzić? Jestem pewien, że brakuje mi oczywistego rozwiązania.

Philip Walton
źródło
1
Należy użyć, applicationWillEnterForeground:aby określić, kiedy aplikacja ponownie weszła w stan aktywny.
sudo rm -rf
Powiedziałem, że próbuję tego w swoim pytaniu. Proszę odnieść się powyżej. Czy możesz zaoferować sposób ustalenia, który jest bieżącym kontrolerem widoku z poziomu aplikacji delegowanej?
Philip Walton,
Możesz użyć isMemberOfClasslub isKindOfClass, w zależności od potrzeb.
sudo rm -rf
@sudo rm -rf Jak to by wtedy działało? Czym on się nazywa jest KindOfClass?
occulus
@occulus: Dobroć wie, próbowałem tylko odpowiedzieć na jego pytanie. Na pewno Twój sposób na zrobienie tego jest właściwy.
sudo rm -rf

Odpowiedzi:

202

Metodę viewWillAppearnależy brać pod uwagę w kontekście tego, co dzieje się we własnej aplikacji, a nie w kontekście umieszczania aplikacji na pierwszym planie po przełączeniu się z innej aplikacji.

Innymi słowy, jeśli ktoś spojrzy na inną aplikację lub odbierze telefon, a następnie przełączy się z powrotem na twoją aplikację, która była wcześniej uruchomiona w tle, twój UIViewController, który był już widoczny, kiedy opuściłeś aplikację, „nie przejmuje się”, że tak powiem - jeśli o to chodzi, to nigdy nie zniknęło i jest nadal widoczne - i dlatego viewWillAppearnie jest nazywane.

Odradzam dzwonienie do viewWillAppearsiebie - ma określone znaczenie, którego nie powinieneś obalać! Refaktoryzacja, którą możesz zrobić, aby osiągnąć ten sam efekt, może wyglądać następująco:

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

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

Następnie uruchamiasz również doMyLayoutStuffz odpowiedniego powiadomienia:

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

Nawiasem mówiąc, nie ma gotowego sposobu na określenie, który jest „bieżącym” UIViewController. Ale można znaleźć sposoby na obejście tego, np. Istnieją metody delegowania UINavigationController do sprawdzania, kiedy jest prezentowany UIViewController. Możesz użyć czegoś takiego do śledzenia najnowszego UIViewController, który został zaprezentowany.

Aktualizacja

Jeśli układasz interfejsy użytkownika za pomocą odpowiednich masek automatycznej zmiany rozmiaru różnych bitów, czasami nawet nie musisz zajmować się „ręcznym” układaniem interfejsu - po prostu zajmuje się ...

okluzja
źródło
101
Dzięki za to rozwiązanie. Właściwie dodaję obserwatora dla UIApplicationDidBecomeActiveNotification i działa bardzo dobrze.
Wayne Liu,
2
To z pewnością poprawna odpowiedź. Warto jednak zauważyć, że w odpowiedzi na „nie ma gotowego sposobu na określenie, który jest„ bieżącym ”UIViewController”, uważam, że self.navigationController.topViewControllerskutecznie to zapewnia, a przynajmniej ten na górze stosu, który byłby bieżący, jeśli ten kod jest uruchamiany w głównym wątku w widoku kontrolera. (Może się mylić, nie bawiłem się zbytnio, ale wydaje się, że działa.)
Matthew Frederick
appDelegate.rootViewControllerteż będzie działać, ale może zwrócić a UINavigationController, a wtedy będziesz potrzebować, .topViewControllerjak mówi @MatthewFrederick.
samson,
7
UIApplicationDidBecomeActiveNotification jest niepoprawna (pomimo tego, że wszystkie osoby ją oceniają). Przy uruchamianiu aplikacji (i tylko przy uruchamianiu aplikacji!) To powiadomienie jest nazywane inaczej - jest wywoływane jako dodatek do viewWillAppear, więc dzięki tej odpowiedzi otrzymasz dwa razy. Apple sprawiło, że niepotrzebnie trudno było to naprawić - wciąż brakuje dokumentów (od 2013 roku!).
Adam,
1
Rozwiązaniem, które wymyśliłem, było użycie klasy ze zmienną statyczną („static BOOL wszedłBackground;”, a następnie dodałem metody ustawiające i pobierające metody klas. W applicationDidEnterBackground ustawiam zmienną na true. Następnie w applicationDidBecomeActive sprawdzam statyczne bool , a jeśli to prawda, wykonuję „doMyLayoutStuff” i resetuję zmienną na „NIE”. Zapobiega to: viewWillAppear z kolizją applicationDidBecomeActive, a także upewnia się, że aplikacja nie pomyśli, że została wprowadzona z tła, jeśli zostanie przerwana z powodu presji pamięci.
vejmartin
196

Szybki

Krótka odpowiedź

Użyj NotificationCenterraczej obserwatora niż 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
}

Długa odpowiedź

Aby dowiedzieć się, kiedy aplikacja wraca z tła, użyj NotificationCenterobserwatora zamiast viewWillAppear. Oto przykładowy projekt, który pokazuje, które zdarzenia mają miejsce, kiedy. (Jest to adaptacja tej odpowiedzi na Cel 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")
    }

}

Przy pierwszym uruchomieniu aplikacji kolejność wyjściowa to:

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

Po naciśnięciu przycisku Home, a następnie przeniesieniu aplikacji z powrotem na pierwszy plan, kolejność wyjściowa jest następująca:

will enter foreground
did become active 

Więc jeśli były pierwotnie próbuje użyć viewWillAppearwtedy UIApplication.willEnterForegroundNotificationjest chyba to, co chcesz.

Uwaga

Począwszy od iOS 9 i nowszych, nie musisz usuwać obserwatora. W dokumentacji czytamy:

Jeśli aplikacja jest przeznaczona na iOS 9.0 i nowsze lub macOS 10.11 i nowsze, nie musisz wyrejestrowywać obserwatora w tej deallocmetodzie.

Suragch
źródło
6
W szybkim 4.2 nazwa powiadomienia to teraz UIApplication.willEnterForegroundNotification i UIApplication.didBecomeActiveNotification
hordurh
140

Użyj Centrum powiadomień w viewDidLoad:metodzie ViewController, aby wywołać metodę, a następnie zrób to, co powinieneś zrobić w viewWillAppear:metodzie. viewWillAppear:Bezpośrednie dzwonienie nie jest dobrym rozwiązaniem.

- (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
źródło
9
W takim przypadku dobrym pomysłem byłoby usunięcie obserwatora deallocmetodą.
AncAinu
2
viewDidLoad nie jest najlepszą metodą dodawania siebie jako obserwatora, jeśli tak, usuń obserwatora z viewDidUnload
Injectios
jaka jest najlepsza metoda dodania siebie obserwatora?
Piotr Wasilewicz
Czy kontroler widoku nie może zaobserwować tylko jednego powiadomienia, tj. UIApplicationWillEnterForegroundNotification. Po co słuchać obu?
zulkarnain shah
34

viewWillAppear:animated:, jedna z najbardziej mylących metod w zestawach SDK systemu iOS, moim zdaniem, nigdy nie jest wywoływana w takiej sytuacji, tj. przy przełączaniu aplikacji. Ta metoda jest wywoływana tylko zgodnie z relacją między widokiem kontrolera widoku a oknem aplikacji , tj. Wiadomość jest wysyłana do kontrolera widoku tylko wtedy, gdy jego widok pojawia się w oknie aplikacji, a nie na ekranie.

Gdy aplikacja przechodzi w tło, najwyraźniej najwyższe widoki okna aplikacji nie są już widoczne dla użytkownika. Jednak w perspektywie okna aplikacji są one wciąż najwyższymi widokami i dlatego nie zniknęły z okna. Widoki te raczej zniknęły, ponieważ zniknęło okno aplikacji. Nie zniknęli, ponieważ zniknęli z okna.

Dlatego, gdy użytkownik przełącza się z powrotem na twoją aplikację, najwyraźniej wydają się pojawiać na ekranie, ponieważ okno pojawia się ponownie. Ale z perspektywy okna wcale nie zniknęły. Dlatego kontrolery widoku nigdy nie otrzymują viewWillAppear:animatedwiadomości.

MHC
źródło
2
Dodatkowo opcja -viewWillDisappear: animated: była wygodnym miejscem do zapisywania stanu, ponieważ jest wywoływana przy wyjściu z aplikacji. Nie jest wywoływana, gdy aplikacja jest w tle, a aplikację w tle można zabić bez ostrzeżenia.
tc.
6
Inną naprawdę źle nazwaną metodą jest viewDidUnload. Można by pomyśleć, że to przeciwieństwo viewDidLoad, ale nie; jest wywoływany tylko wtedy, gdy wystąpiła niska pamięć, która spowodowała rozładowanie widoku, a nie za każdym razem, gdy widok jest faktycznie zwalniany w czasie zwolnienia.
occulus
Absolutnie zgadzam się z @occulus. viewWillAppear ma swoją wymówkę, ponieważ nie było (tak jakby) wielozadaniowości, ale viewDidUnload zdecydowanie może mieć lepszą nazwę.
MHC,
Dla mnie viewDidDisappear JEST wywoływany, gdy aplikacja działa w tle na iOS7. Czy mogę otrzymać potwierdzenie?
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
źródło
3

Starając się maksymalnie ułatwić, zobacz poniższy kod:

- (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
źródło