アプリがバックグラウンドから戻ったときに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:をapplicationWillEnterForeground:から呼び出そうとさえしましたが、その時点で現在のビューコントローラがどれであるかを正確に示すことができません。

誰かがこれに対処する適切な方法を知っていますか?私は明白な解決策を見逃していると確信しています。


1
を使用applicationWillEnterForeground:して、アプリケーションがいつアクティブな状態になったかを判断する必要があります。
sudo rm -rf 2011年

私はそれを私の質問で試みていたと述べました。上記をご参照ください。アプリデリゲート内から現在のビューコントローラーを特定する方法を提供できますか?
フィリップウォルトン

必要に応じて、isMemberOfClassまたはを使用できisKindOfClassます。
sudo rm -rf 2011年

@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であるかを知るための特別な方法はありません。しかし、それを回避する方法を見つけることができます。たとえば、UIViewControllerがいつそこに表示されるかを確認するためのUINavigationControllerのデリゲートメソッドがあります。そのようなものを使用して、提示された最新のUIViewControllerを追跡できます。

更新

さまざまなビットに適切な自動サイズ変更マスクを使用してUIをレイアウトする場合、UIの「手動」レイアウトに対処する必要がないこともあります。


101
この解決策をありがとう。私は実際にUIApplicationDidBecomeActiveNotificationのオブザーバーを追加し、それは非常にうまく機能します。
ウェイン・リュー

2
これは確かに正しい答えです。ただし、「「現在の」UIViewController self.navigationController.topViewControllerであるかどうかをすぐに確認できる方法はありません」に応じて、効果的に提供するか、少なくともスタックの一番上にあるこのコードがビューコントローラーのメインスレッドで発生している場合は、現在のコード。(間違っている可能性がありますが、あまり遊んでいませんが、うまくいくようです。)
マシューフレデリック

appDelegate.rootViewControllerも動作しますが、それはを返す可能性UINavigationControllerがあり.topViewController、@ MatthewFrederickが言うように必要になります。
samson

7
UIApplicationDidBecomeActiveNotificationは正しくありません(すべての人々がそれを支持していますが)。アプリの起動時に(そしてアプリの起動時にのみ!)この通知は別の方法で呼び出されます - この通知はviewWillAppearに加えて呼び出されるため、この回答では2回呼び出されます。Appleはこれを正しく行うことを不必要に困難にしました-(2013年の時点で)ドキュメントはまだありません
アダム

1
私が思いついた解決策は、静的変数を持つクラスを使用することでした( '静的BOOL入りバックグラウンド;'次に、クラスメソッドのセッターとゲッターを追加します。アプリケーションDidEnterBackgroundでは、変数をtrueに設定します。次に、applicationDidBecomeActiveで、静的ブールをチェックします、そしてそれがtrueの場合、「doMyLayoutStuff」を実行して変数を「NO」にリセットします。これにより、viewDillAppearとapplicationDidBecomeActiveの衝突が回避され、メモリプレッシャーが原因でアプリケーションが終了した場合に、アプリケーションがバックグラウンドから入ったと見なされなくなります。
vejmartin 14

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
Swift

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 2014

2
自身をオブザーバーとして追加するのに、viewDidLoadは最善の方法ではありません。そうであれば、viewDidUnloadでオブザーバーを削除します
Injectios

自分にオブザーバーを追加する最善の方法は何ですか?
Piotr Wasilewicz 2017

ビューコントローラは、1つの通知、つまりUIApplicationWillEnterForegroundNotificationのみを監視することはできません。なぜ両方を聞くのですか?
zulkarnain shah 2017

34

viewWillAppear:animated:私の意見では、iOS SDKで最も混乱しやすいメソッドの1つは、このような状況、つまりアプリケーションの切り替えでは呼び出されません。このメソッドは、ビューコントローラーのビューとアプリケーションのウィンドウの関係に従ってのみ呼び出されます。つまり、ビューが画面ではなくアプリケーションのウィンドウに表示される場合にのみ、メッセージがビューコントローラーに送信されます。

アプリケーションがバックグラウンドになると、アプリケーションウィンドウの最上位のビューがユーザーに表示されなくなります。ただし、アプリケーションウィンドウの観点では、これらは依然として最上位のビューであるため、ウィンドウから消えることはありませんでした。むしろ、これらのビューは、アプリケーションウィンドウが消えたために消えました。彼らは窓から姿を消したので消えませんでした。

したがって、ユーザーがアプリケーションに戻ると、ウィンドウが再度表示されるため、画面に表示されているように見えます。しかし、ウィンドウの観点からは、それらはまったく消えていません。したがって、ビューコントローラーはviewWillAppear:animatedメッセージを取得しません。


2
また、-viewWillDisappear:animated:アプリの終了時に呼び出されるため、以前は状態を保存するのに便利な場所でした。ただし、アプリがバックグラウンドで実行されている場合は呼び出されず、バックグラウンドで実行されているアプリは警告なしに強制終了される可能性があります。
tc。

6
別の本当にひどい名前のメソッドはviewDidUnloadです。これはviewDidLoadの反対であると思いますが、違います。ビューがアンロードされる原因となるメモリ不足の状況が発生したときにのみ呼び出されます。割り当て解除時にビューが実際にアンロードされるたびに呼び出されるわけではありません。
オクルス

@occulusに完全に同意します。(一種の)マルチタスクがなかったため、viewWillAppearには言い訳がありますが、viewDidUnloadの方がより適切な名前である可能性があります。
MHC

私にとって、viewDidDisappearは、アプリがiOS7でバックグラウンド化されているときに呼び出されます。確認は取れますか?
Mike Kogan、

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


}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.