Mengapa viewWillAppear tidak dipanggil saat aplikasi kembali dari latar belakang?

280

Saya sedang menulis aplikasi dan saya perlu mengubah tampilan jika pengguna melihat aplikasi sambil berbicara di telepon.

Saya telah menerapkan metode berikut:

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    NSLog(@"viewWillAppear:");
    _sv.frame = CGRectMake(0.0, 0.0, 320.0, self.view.bounds.size.height);
}

Tapi itu tidak dipanggil ketika aplikasi kembali ke latar depan.

Saya tahu bahwa saya dapat menerapkan:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarFrameChanged:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];

tetapi saya tidak ingin melakukan ini. Saya lebih suka meletakkan semua informasi tata letak saya di metode viewWillAppear:, dan biarkan itu menangani semua skenario yang mungkin.

Saya bahkan sudah mencoba memanggil viewWillAppear: from applicationWillEnterForeground :, tapi saya tidak bisa menunjukkan dengan tepat yang mana merupakan view controller saat ini.

Adakah yang tahu cara yang tepat untuk menangani ini? Saya yakin saya kehilangan solusi yang jelas.

Philip Walton
sumber
1
Anda harus menggunakan applicationWillEnterForeground:untuk menentukan kapan aplikasi Anda telah memasuki kembali status aktif.
sudo rm -rf
Saya katakan saya mencoba itu dalam pertanyaan saya. Silakan lihat di atas. Bisakah Anda menawarkan cara untuk menentukan pengontrol tampilan saat ini dari dalam delegasi aplikasi?
Philip Walton
Anda dapat menggunakan isMemberOfClassatau isKindOfClass, tergantung pada kebutuhan Anda.
sudo rm -rf
@sudo rm -rf Bagaimana cara kerjanya? Apa yang akan dia panggil isKindOfClass?
occulus
@occulus: Ya ampun, saya hanya mencoba menjawab pertanyaannya. Pasti cara Anda melakukannya adalah cara untuk pergi.
sudo rm -rf

Jawaban:

202

Metode ini viewWillAppearharus diambil dalam konteks apa yang terjadi di aplikasi Anda sendiri, dan tidak dalam konteks aplikasi Anda ditempatkan di latar depan ketika Anda kembali ke sana dari aplikasi lain.

Dengan kata lain, jika seseorang melihat aplikasi lain atau melakukan panggilan telepon, kemudian beralih kembali ke aplikasi Anda yang sebelumnya di latar belakang, UIViewController Anda yang sudah terlihat ketika Anda meninggalkan aplikasi Anda 'tidak peduli' untuk berbicara - sejauh menyangkut, itu tidak pernah menghilang dan masih terlihat - dan viewWillAppeartidak disebut.

Saya sarankan untuk tidak menyebut viewWillAppeardiri Anda sendiri - itu memiliki makna khusus yang tidak boleh Anda ubah! Refactoring yang dapat Anda lakukan untuk mencapai efek yang sama mungkin sebagai berikut:

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [self doMyLayoutStuff:self];
}

- (void)doMyLayoutStuff:(id)sender {
    // stuff
}

Kemudian Anda juga memicu doMyLayoutStuffdari notifikasi yang sesuai:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(doMyLayoutStuff:) name:UIApplicationDidChangeStatusBarFrameNotification object:self];

Tidak ada cara di luar kotak untuk mengetahui UIViewController 'saat ini'. Tetapi Anda dapat menemukan cara-cara di sekitar itu, misalnya ada metode delegasi dari UINavigationController untuk mencari tahu kapan UIViewController disajikan di dalamnya. Anda bisa menggunakan hal itu untuk melacak UIViewController terbaru yang telah disajikan.

Memperbarui

Jika Anda tata letak UI dengan masker autoresizing yang sesuai pada berbagai bit, kadang-kadang Anda bahkan tidak perlu berurusan dengan meletakkan 'manual' dari UI Anda - itu hanya akan ditangani ...

occulus
sumber
101
Terima kasih atas solusi ini. Saya sebenarnya menambahkan pengamat untuk UIApplicationDidBecomeActiveNotification dan itu berfungsi dengan sangat baik.
Wayne Liu
2
Ini tentu jawaban yang benar. Dari catatan, bagaimanapun, dalam menanggapi "tidak ada cara untuk mengetahui mana yang 'UIViewController' saat ini", saya percaya bahwa self.navigationController.topViewControllersecara efektif menyediakannya, atau setidaknya yang ada di atas tumpukan, yang akan menjadi saat ini jika kode ini diaktifkan pada utas utama di view contoller. (Bisa jadi salah, belum sering bermain dengannya, tetapi tampaknya berhasil.)
Matthew Frederick
appDelegate.rootViewControllerakan bekerja juga, tetapi mungkin mengembalikan UINavigationController, dan kemudian Anda akan perlu .topViewControllerseperti yang dikatakan @MatthewFrederick.
samson
7
UIApplicationDidBecomeActiveNotification tidak benar (meskipun semua orang melakukan upvoting). Di awal Aplikasi (dan hanya saat aplikasi dimulai!) Pemberitahuan ini disebut berbeda - itu disebut sebagai tambahan untuk viewWillAppear, jadi dengan jawaban ini Anda akan mendapatkannya dipanggil dua kali. Apple membuatnya sulit untuk mendapatkan hak ini - dokumen masih hilang (pada 2013!).
Adam
1
Solusi yang saya buat adalah dengan menggunakan kelas dengan variabel statis ('static BOOL enterBackground;' kemudian saya menambahkan metode setter dan getter kelas. Dalam applicationDidEnterBackground, saya mengatur variabel ke true. Kemudian di applicationDidBecomeActive, saya memeriksa bool statis , dan jika itu benar, saya "doMyLayoutStuff" dan mengatur ulang variabel ke 'TIDAK'. Ini mencegah: viewWillAppear dengan tabrakan applicationDidBecomeActive, dan juga memastikan bahwa aplikasi tidak berpikir itu masuk dari latar belakang jika diakhiri karena tekanan memori.
vejmartin
197

Cepat

Jawaban singkat

Gunakan NotificationCenterpengamat daripada 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
}

Jawaban panjang

Untuk mengetahui kapan aplikasi kembali dari latar belakang, gunakan NotificationCenterpengamat daripada viewWillAppear. Berikut adalah contoh proyek yang menunjukkan peristiwa mana yang terjadi kapan. (Ini adalah adaptasi dari jawaban Objective-C ini .)

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

}

Pada saat pertama memulai aplikasi, urutan output adalah:

view did load
view will appear
did become active
view did appear

Setelah menekan tombol beranda dan kemudian membawa aplikasi kembali ke latar depan, urutan output adalah:

will enter foreground
did become active 

Jadi jika Anda awalnya mencoba menggunakan viewWillAppearmaka UIApplication.willEnterForegroundNotificationmungkin itu yang Anda inginkan.

Catatan

Pada iOS 9 dan yang lebih baru, Anda tidak perlu menghapus pengamat. Itu dokumentasi menyatakan:

Jika aplikasi Anda menargetkan iOS 9.0 dan yang lebih baru atau macOS 10.11 dan yang lebih baru, Anda tidak perlu membatalkan registrasi pengamat dalam deallocmetodenya.

Suragch
sumber
6
Di swift 4.2, nama notifikasi sekarang adalah UIApplication.willEnterForegroundNotification dan UIApplication.didBecomeActiveNotification
hordurh
140

Gunakan Pusat Pemberitahuan dalam viewDidLoad:metode ViewController Anda untuk memanggil metode dan dari sana lakukan apa yang seharusnya Anda lakukan dalam viewWillAppear:metode Anda . Menelepon viewWillAppear:langsung bukanlah pilihan yang baik.

- (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");
}
Manju
sumber
9
Bisa jadi ide yang bagus untuk menghapus pengamat dalam deallocmetode itu.
AncAinu
2
viewDidLoad bukan metode terbaik untuk menambahkan diri sebagai pengamat, jika demikian, hapus pengamat di viewDidUnload
Injectios
apa metode terbaik untuk menambahkan diri seorang pengamat?
Piotr Wasilewicz
Tidak dapat viewcontroller mengamati hanya satu pemberitahuan, yaitu, UIApplicationWillEnterForegroundNotification. Mengapa mendengarkan keduanya?
zulkarnain shah
34

viewWillAppear:animated:, salah satu metode yang paling membingungkan dalam SDK iOS menurut saya, tidak pernah terlibat dalam situasi seperti itu, yaitu, pengalihan aplikasi. Metode itu hanya dipanggil sesuai dengan hubungan antara tampilan pengontrol tampilan dan jendela aplikasi , yaitu, pesan dikirim ke pengontrol tampilan hanya jika pandangannya muncul di jendela aplikasi, bukan di layar.

Ketika aplikasi Anda berjalan di latar belakang, jelas tampilan paling atas dari jendela aplikasi tidak lagi terlihat oleh pengguna. Namun, dalam perspektif jendela aplikasi Anda, mereka masih merupakan tampilan teratas dan karenanya mereka tidak menghilang dari jendela. Sebaliknya, pandangan itu menghilang karena jendela aplikasi menghilang. Mereka tidak menghilang karena mereka menghilang dari jendela.

Karena itu, ketika pengguna beralih kembali ke aplikasi Anda, mereka jelas tampak muncul di layar, karena jendela itu muncul lagi. Tapi dari perspektif jendela, mereka belum menghilang sama sekali. Karenanya pengendali tampilan tidak pernah menerima viewWillAppear:animatedpesan.

MHC
sumber
2
Selain itu, -viewWillDisappear: animated: digunakan sebagai tempat yang nyaman untuk menyimpan status karena dipanggil saat keluar dari aplikasi. Itu tidak dipanggil saat aplikasi di latar belakang, dan aplikasi di latar belakang bisa dimatikan tanpa peringatan.
tc.
6
Metode lain yang benar-benar buruk bernama viewDidUnload. Anda akan berpikir itu kebalikan dari viewDidLoad, tetapi tidak; itu hanya dipanggil ketika ada situasi memori yang rendah yang menyebabkan tampilan diturunkan, dan tidak setiap kali tampilan sebenarnya diturunkan pada waktu dealloc.
occulus
Saya sangat setuju dengan @occulus. viewWillAppear memiliki alasan karena multitasking (semacam) tidak ada, tetapi viewDidUnload pasti bisa memiliki nama yang lebih baik.
MHC
Bagi saya viewDidDisappear IS dipanggil ketika aplikasi dilatar belakangi pada iOS7. Bisakah saya mendapatkan konfirmasi?
Mike Kogan
4

Swift 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
sumber
3

Hanya berusaha membuatnya semudah mungkin lihat kode di bawah ini:

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


}
Bingung
sumber