Почему viewWillAppear не вызывается, когда приложение возвращается из фона?

280

Я пишу приложение, и мне нужно изменить представление, если пользователь смотрит на приложение во время разговора по телефону.

Я реализовал следующий метод:

- (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: из applicationWillEnterForeground:, но я не могу точно определить, какой контроллер представления является текущим в этой точке.

Кто-нибудь знает правильный способ справиться с этим? Я уверен, что мне не хватает очевидного решения.

Филип Уолтон
источник
1
Вы должны использовать, applicationWillEnterForeground:чтобы определить, когда ваше приложение снова вошло в активное состояние.
sudo rm -rf
Я сказал, что пытался это в моем вопросе. Пожалуйста, обратитесь выше. Можете ли вы предложить способ определить, какой контроллер текущего представления находится в делегате приложения?
Филипп Уолтон
Вы можете использовать isMemberOfClassили isKindOfClass, в зависимости от ваших потребностей.
sudo rm -rf
@sudo rm -rf Как это будет работать? Что он собирается называть isKindOfClass?
Occulus
@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, который был представлен.

Обновить

Если вы размещаете пользовательский интерфейс с соответствующими масками автоматического изменения размера для различных битов, иногда вам даже не нужно иметь дело с «ручной» раскладкой вашего пользовательского интерфейса - с этим просто справляются ...

occulus
источник
101
Спасибо за это решение. Я фактически добавляю наблюдателя для UIApplicationDidBecomeActiveNotification, и он работает очень хорошо.
Уэйн Лю
2
Это, безусловно, правильный ответ. Однако следует отметить, что в ответ на «нет готового способа узнать, какой это« текущий »UIViewController», я полагаю, что это self.navigationController.topViewControllerэффективно обеспечивает его, или, по крайней мере, тот, который находится на вершине стека, который был бы текущий, если этот код запускает основной поток в контроллере представления. (Могу ошибаться, не играл с этим много, но, кажется, работает.)
Мэтью Фредерик
appDelegate.rootViewControllerтоже сработает, но может вернуть UINavigationController, и тогда вам понадобится, .topViewControllerкак говорит @MatthewFrederick.
Самсон
7
UIApplicationDidBecomeActiveNotification является неправильным (несмотря на все люди, голосующие против него). При запуске приложения (и только при запуске приложения) это уведомление вызывается по- разному - оно вызывается в дополнение к viewWillAppear, поэтому с этим ответом вы получите его дважды. Apple сделала излишне трудным получить это право - документы по-прежнему отсутствуют (по состоянию на 2013 год!).
Адам
1
Решение, которое я придумал, состояло в том, чтобы использовать класс со статической переменной ('static BOOL enterBackground;', ​​затем я добавляю методы установки и получения методов класса. В applicationDidEnterBackground я устанавливаю переменную в true. Затем в applicationDidBecomeActive я проверяю статический bool и, если это правда, я «doMyLayoutStuff» и сбрасываю переменную в «НЕТ». Это предотвращает: viewWillAppear с коллизией applicationDidBecomeActive, а также гарантирует, что приложение не думает, что оно вошло из фона, если оно было прекращено из-за нехватки памяти.
Веймартен
197

стриж

Короткий ответ

Используйте 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методе.

Suragch
источник
6
В swift 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
Каков наилучший способ добавить себя в качестве наблюдателя?
Петр Васильевич
Контроллер представления не может наблюдать только за одним уведомлением, то есть UIApplicationWillEnterForegroundNotification. Зачем слушать оба?
zulkarnain shah
34

viewWillAppear:animated:На мой взгляд, один из самых запутанных методов в iOS SDK никогда не используется в такой ситуации, т. е. переключение приложений. Этот метод вызывается только в соответствии с отношением между представлением контроллера представления и окном приложения , то есть сообщение отправляется в контроллер представления, только если его представление появляется в окне приложения, а не на экране.

Когда ваше приложение переходит в фоновый режим, очевидно, что самые верхние виды окна приложения больше не видны пользователю. Однако, с точки зрения окна вашего приложения, они по-прежнему являются самыми верхними представлениями, и поэтому они не исчезли из окна. Скорее эти представления исчезли, потому что исчезло окно приложения. Они не исчезли, потому что исчезли из окна.

Поэтому, когда пользователь переключается обратно на ваше приложение, они, очевидно, появляются на экране, потому что окно появляется снова. Но с точки зрения окна они совсем не исчезли. Поэтому контроллеры представления никогда не получают viewWillAppear:animatedсообщение.

MHC
источник
2
Кроме того, -viewWillDisappear: animated: раньше было удобным местом для сохранения состояния, поскольку оно вызывается при выходе из приложения. Однако оно не вызывается, когда приложение имеет фоновый режим, и фоновое приложение может быть убито без предупреждения.
тк.
6
Другой действительно плохо названный метод - viewDidUnload. Вы могли бы подумать, что это противоположность viewDidLoad, но нет; он вызывается только в случае нехватки памяти, которая вызвала выгрузку представления, а не каждый раз, когда представление фактически выгружается во время освобождения.
Occulus
Я абсолютно согласен с @occulus. viewWillAppear имеет свое оправдание, потому что (вроде) многозадачности не было, но viewDidUnload определенно может иметь лучшее имя.
MHC
Для меня viewDidDisappear вызывается, когда приложение работает на 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
}
aviran
источник
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");


}
ConfusedDeer
источник