Чому viewWillAppear не дзвонить, коли додаток повертається з фону?


279

Я пишу програму, і мені потрібно змінити погляд, якщо користувач дивиться на додаток під час розмови по телефону.

Я реалізував такий метод:

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

Але це не називається, коли додаток повертається на перший план.

Я знаю, що можу реалізувати:

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

але я не хочу цього робити. Я набагато краще розмістити всю інформацію про макет у методі viewWillAppear: та дозволити цьому обробляти всі можливі сценарії.

Я навіть намагався зателефонувати viewWillAppear: from applicationWillEnterForeground:, але я не можу точно визначити, який є поточним контролером подання в цій точці.

Хтось знає правильний спосіб боротьби з цим? Я впевнений, що я пропускаю очевидне рішення.


1
Ви повинні використовувати, applicationWillEnterForeground:щоб визначити, коли ваша програма знову перейшла в активний стан.
sudo rm -rf

Я сказав, що намагаюсь у своєму питанні. Будь ласка, зверніться вище. Чи можете ви запропонувати спосіб визначити, який є поточний контролер подання з-за делегата програми?
Філіп Уолтон

Ви можете використовувати isMemberOfClassабо isKindOfClass, залежно від ваших потреб.
sudo rm -rf

@sudo rm -rf Як би це працювало тоді? На що він буде дзвонити isKindOfClass?
окулус

@occulus: Добресть знає, я просто намагався відповісти на його запитання. Напевно, ваш шлях робити це шлях.
sudo rm -rf

Відповіді:


202

Метод viewWillAppear слід застосовувати у контексті того, що відбувається у вашій власній програмі, а не в контексті того, як ваша програма ставиться на перший план, коли ви переходите до неї з іншого додатка.

Іншими словами, якщо хтось переглядає іншу програму або здійснює телефонний дзвінок, то повертається до вашого додатка, який раніше був у фоновому режимі, ваш UIViewController, який вже був видно, коли ви вийшли зі свого додатка, «не хвилює», так би мовити - наскільки це стосується, він ніколи не зникав і все ще видно - і так viewWillAppear не називається.

Рекомендую не дзвонити viewWillAppearсобі - це має конкретне значення, яке не варто підривати! Рефакторинг, який ви можете зробити для досягнення такого ж ефекту, може бути таким:

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

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

Потім також ви запускаєте doMyLayoutStuffвідповідне повідомлення:

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

Немає способу сказати, що саме є "поточним" UIViewController. Але ви можете знайти шляхи навколо цього, наприклад, існують методи делегування UINavigationController для з'ясування, коли в ньому представлений UIViewController. Ви можете використовувати таку річ, щоб відстежувати останній представлений UIViewController, який був представлений.

Оновлення

Якщо ви розміщуєте інтерфейси інтерфейсів з відповідними автоматичними масками для різних бітів, іноді вам навіть не потрібно мати справу з "ручним" закладанням вашого інтерфейсу - він просто вирішується ...


101
Дякую за це рішення. Я фактично додаю спостерігача за UIApplicationDidBecomeActiveNotification, і він працює дуже добре.
Уейн Лю

2
Це, безумовно, правильна відповідь. Зауважимо, проте, у відповідь на "немає жодного способу сказати, що таке" поточний "UIViewController", я вважаю, що це self.navigationController.topViewControllerефективно забезпечує його, або хоча б той, що знаходиться у верхній частині стека, який би був поточний, якщо цей код спрацьовує по головній нитці в контулері перегляду. (Можливо, помиляюся, не дуже багато грали з цим, але, здається, працює.)
Меттью Фредерік

appDelegate.rootViewControllerтакож буде працювати, але він може повернути a UINavigationController, і тоді вам знадобиться, .topViewControllerяк говорить @MatthewFrederick
Самсон

7
UIApplicationDidBecomeActiveNotification є невірним (незважаючи на те, що всі люди його підтримують). Під час запуску програми (і лише при запуску програми) це сповіщення називається по- різному - воно називається на додаток до viewWillAppear, тому з цією відповіддю ви отримаєте дзвінок двічі. Apple зробила це зайвим складно отримати це право - документи поки відсутні (станом на 2013 рік!).
Адам

1
Я придумав рішення використовувати клас зі статичною змінною ("статична BOOL введенаBackground;", потім я додаю методи класових сеттерів і getters. У applicationDidEnterBackground я встановлюю змінну на true. Потім у applicationDidBecomeActive я перевіряю статичний bool , і якщо це правда, я "doMyLayoutStuff" і скидаю змінну на "НІ". Це запобігає: viewWillAppear зі зіткненням applicationDidBecomeActive, а також гарантує, що програма не вважає, що вона вступила з фону, якщо вона припинена через тиск в пам'яті.
веймартін

196

Швидкий

Коротка відповідь

Більше використовуйте NotificationCenterспостерігача 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
}

Довга відповідь

Щоб дізнатися, коли додаток повертається з фону, NotificationCenterскоріше скористайтеся спостережником, а не viewWillAppear. Ось зразок проекту, який показує, які події трапляються коли. (Це адаптація цієї відповіді 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")
    }

}

Під час першого запуску програми порядок виводу:

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

Після натискання кнопки додому та повернення програми на перший план, порядок виводу:

will enter foreground
did become active 

Тож якщо ви спочатку намагалися використовувати, viewWillAppearто UIApplication.willEnterForegroundNotification, мабуть, те, що ви хочете.

Примітка

Станом на iOS 9 і пізніших версій вам не потрібно видаляти спостерігача. У документації зазначено:

Якщо ваша програма націлена на iOS 9.0 та новіші версії або macOS 10.11 та новіші версії, вам не потрібно скасовувати реєстрацію спостерігача за його deallocметодом.


6
Швидко 4.2, ім'я сповіщення тепер UIApplication.willEnterForegroundNotification та UIApplication.didBecomeActiveNotification
hordurh

140

Використовуйте Центр сповіщень у viewDidLoad:методі вашого ViewController, щоб викликати метод, а звідти зробіть те, що ви мали зробити у своєму viewWillAppear:методі. Телефонувати viewWillAppear:безпосередньо - не вдалий варіант.

- (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
deallocТоді може бути хорошою ідеєю видалити спостерігача методом.
AncAinu

2
viewDidLoad не найкращий спосіб додати себе як спостерігача, якщо так, видаліть спостерігача у viewDidUnload
Injectios

який найкращий метод додати себе спостерігачем?
Piotr Wasilewicz

Неможливо контролер перегляду спостерігати лише за одним сповіщенням, тобто UIApplicationWillEnterForegroundNotification. Навіщо слухати обох?
zulkarnain shah

34

viewWillAppear:animated:Один із найбільш заплутаних методів у SDK для iOS, на мою думку, ніколи не застосовується у такій ситуації, тобто переключення програм. Цей метод застосовується лише відповідно до зв'язку між поданням контролера перегляду та вікном програми.

Коли ваша програма перебуває у фоновому режимі, очевидно, що найвищі види вікна програми більше не видно користувачеві. Однак, з точки зору вікна програми, вони все ще є найвищими видами, і тому вони не зникли з вікна. Швидше, ці погляди зникли, оскільки зникло вікно програми. Вони не зникли , тому що вони зникли з вікна.

Тому, коли користувач повертається назад до вашої програми, вони, очевидно, здаються на екрані, оскільки вікно знову з’являється. Але з точки зору вікна вони зовсім не зникли. Тому контролери перегляду ніколи не отримують viewWillAppear:animatedповідомлення.


2
Крім того, -viewWillDisappear: анімований: раніше був зручним місцем для збереження стану, оскільки його викликають при виході програми. Однак це не викликається, коли додаток перебуває у фоновому режимі, і фонове додаток можна вбити без попередження.
тс.

6
Ще один дійсно погано названий метод - viewDidUnload. Ви б могли подумати, що це протилежність viewDidLoad, але ні; його називають лише тоді, коли ситуація з низькою пам'яттю спричинила розвантаження, і не кожен раз, коли перегляд насправді вивантажується під час трансляції.
окулус

Я абсолютно згоден з @occulus. viewWillAppear має своє виправдання, тому що (різновид багатозадачності там не було, але viewDidUnload безумовно міг би мати кращу назву.
MHC

Для мене viewDidDisappear IS викликається, коли додаток фоновий на iOS7. Чи можу я отримати підтвердження?
Майк Коган

4

Швидкий 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

Просто намагаючись зробити це максимально просто, див. Код нижче:

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


}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.