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

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

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

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

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

5 ответов

Решение

Метод 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, который был представлен.

Обновить

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

стриж

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

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

Используйте Центр уведомлений в 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");
}

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

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

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

Свифт 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
}

Просто попытайтесь сделать это как можно проще, см. Код ниже:

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


}

С SwiftUI это еще проще:

var body: some View {     
    Text("Hello World")
    .onReceive(NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)) { _ in
        print("Moving to background!")
    }
    .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
        print("Moving back to foreground!")
    }   
}
Другие вопросы по тегам