Чому 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.