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

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

Ответ 1

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

Обновление

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

Ответ 2

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

Ответ 3

Swift

Обновлен для Swift 3

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

Используйте наблюдатель NotificationCenter, а не viewWillAppear.

override func viewDidLoad() {
    super.viewDidLoad()

    // set observer for UIApplicationWillEnterForeground
    NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: .UIApplicationWillEnterForeground, object: nil)
}

// my selector that was defined above
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: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: NSNotification.Name.UIApplicationWillEnterForeground, object: nil)

    }

    override func viewWillAppear(_ animated: Bool) {
        print("view will appear")
    }

    override func viewDidAppear(_ animated: Bool) {
        print("view did appear")
    }

    // MARK: - Notification oberserver methods

    func didBecomeActive() {
        print("did become active")
    }

    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, то UIApplicationWillEnterForeground, вероятно, вы хотите.

Ответ 4

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

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

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

Ответ 5

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

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


}