ทำไม viewWillAppear ไม่ถูกเรียกเมื่อแอพกลับมาจากพื้นหลัง


280

ฉันกำลังเขียนแอพและฉันจำเป็นต้องเปลี่ยนมุมมองหากผู้ใช้กำลังดูแอปขณะคุยโทรศัพท์

ฉันใช้วิธีการต่อไปนี้แล้ว:

- (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

ฉันบอกว่าฉันกำลังลองสิ่งนั้นในคำถามของฉัน โปรดดูด้านบน คุณสามารถเสนอวิธีการตรวจสอบตัวควบคุมมุมมองปัจจุบันจากภายในตัวแทนแอพได้อย่างไร?
Philip Walton

คุณสามารถใช้isMemberOfClassหรือisKindOfClassขึ้นอยู่กับความต้องการของคุณ
sudo rm -rf

@sudo rm -rf มันจะทำงานอย่างไร? เขาจะเรียกใช้ isKindOfClass ว่าอย่างไร
occulus

@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 'ปัจจุบัน' อยู่ที่ใด แต่คุณสามารถหาวิธีรอบ ๆ เช่นมีวิธีการมอบหมายของ UINavigationController สำหรับการค้นหาเมื่อมีการนำเสนอ UIViewController นั้น คุณสามารถใช้สิ่งนี้เพื่อติดตาม UIViewController ล่าสุดที่นำเสนอ

ปรับปรุง

หากคุณจัดวาง UIs ด้วยมาสก์การปรับขนาดอัตโนมัติที่เหมาะสมในบิตต่าง ๆ บางครั้งคุณไม่จำเป็นต้องจัดการกับ 'การวางด้วยตนเอง' จาก UI ของคุณ - มันเพิ่งได้รับการจัดการ ...


101
ขอบคุณสำหรับการแก้ปัญหานี้ ฉันเพิ่มผู้สังเกตการณ์สำหรับ UIApplicationDidBecomeActiveNotification และทำงานได้ดีมาก
Wayne Liu

2
นี่เป็นคำตอบที่ถูกต้องอย่างแน่นอน อย่างไรก็ตามจากการตอบกลับไปยัง "ไม่มีทางที่จะบอกได้ว่าเป็น UIViewController 'ปัจจุบัน'" ฉันเชื่อว่าself.navigationController.topViewControllerมีประสิทธิภาพหรืออย่างน้อยก็ด้านบนของสแต็กซึ่งจะเป็น รหัสปัจจุบันถ้าโค้ดนี้เริ่มทำงานที่เธรดหลักในตัวควบคุมมุมมอง (อาจจะผิดไม่ได้เล่นกับมันมาก แต่ดูเหมือนว่าจะทำงาน)
แมทธิวเฟรเดอริ

appDelegate.rootViewControllerจะใช้งานได้เช่นกัน แต่อาจส่งคืนUINavigationControllerและคุณจะต้อง.topViewController@MatthewFrederick พูด
แซมซั่น

7
UIApplicationDidBecomeActiveNotification ไม่ถูกต้อง (แม้ทุกคนจะถอนออก) ในการเริ่มต้นแอพ (และเมื่อเริ่มแอพเท่านั้น!) การแจ้งเตือนนี้เรียกว่าแตกต่างกัน - มันถูกเรียกว่านอกเหนือไปจาก viewWillAppear ดังนั้นด้วยคำตอบนี้คุณจะได้รับการเรียกสองครั้ง แอปเปิ้ลทำให้ยากที่จะได้รับสิทธินี้โดยไม่จำเป็น - เอกสารยังคงหายไป (ตั้งแต่ปี 2013!)
อดัม

1
วิธีแก้ปัญหาที่ฉันเกิดขึ้นคือการใช้คลาสที่มีตัวแปรแบบคงที่ ('static BOOL enterBackground;' จากนั้นฉันเพิ่ม setters เมธอดและคลาส getters ใน applicationDidEnterBackground ฉันตั้งค่าตัวแปรเป็น true จากนั้นใน applicationDidBecomeActive ฉันตรวจสอบบูลแบบคงที่ และหากเป็นจริงฉัน "doMyLayoutStuff" และรีเซ็ตตัวแปรเป็น 'ไม่' สิ่งนี้จะป้องกัน: viewWillAppear ด้วย applicationDidBecomeActive collision และตรวจสอบให้แน่ใจว่าแอปพลิเคชันไม่คิดว่ามันเข้ามาจากพื้นหลังหากถูกยกเลิกเนื่องจากแรงกดดันหน่วยความจำ
vejmartin

196

รวดเร็ว

คำตอบสั้น ๆ

ใช้สังเกตการณ์มากกว่าNotificationCenterviewWillAppear

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
}

คำตอบที่ยาว

เพื่อหาเมื่อ app กลับมาจากพื้นหลังใช้สังเกตการณ์มากกว่าNotificationCenter viewWillAppearนี่คือโครงการตัวอย่างที่แสดงเหตุการณ์ที่เกิดขึ้นเมื่อ (นี่คือการปรับคำตอบวัตถุประสงค์ -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 4.2 ตอนนี้ชื่อการแจ้งเตือนคือ UIApplication.willEnterForegroundNotification และ UIApplication.didBecomeActiveNotification
hordurh

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

2
viewDidLoad ไม่ใช่วิธีที่ดีที่สุดในการเพิ่มตัวเองเป็นผู้สังเกตการณ์ถ้าเป็นเช่นนั้นให้ลบผู้สังเกตการณ์ใน viewDidUnload
Injectios

วิธีที่ดีที่สุดในการเพิ่มผู้สังเกตการณ์ด้วยตนเองคืออะไร?
Piotr Wasilewicz

ไม่สามารถดู viewcontroller สำหรับการแจ้งเตือนเพียงครั้งเดียวเช่น UIApplicationWillEnterForegroundNotification ฟังทั้งสองทำไม
zulkarnain shah

34

viewWillAppear:animated:หนึ่งในวิธีที่สับสนที่สุดใน iOS SDK ในความคิดของฉันจะไม่ถูกเรียกใช้ในสถานการณ์เช่นการเปลี่ยนแอปพลิเคชัน วิธีการนั้นเรียกใช้ตามความสัมพันธ์ระหว่างมุมมองของตัวควบคุมมุมมองและหน้าต่างของแอปพลิเคชันเท่านั้นเช่นข้อความจะถูกส่งไปยังตัวควบคุมมุมมองเฉพาะเมื่อมุมมองนั้นปรากฏบนหน้าต่างของแอปพลิเคชันไม่ใช่บนหน้าจอ

เมื่อแอปพลิเคชันของคุณเป็นแบ็คกราวน์ผู้ใช้จะไม่เห็นมุมมองด้านบนสุดของหน้าต่างแอปพลิเคชันอีกต่อไป ในมุมมองของหน้าต่างแอปพลิเคชันของคุณอย่างไรก็ตามมุมมองเหล่านี้ยังคงเป็นมุมมองสูงสุดดังนั้นจึงไม่ได้หายไปจากหน้าต่าง มุมมองเหล่านั้นหายไปเนื่องจากหน้าต่างแอปพลิเคชันหายไป พวกเขาไม่ได้หายไปเพราะหายไปจากหน้าต่าง

ดังนั้นเมื่อผู้ใช้สลับกลับไปที่แอปพลิเคชันของคุณพวกเขาดูเหมือนจะปรากฏบนหน้าจอเนื่องจากหน้าต่างปรากฏขึ้นอีกครั้ง แต่จากมุมมองของหน้าต่างพวกเขาไม่ได้หายไปเลย ดังนั้นตัวควบคุมมุมมองไม่เคยได้รับviewWillAppear:animatedข้อความ


2
นอกจากนี้ -viewWillDisappear: animated: เคยเป็นสถานที่ที่สะดวกในการบันทึกสถานะเนื่องจากถูกเรียกเมื่อออกจากแอป มันไม่ได้ถูกเรียกเมื่อแอปมีพื้นหลัง แต่และแอปที่มีพื้นหลังสามารถฆ่าได้โดยไม่มีการเตือน
tc

6
อีกวิธีที่มีชื่อไม่ดีจริงๆคือ viewDidUnload คุณคิดว่าตรงกันข้ามกับ viewDidLoad แต่ไม่ใช่ มันจะถูกเรียกก็ต่อเมื่อมีสถานการณ์หน่วยความจำเหลือน้อยซึ่งทำให้มุมมองยกเลิกการโหลดและไม่ใช่ทุกครั้งที่มีการยกเลิกการโหลดมุมมองในเวลา dealloc
occulus

ฉันเห็นด้วยอย่างยิ่งกับ @occulus viewWillAppear มีข้อแก้ตัวเพราะมัลติทาสกิ้ง (เรียงลำดับ) ไม่ได้อยู่ที่นั่น แต่ viewDidUnload อาจมีชื่อที่ดีกว่า
MHC

สำหรับฉัน viewDidDisappear IS จะถูกเรียกใช้เมื่อแอปมีพื้นหลังใน iOS7 ฉันจะได้รับการยืนยันหรือไม่
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

เพียงพยายามทำให้ง่ายที่สุดเท่าที่จะทำได้ดูรหัสด้านล่าง:

- (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.