Tại sao viewWillAppear không được gọi khi ứng dụng quay lại từ nền?


279

Tôi đang viết một ứng dụng và tôi cần thay đổi chế độ xem nếu người dùng đang nhìn vào ứng dụng trong khi nói chuyện trên điện thoại.

Tôi đã thực hiện phương pháp sau:

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

Nhưng nó không được gọi khi ứng dụng trở lại nền trước.

Tôi biết rằng tôi có thể thực hiện:

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

nhưng tôi không muốn làm điều này Thay vào đó, tôi muốn đặt tất cả thông tin bố cục của mình vào phương thức viewWillAppear: và để điều đó xử lý tất cả các tình huống có thể xảy ra.

Tôi thậm chí đã cố gắng gọi viewWillAppear: từ applicationWill EntryForeground:, nhưng dường như tôi không thể xác định chính xác bộ điều khiển xem hiện tại vào thời điểm đó.

Có ai biết cách thích hợp để đối phó với điều này? Tôi chắc chắn tôi đang thiếu một giải pháp rõ ràng.


1
Bạn nên sử dụng applicationWillEnterForeground:để xác định khi nào ứng dụng của bạn đã nhập lại trạng thái hoạt động.
sudo rm -rf

Tôi nói tôi đã thử điều đó trong câu hỏi của tôi. Vui lòng tham khảo ở trên. Bạn có thể cung cấp một cách để xác định bộ điều khiển xem hiện tại từ bên trong đại biểu ứng dụng không?
Philip Walton

Bạn có thể sử dụng isMemberOfClasshoặc isKindOfClass, tùy thuộc vào nhu cầu của bạn.
sudo rm -rf

@sudo rm -rf Làm thế nào mà nó hoạt động? Anh ta định gọi isKindOfClass là gì?
thỉnh thoảng

@occulus: Trời đất ơi, tôi chỉ đang cố trả lời câu hỏi của anh thôi. Để chắc chắn cách làm của bạn là cách để đi.
sudo rm -rf

Câu trả lời:


202

Phương pháp viewWillAppearnên được thực hiện trong bối cảnh những gì đang diễn ra trong ứng dụng của riêng bạn chứ không phải trong bối cảnh ứng dụng của bạn được đặt ở phía trước khi bạn chuyển trở lại từ ứng dụng khác.

Nói cách khác, nếu ai đó nhìn vào một ứng dụng khác hoặc nhận một cuộc gọi điện thoại, sau đó chuyển trở lại ứng dụng của bạn trước đó trên nền, UIViewContoder của bạn đã hiển thị khi bạn rời khỏi ứng dụng của mình 'không quan tâm' để nói - theo như nó liên quan, nó không bao giờ biến mất và nó vẫn hiển thị - và vì vậy viewWillAppearkhông được gọi.

Tôi khuyên bạn không nên tự gọi viewWillAppearmình - nó có một ý nghĩa cụ thể mà bạn không nên lật đổ! Việc tái cấu trúc bạn có thể làm để đạt được hiệu quả tương tự có thể như sau:

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

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

Sau đó, bạn cũng kích hoạt doMyLayoutStufftừ thông báo thích hợp:

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

Nhân tiện, không có cách nào khác để nói UIViewControll là 'hiện tại'. Nhưng bạn có thể tìm ra các cách xung quanh đó, ví dụ, có các phương thức ủy nhiệm của UINavestionControll để tìm ra khi nào UIViewContoder được trình bày trong đó. Bạn có thể sử dụng một thứ như vậy để theo dõi UIViewControll mới nhất đã được trình bày.

Cập nhật

Nếu bạn bố trí các UI với mặt nạ tự động thích hợp trên các bit khác nhau, đôi khi bạn thậm chí không cần phải xử lý 'thủ công' đặt ra khỏi UI của mình - nó sẽ bị xử lý ...


101
Cảm ơn giải pháp này. Tôi thực sự thêm trình quan sát cho UIApplicationDidBecomeActiveNotification và nó hoạt động rất tốt.
Wayne Liu

2
Đây chắc chắn là câu trả lời chính xác. Tuy nhiên, lưu ý rằng để đáp ứng với "không có cách nào khác để biết UIViewContoder" hiện tại là gì ", tôi tin rằng self.navigationController.topViewControllerviệc cung cấp nó một cách hiệu quả, hoặc ít nhất là trên đỉnh của ngăn xếp, sẽ là mã hiện tại nếu mã này đang bắn vào luồng chính trong chế độ xem. (Có thể sai, không chơi với nó nhiều, nhưng dường như hoạt động.)
Matthew Frederick

appDelegate.rootViewControllercũng sẽ hoạt động, nhưng nó có thể trả về a UINavigationController, và sau đó bạn sẽ cần .topViewControllernhư @MatthewFrederick nói.
samson

7
UIApplicationDidBecomeActiveNotification là không chính xác (mặc dù tất cả mọi người nâng cao nó). Khi bắt đầu ứng dụng (và chỉ khi bắt đầu ứng dụng!), Thông báo này được gọi khác - nó được gọi ngoài viewWillAppear, vì vậy với câu trả lời này, bạn sẽ nhận được nó được gọi hai lần. Apple đã làm cho nó trở nên khó khăn một cách không cần thiết để có được quyền này - các tài liệu vẫn còn thiếu (kể từ năm 2013!).
Adam

1
Giải pháp tôi đưa ra là sử dụng một lớp có biến tĩnh ('BOOL staticBackground;' sau đó tôi thêm setters và getters phương thức lớp. Trong applicationDid EntryBackground, tôi đặt biến thành true. Sau đó, trong applicationDidBecomeActive, tôi kiểm tra bool tĩnh và nếu đó là sự thật, tôi "doMyLayoutStuff" và đặt lại biến thành 'NO'. Điều này ngăn: viewWillAppear với applicationDidBecomeActive va chạm và cũng đảm bảo rằng ứng dụng không nghĩ rằng nó được nhập từ nền nếu bị chấm dứt do áp lực bộ nhớ.
vejmartin

195

Nhanh

Câu trả lời ngắn

Sử dụng một NotificationCenterngười quan sát hơn là 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
}

Câu trả lời dài

Để tìm hiểu khi nào một ứng dụng quay trở lại từ nền, hãy sử dụng một NotificationCenterngười quan sát hơn là viewWillAppear. Đây là một dự án mẫu cho thấy những sự kiện xảy ra khi nào. (Đây là bản phóng tác của câu trả lời Objective-C này .)

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

}

Khi bắt đầu ứng dụng lần đầu tiên, thứ tự đầu ra là:

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

Sau khi nhấn nút home và sau đó đưa ứng dụng trở lại nền trước, thứ tự đầu ra là:

will enter foreground
did become active 

Vì vậy, nếu ban đầu bạn đang cố gắng sử dụng viewWillAppearthì UIApplication.willEnterForegroundNotificationcó lẽ là những gì bạn muốn.

Ghi chú

Kể từ iOS 9 trở lên, bạn không cần phải xóa người quan sát. Các tài liệu nêu:

Nếu ứng dụng của bạn nhắm mục tiêu iOS 9.0 trở lên hoặc macOS 10.11 trở lên, bạn không cần hủy đăng ký người quan sát trong deallocphương thức của nó .


6
Trong swift 4.2, tên thông báo hiện là UIApplication.will
EntryForegroundNotification

140

Sử dụng Trung tâm thông báo trong viewDidLoad:phương thức ViewContoder của bạn để gọi một phương thức và từ đó thực hiện những gì bạn phải làm trong viewWillAppear:phương thức của mình . Gọi viewWillAppear:trực tiếp không phải là một lựa chọn tốt.

- (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
Có thể là một ý tưởng tốt để loại bỏ người quan sát trong deallocphương thức sau đó.
AncAinu

2
viewDidLoad không phải là phương pháp tốt nhất để thêm tự như quan sát, nếu như vậy, loại bỏ quan sát viên trong viewDidUnload
Injectios

phương pháp tốt nhất để thêm một người quan sát là gì?
Piotr Wasilewicz

Bộ điều khiển khung nhìn không thể quan sát chỉ một thông báo, ví dụ, UIApplicationWill EntryForegroundNotification. Tại sao nghe cả hai?
zulkarnain shah

34

viewWillAppear:animated:, theo tôi, một trong những phương pháp khó hiểu nhất trong SDK iOS, không bao giờ được gọi trong tình huống như vậy, tức là chuyển đổi ứng dụng. Phương thức đó chỉ được gọi theo mối quan hệ giữa chế độ xem của trình điều khiển chế độ xem và cửa sổ của ứng dụng , tức là, thông báo chỉ được gửi đến trình điều khiển chế độ xem nếu chế độ xem của nó xuất hiện trên cửa sổ của ứng dụng chứ không phải trên màn hình.

Khi ứng dụng của bạn chạy nền, rõ ràng các chế độ xem trên cùng của cửa sổ ứng dụng sẽ không còn hiển thị cho người dùng. Tuy nhiên, trong phối cảnh của cửa sổ ứng dụng của bạn, chúng vẫn là các chế độ xem cao nhất và do đó chúng không biến mất khỏi cửa sổ. Thay vào đó, những khung nhìn biến mất vì cửa sổ ứng dụng biến mất. Họ không biến mất vì họ biến mất khỏi cửa sổ.

Do đó, khi người dùng chuyển trở lại ứng dụng của bạn, rõ ràng họ dường như xuất hiện trên màn hình, vì cửa sổ xuất hiện lại. Nhưng từ góc nhìn của cửa sổ, chúng vẫn chưa biến mất. Do đó, bộ điều khiển xem không bao giờ nhận được viewWillAppear:animatedthông báo.


2
Ngoài ra, -viewWillDisappear: hoạt hình: từng là một nơi thuận tiện để lưu trạng thái vì nó được gọi khi thoát ứng dụng. Tuy nhiên, nó không được gọi khi ứng dụng chạy nền và ứng dụng chạy nền có thể bị giết mà không cần cảnh báo.
tc.

6
Một phương thức được đặt tên rất tệ là viewDidUnload. Bạn sẽ nghĩ nó trái ngược với viewDidLoad, nhưng không; nó chỉ được gọi khi có tình huống bộ nhớ thấp khiến chế độ xem không tải và không phải mỗi lần chế độ xem thực sự được tải vào thời gian dealloc.
thỉnh thoảng

Tôi hoàn toàn đồng ý với @occulus. viewWillAppear có lý do của nó bởi vì (loại) đa nhiệm không có ở đó, nhưng viewDidUnload chắc chắn có thể có một tên tốt hơn.
MHC

Đối với tôi, viewDidDisappear IS được gọi khi ứng dụng được chạy trên iOS7. Tôi có thể xác nhận không?
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
}

3

Chỉ cần cố gắng làm cho nó dễ dàng nhất có thể, xem mã dưới đây:

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


}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.