วิธีค้นหาคอนโทรลเลอร์มุมมองสูงสุดบน iOS


253

ฉันพบหลายกรณีในขณะนี้ซึ่งจะสะดวกในการค้นหาตัวควบคุมมุมมอง "ระดับบนสุด" (ตัวควบคุมที่รับผิดชอบมุมมองปัจจุบัน) แต่ก็ไม่พบวิธีที่จะทำได้

โดยทั่วไปความท้าทายคือ: ระบุว่าสิ่งหนึ่งกำลังดำเนินการในคลาสที่ไม่ใช่ตัวควบคุมมุมมอง (หรือมุมมอง) [และไม่มีที่อยู่ของมุมมองที่ใช้งานอยู่]และไม่ได้ผ่านที่อยู่ของตัวควบคุมมุมมองสูงสุด ( หรือว่าที่อยู่ของตัวควบคุมทิศทาง) เป็นไปได้หรือไม่ที่จะพบตัวควบคุมมุมมองนั้น (และถ้าเป็นเช่นนั้นได้อย่างไร)

หรือล้มเหลวนั้นเป็นไปได้หรือไม่ที่จะพบมุมมองสูงสุด?


คุณกำลังบอกว่ามันเป็นไปไม่ได้
Licks ร้อน

@ แดเนียลไม่ฉันกำลังบอกว่าดูเหมือนว่ารหัสของคุณสามารถใช้การออกแบบใหม่เพราะคุณไม่จำเป็นต้องรู้สิ่งนี้ นอกจากนี้แนวคิดของ "สูงสุด" จะใช้ได้เฉพาะในบริบทบางอย่างเท่านั้นและถึงแม้จะไม่เสมอไปก็ตาม
Dave DeLong

@Daniel ฉันได้อ่านคำถามของคุณผิด มีจำนวนมากของ ifs และ buts พยายามที่จะตอบคำถามนี้ ขึ้นอยู่กับโฟลว์ตัวควบคุมมุมมองของคุณ @ คำตอบของวิลเบอร์ควรเป็นจุดเริ่มต้นที่ดีในการติดตามมัน
Deepak Danduprolu

ทีนี้มาทำให้มันง่ายขึ้นสำหรับกรณีเฉพาะ ถ้าฉันต้องการเขียนโคลนของ UIAlertView ฉันจะทำอย่างไร โปรดทราบว่ามันสามารถทำงานได้ดีโดยไม่ถูกส่งผ่านความสามารถในการควบคุมแอดเดรสหรือมุมมองอื่น ๆ
เลียร้อน

4
@Daniel: การเพิ่ม UIWindow อันที่สองทำงานได้ดีสำหรับการซ้อนทับคล้ายมุมมองการแจ้งเตือน
Wilbur Vandrsmith

คำตอบ:


75

iOS 4 แนะนำคุณสมบัติ rootViewController บน UIWindow:

[UIApplication sharedApplication].keyWindow.rootViewController;

คุณจะต้องตั้งค่าด้วยตนเองหลังจากสร้างตัวควบคุมมุมมอง


155
วิลเบอร์นี้จะให้สิ่งที่ตรงกันข้ามกับสิ่งที่ op ขอ rootViewController เป็นคอนโทรลเลอร์มุมมองพื้นฐานมากกว่าส่วนใหญ่ด้านบน
m4rkk

3
m4rkk: "มากที่สุด" ขึ้นอยู่กับทิศทางที่คุณกำลังมองหา คอนโทรลเลอร์ใหม่ถูกเพิ่มไปด้านบน (เหมือนกอง) หรือล่าง (เหมือนต้นไม้) หรือไม่? ไม่ว่าในกรณีใด OP จะกล่าวถึงตัวควบคุมการนำทางว่าอยู่ด้านบนซึ่งหมายถึงมุมมองที่เพิ่มขึ้น
Wilbur Vandrsmith

50
Word„ top“ ใช้สำหรับตัวควบคุมมุมมองซึ่งมีภาพอยู่ด้านบน (เช่น-[UINavigationController topViewController]) จากนั้นก็มีคำว่า“ราก” ซึ่งเป็นรากของต้นไม้ (ชอบ-[UIWindow rootViewController].
Tricertops

13
@ImpurestClub ฉันไม่สามารถค้นหาได้ในเอกสารประกอบ Xcode ดูเหมือนจะไม่พบมัน
Drux

4
@adib ไม่เป็นของUINavigationController
David H

428

ฉันคิดว่าคุณต้องการการรวมกันของคำตอบที่ได้รับการยอมรับและของ @ fishstix

+ (UIViewController*) topMostController
{
    UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;

    while (topController.presentedViewController) {
        topController = topController.presentedViewController;
    }

    return topController;
}

Swift 3.0+

func topMostController() -> UIViewController? {
    guard let window = UIApplication.shared.keyWindow, let rootViewController = window.rootViewController else {
        return nil
    }

    var topController = rootViewController

    while let newTopController = topController.presentedViewController {
        topController = newTopController
    }

    return topController
}

4
นอกจากนี้คุณสามารถตรวจสอบUINavigationControllerและขอของtopViewControllerหรือแม้กระทั่งการตรวจสอบและขอUITabBarController selectedViewControllerสิ่งนี้จะให้ตัวควบคุมมุมมองที่คุณเห็นแก่ผู้ใช้ในปัจจุบัน
Tricertops

33
นี่เป็นโซลูชันที่ไม่สมบูรณ์เนื่องจากจะสำรวจเฉพาะลำดับชั้นของตัวควบคุมมุมมองที่นำเสนออย่างสุภาพเท่านั้นไม่ใช่ลำดับชั้นของ childViewControllers (ตามที่ใช้โดย UINavigationController, UITabBarController ฯลฯ )
สาหร่าย

3
นี่เป็นวิธีที่ดีในการสรุปการนำเสนอตัวควบคุมมุมมองโมดัลที่กลับสู่สถานะแอปพลิเคชันปัจจุบันในกรณีของฉันมันเป็นหน้าจอการป้อนรหัสผ่านหลังจากแอปพลิเคชันหมดเวลา ขอบคุณ!
erversteeg

11
@algal: ไม่จริง: UITabBarController, UINavigationController เป็นตัวควบคุมมุมมองสูงสุดในลำดับชั้นอยู่แล้ว ขึ้นอยู่กับสิ่งที่คุณต้องการจะทำกับ "ตัวควบคุมระดับสูงสุด" คุณอาจไม่ต้องการสำรวจพวกเขาเลยและเล่นซอกับเนื้อหาของพวกเขา ในกรณีของฉันมันคือการนำเสนอตัวควบคุม modal ด้านบนของทุกอย่างและเพื่อที่ฉันต้องได้รับ UINaviationController หรือ UITabBarController ไม่ใช่เนื้อหาของพวกเขา !!
Rick77

1
@ Rick77 ถ้าเป็นจริงความคิดเห็นเล็ก ๆ น้อย ๆ ของคุณที่ถูกฝังลงที่นี่ทำให้ไม่จำเป็นต้องมีการแก้ไขที่ซับซ้อนมากมายในคำตอบอื่น ๆ เนื่องจากไม่มีใครพูดถึงเรื่องนี้เลยฉันรู้สึกว่าฉันต้องขอให้คุณยืนยันว่ามันเป็นเรื่องจริง และถ้ามันเป็นสิ่งสำคัญที่มันควรจะเป็นคำตอบของตัวเองทั้งหมด เนื่องจากคำตอบอื่น ๆ ส่วนใหญ่ทำ backflips พยายามแก้ไขปัญหานี้ คุณจะช่วยชีวิต!
Le Mot Juiced

150

เพื่อให้คำตอบของ JonasG เสร็จสมบูรณ์(ผู้ที่ออกจากตัวควบคุมแถบของแท็บขณะสำรวจ) นี่คือเวอร์ชันของฉันในการส่งคืนตัวควบคุมมุมมองที่มองเห็นได้ในปัจจุบัน:

- (UIViewController*)topViewController {
    return [self topViewControllerWithRootViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}

- (UIViewController*)topViewControllerWithRootViewController:(UIViewController*)rootViewController {
    if ([rootViewController isKindOfClass:[UITabBarController class]]) {
        UITabBarController* tabBarController = (UITabBarController*)rootViewController;
        return [self topViewControllerWithRootViewController:tabBarController.selectedViewController];
    } else if ([rootViewController isKindOfClass:[UINavigationController class]]) {
        UINavigationController* navigationController = (UINavigationController*)rootViewController;
        return [self topViewControllerWithRootViewController:navigationController.visibleViewController];
    } else if (rootViewController.presentedViewController) {
        UIViewController* presentedViewController = rootViewController.presentedViewController;
        return [self topViewControllerWithRootViewController:presentedViewController];
    } else {
        return rootViewController;
    }
}

2
ดีใช่ฉันลืมเกี่ยวกับตัวควบคุม TabBar: P
JonasG

9
ไม่รวมchildViewControllers
เลิศ

ดูคำตอบของฉันด้านล่างซึ่งปรับปรุงคำตอบข้างต้นโดยการจัดการเคส @kleo ที่เหลือออกไปเช่น popovers ดูตัวควบคุมที่เพิ่มเข้ามาเป็นตัวอย่างของตัวควบคุมมุมมองอื่น ๆ ในขณะที่เดินทางข้าม
Rajesh

หากคุณกำลังใช้ return [self topViewControllerWithRootViewController: navigationController.visibleViewController];, visibleViewController จะส่งคืนตัวควบคุมมุมมองที่แสดง (IF ANY) แม้ว่ามันจะเป็น UIAlertController ก็ตาม สำหรับคนที่ต้องการหลีกเลี่ยงตัวควบคุม ui alert ให้ใช้ topViewController แทน visibleViewController
Johnykutty

เพียงเพิ่ม 50 เซ็นต์ของฉันไปที่นี้ - ฉันกำลังดิ้นรนเพื่อให้การทำงานนี้ในตัวควบคุมของฉันที่โหลด webView .. เหตุผลที่ฉันไม่สามารถทำงานนี้ได้เพราะมุมมองยังไม่พร้อม (ยังไม่เสร็จโหลด) และดังนั้นจึงมองไม่เห็น สิ่งนี้นำไปสู่สถานการณ์ที่การรับ topViewContoller ล้มเหลวเนื่องจาก UINavigationController พยายามรับ ViewController ที่มองเห็นได้ในขณะที่ยังไม่มี ViewController ที่มองเห็นได้ ดังนั้นหากใครประสบปัญหานี้ตรวจสอบให้แน่ใจว่ามุมมองของคุณเสร็จสิ้นการโหลดก่อนที่คุณจะโทรไปที่วิธีการ topViewController ข้างต้น
mbuster

52

เวอร์ชันที่ไม่ใช่แบบเรียกซ้ำทั้งหมดเพื่อดูแลสถานการณ์ต่าง ๆ :

  • ตัวควบคุมมุมมองกำลังนำเสนอมุมมองอื่น
  • ตัวควบคุมมุมมองเป็น UINavigationController
  • ตัวควบคุมมุมมองเป็น UITabBarController

Objective-C

 UIViewController *topViewController = self.window.rootViewController;
 while (true)
 {
     if (topViewController.presentedViewController) {
         topViewController = topViewController.presentedViewController;
     } else if ([topViewController isKindOfClass:[UINavigationController class]]) {
         UINavigationController *nav = (UINavigationController *)topViewController;
         topViewController = nav.topViewController;
     } else if ([topViewController isKindOfClass:[UITabBarController class]]) {
         UITabBarController *tab = (UITabBarController *)topViewController;
         topViewController = tab.selectedViewController;
     } else {
         break;
     }
 }

Swift 4+

extension UIWindow {
    func topViewController() -> UIViewController? {
        var top = self.rootViewController
        while true {
            if let presented = top?.presentedViewController {
                top = presented
            } else if let nav = top as? UINavigationController {
                top = nav.visibleViewController
            } else if let tab = top as? UITabBarController {
                top = tab.selectedViewController
            } else {
                break
            }
        }
        return top
    }
}

2
ฉันตั้งชื่อvisibleViewControllerเพื่อให้ชัดเจนว่ามันทำอะไร
Jonny

31

การเรียกใช้คอนโทรลเลอร์มุมมองด้านบนสุดสำหรับ Swift โดยใช้ส่วนขยาย

รหัส:

extension UIViewController {
    @objc func topMostViewController() -> UIViewController {
        // Handling Modal views
        if let presentedViewController = self.presentedViewController {
            return presentedViewController.topMostViewController()
        }
        // Handling UIViewController's added as subviews to some other views.
        else {
            for view in self.view.subviews
            {
                // Key property which most of us are unaware of / rarely use.
                if let subViewController = view.next {
                    if subViewController is UIViewController {
                        let viewController = subViewController as! UIViewController
                        return viewController.topMostViewController()
                    }
                }
            }
            return self
        }
    }
}

extension UITabBarController {
    override func topMostViewController() -> UIViewController {
        return self.selectedViewController!.topMostViewController()
    }
}

extension UINavigationController {
    override func topMostViewController() -> UIViewController {
        return self.visibleViewController!.topMostViewController()
    }
}

การใช้งาน:

UIApplication.sharedApplication().keyWindow!.rootViewController!.topMostViewController()

ยอดเยี่ยม - ขอบคุณมากสำหรับวิธีนี้ ต้องการ Subviews'trick! อีกครั้งขอบคุณมากคุณช่วยชีวิตฉันไว้
iKK

25

เพื่อให้คำตอบของ Eric เสร็จสมบูรณ์(ผู้ที่ออกจาก popovers ตัวควบคุมการนำทาง tabbarcontrollers ดูตัวควบคุมที่เพิ่มเป็นมุมมองไปยังตัวควบคุมมุมมองอื่น ๆ ในขณะที่แวะเวียน) นี่คือเวอร์ชันของฉันที่ส่งคืนตัวควบคุมมุมมองที่มองเห็นได้

================================================== ===================

- (UIViewController*)topViewController {
    return [self topViewControllerWithRootViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}

- (UIViewController*)topViewControllerWithRootViewController:(UIViewController*)viewController {
    if ([viewController isKindOfClass:[UITabBarController class]]) {
        UITabBarController* tabBarController = (UITabBarController*)viewController;
        return [self topViewControllerWithRootViewController:tabBarController.selectedViewController];
    } else if ([viewController isKindOfClass:[UINavigationController class]]) {
        UINavigationController* navContObj = (UINavigationController*)viewController;
        return [self topViewControllerWithRootViewController:navContObj.visibleViewController];
    } else if (viewController.presentedViewController && !viewController.presentedViewController.isBeingDismissed) {
        UIViewController* presentedViewController = viewController.presentedViewController;
        return [self topViewControllerWithRootViewController:presentedViewController];
    }
    else {
        for (UIView *view in [viewController.view subviews])
        {
            id subViewController = [view nextResponder];
            if ( subViewController && [subViewController isKindOfClass:[UIViewController class]])
            {
                if ([(UIViewController *)subViewController presentedViewController]  && ![subViewController presentedViewController].isBeingDismissed) {
                    return [self topViewControllerWithRootViewController:[(UIViewController *)subViewController presentedViewController]];
                }
            }
        }
        return viewController;
    }
}

================================================== ===================

และตอนนี้สิ่งที่คุณต้องทำเพื่อให้ได้ตัวควบคุมมุมมองส่วนใหญ่ด้านบนเรียกวิธีข้างต้นดังนี้

UIViewController *topMostViewControllerObj = [self topViewController];

ไม่พบ SplitViewController เช่นกัน?
apinho

21

คำตอบนี้รวมถึงchildViewControllersและรักษาการใช้งานที่สะอาดและอ่านได้

+ (UIViewController *)topViewController
{
    UIViewController *rootViewController = [UIApplication sharedApplication].keyWindow.rootViewController;

    return [rootViewController topVisibleViewController];
}

- (UIViewController *)topVisibleViewController
{
    if ([self isKindOfClass:[UITabBarController class]])
    {
        UITabBarController *tabBarController = (UITabBarController *)self;
        return [tabBarController.selectedViewController topVisibleViewController];
    }
    else if ([self isKindOfClass:[UINavigationController class]])
    {
        UINavigationController *navigationController = (UINavigationController *)self;
        return [navigationController.visibleViewController topVisibleViewController];
    }
    else if (self.presentedViewController)
    {
        return [self.presentedViewController topVisibleViewController];
    }
    else if (self.childViewControllers.count > 0)
    {
        return [self.childViewControllers.lastObject topVisibleViewController];
    }

    return self;
}

อัปเดตรหัสบางส่วนเนื่องจากแสดงตัวควบคุมว่าเป็นอย่างไรโดยย่อและเรียกคืนอีกครั้ง nik-kov-ios-developer.blogspot.ru/2016/12/…
Nik Kov

เฮ้มาเลย "topVisibleViewController" ของคุณอยู่ที่ไหน
สวรรค์

12

ฉันเพิ่งได้รับสถานการณ์นี้ในโครงการของฉันซึ่งต้องแสดงมุมมองการแจ้งเตือนไม่ว่าตัวควบคุมที่แสดงจะเป็นอะไรและเป็นประเภทใด (UINavigationController ตัวควบคุมแบบคลาสสิกหรือตัวควบคุมมุมมองแบบกำหนดเอง) เมื่อสถานะเครือข่ายเปลี่ยนไป

ดังนั้นฉันเพิ่งปล่อยรหัสของฉันซึ่งค่อนข้างง่ายและตามจริงโปรโตคอลเพื่อให้มีความยืดหยุ่นกับตัวควบคุมคอนเทนเนอร์ทุกประเภท ดูเหมือนว่าจะเกี่ยวข้องกับคำตอบสุดท้าย แต่ในวิธีที่ยืดหยุ่นมาก

คุณสามารถคว้ารหัสได้ที่นี่: PPTopMostController

และได้ตัวควบคุมส่วนใหญ่ที่ใช้

UIViewController *c = [UIViewController topMostController];

10

นี่คือการปรับปรุงคำตอบของ Eric:

UIViewController *_topMostController(UIViewController *cont) {
    UIViewController *topController = cont;

    while (topController.presentedViewController) {
        topController = topController.presentedViewController;
    }

    if ([topController isKindOfClass:[UINavigationController class]]) {
        UIViewController *visible = ((UINavigationController *)topController).visibleViewController;
        if (visible) {
            topController = visible;
        }
    }

    return (topController != cont ? topController : nil);
}

UIViewController *topMostController() {
    UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;

    UIViewController *next = nil;

    while ((next = _topMostController(topController)) != nil) {
        topController = next;
    }

    return topController;
}

_topMostController(UIViewController *cont) เป็นฟังก์ชั่นผู้ช่วย

ตอนนี้สิ่งที่คุณต้องทำคือโทรออกtopMostController()และควรคืน UIViewController อันดับต้น ๆ !


7
ฉันจะบอกว่าตั้งแต่ปี 1983 โปรดจำไว้ว่า Objective-C มี C ... การห่อรหัส ObjC ในฟังก์ชั่น C เป็นวิธีปฏิบัติทั่วไปดังนั้นใช่นี่คือรหัส Objective-C
JonasG

@JonasG สวัสดี Jonas ในสถานการณ์ใดที่คุณต้องการห่อ ObjC ใน C เพราะบางครั้งฉันเห็นฟังก์ชั่น C เช่นนี้และไม่สามารถแยกแยะการใช้งานได้ รหัสการห่อใน C มีประโยชน์ด้านประสิทธิภาพหรือไม่?
OzBoz

1
@OzBoz ในสถานการณ์ที่ไม่ชัดเจนในทันทีว่าselfควรใช้คลาสใด
adib

8

นี่คือสิ่งที่ฉันทำ ขอขอบคุณ @Stakenborg ที่ชี้ให้เห็นวิธีการข้ามการรับ UIAlertView ในฐานะผู้ควบคุมอันดับต้น ๆ

-(UIWindow *) returnWindowWithWindowLevelNormal
{
    NSArray *windows = [UIApplication sharedApplication].windows;
    for(UIWindow *topWindow in windows)
    {
        if (topWindow.windowLevel == UIWindowLevelNormal)
            return topWindow;
    }
    return [UIApplication sharedApplication].keyWindow;
}

-(UIViewController *) getTopMostController
{
    UIWindow *topWindow = [UIApplication sharedApplication].keyWindow;
    if (topWindow.windowLevel != UIWindowLevelNormal)
    {
        topWindow = [self returnWindowWithWindowLevelNormal];
    }

    UIViewController *topController = topWindow.rootViewController;
    if(topController == nil)
    {
        topWindow = [UIApplication sharedApplication].delegate.window;
        if (topWindow.windowLevel != UIWindowLevelNormal)
        {
            topWindow = [self returnWindowWithWindowLevelNormal];
        }
        topController = topWindow.rootViewController;
    }

    while(topController.presentedViewController)
    {
        topController = topController.presentedViewController;
    }

    if([topController isKindOfClass:[UINavigationController class]])
    {
        UINavigationController *nav = (UINavigationController*)topController;
        topController = [nav.viewControllers lastObject];

        while(topController.presentedViewController)
        {
            topController = topController.presentedViewController;
        }
    }

    return topController;
}

คุณควรหลีกเลี่ยงวิธีการตั้งชื่อเช่นเดียวกับgetSomething:ใน Objective-C สิ่งนี้มีความหมายพิเศษ (เพิ่มเติม: cocoadevcentral.com/articles/000082.php ) และคุณไม่ปฏิบัติตามข้อกำหนดเหล่านี้ในรหัสของคุณ
Vive

7
@implementation UIWindow (ส่วนขยาย)

- (UIViewController *) topMostController
{
    UIViewController * topController = [ตนเอง rootViewController];

    ในขณะที่ (topController.presentedViewController) {
        topController = topController.presentedViewController;
    }

    ส่งกลับ topController;
}

@end

ฉันไม่คิดว่าคุณจะพอใจเงื่อนไขที่ระบุไว้ในโพสต์ต้นฉบับ
เลียร้อน

7
- (UIViewController*)topViewController {
    return [self topViewControllerWithRootViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}

- (UIViewController*)topViewControllerWithRootViewController:(UIViewController*)rootViewController {
    if ([rootViewController isKindOfClass:[UITabBarController class]]) {
        UITabBarController* tabBarController = (UITabBarController*)rootViewController;
        return [self topViewControllerWithRootViewController:tabBarController.selectedViewController];
    } else if ([rootViewController isKindOfClass:[UINavigationController class]]) {
        UINavigationController* navigationController = (UINavigationController*)rootViewController;
        return [self topViewControllerWithRootViewController:navigationController.visibleViewController];
    } else if (rootViewController.presentedViewController) {
        UIViewController* presentedViewController = rootViewController.presentedViewController;
        return [self topViewControllerWithRootViewController:presentedViewController];
    } else {
        return rootViewController;
    }
}

ฉันใช้สิ่งนี้ แต่โปรดทราบว่ามันจะหยุดเมื่อมีตัวควบคุมมุมมองที่นำเสนอมากกว่าหนึ่งตัว
ชัคบอริส

7

สำหรับ Swift Version ล่าสุด:
สร้างไฟล์ตั้งชื่อUIWindowExtension.swiftและวางตัวอย่างต่อไปนี้:

import UIKit

public extension UIWindow {
    public var visibleViewController: UIViewController? {
        return UIWindow.getVisibleViewControllerFrom(self.rootViewController)
    }

    public static func getVisibleViewControllerFrom(vc: UIViewController?) -> UIViewController? {
        if let nc = vc as? UINavigationController {
            return UIWindow.getVisibleViewControllerFrom(nc.visibleViewController)
        } else if let tc = vc as? UITabBarController {
            return UIWindow.getVisibleViewControllerFrom(tc.selectedViewController)
        } else {
            if let pvc = vc?.presentedViewController {
                return UIWindow.getVisibleViewControllerFrom(pvc)
            } else {
                return vc
            }
        }
    }
}

func getTopViewController() -> UIViewController? {
    let appDelegate = UIApplication.sharedApplication().delegate
    if let window = appDelegate!.window {
        return window?.visibleViewController
    }
    return nil
}

ใช้งานได้ทุกที่เช่น:

if let topVC = getTopViewController() {

}

ฉันไม่ต้องการเปลี่ยนคำตอบของคุณมากเกินไป แต่จะแนะนำบางสิ่ง 1. เพิ่มการสนับสนุนสำหรับ UISplitViewController 2. ใช้switchแทนถ้าอื่น 3. ไม่แน่ใจว่าคุณต้องการฟังก์ชั่นแบบคงที่เช่นกันฉันคิดว่าคุณสามารถทำได้อย่างง่ายดายในระดับอินสแตนซ์แรกที่คุณประกาศ 4. น่าจะดีที่สุดที่จะไม่สร้างฟังก์ชั่นระดับโลกมากเกินไป แต่นั่นเป็นเรื่องของรสนิยม คุณสามารถใช้โค้ดหนึ่งบรรทัดเพื่อให้ได้ผลกระทบของฟังก์ชั่นทั่วโลก:UIApplication.sharedApplication().delegate?.window?.visibleViewController
Jordan Smith

7

ส่วนขยายอย่างง่ายสำหรับUIApplicationใน Swift:

บันทึก:

มันใส่ใจเกี่ยวกับmoreNavigationControllerภายในUITabBarController

extension UIApplication {

    class func topViewController(baseViewController: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController) -> UIViewController? {

        if let navigationController = baseViewController as? UINavigationController {
            return topViewController(navigationController.visibleViewController)
        }

        if let tabBarViewController = baseViewController as? UITabBarController {

            let moreNavigationController = tabBarViewController.moreNavigationController

            if let topViewController = moreNavigationController.topViewController where topViewController.view.window != nil {
                return topViewController(topViewController)
            } else if let selectedViewController = tabBarViewController.selectedViewController {
                return topViewController(selectedViewController)
            }
        }

        if let splitViewController = baseViewController as? UISplitViewController where splitViewController.viewControllers.count == 1 {
            return topViewController(splitViewController.viewControllers[0])
        }

        if let presentedViewController = baseViewController?.presentedViewController {
            return topViewController(presentedViewController)
        }

        return baseViewController
    }
}

การใช้งานง่าย:

if let topViewController = UIApplication.topViewController() {
    //do sth with top view controller
}

ใช่ใช่ใช่ - มีวิธีแก้ปัญหามากมายรอบ ๆ เว็บสำหรับการค้นหา topMostViewController แต่หากแอปของคุณมีแถบแท็บที่มีแท็บเพิ่มเติมคุณต้องจัดการกับสิ่งที่แตกต่างกันเล็กน้อย
Andy Obusek

7

UIViewControllerใช้ด้านล่างส่วนขยายที่จะคว้าปัจจุบันที่มองเห็นได้ ทำงานกับ Swift 4.0 และใหม่กว่า

Swift 4.0 และใหม่กว่า:

extension UIApplication {
    
    class func topViewController(_ viewController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
        if let nav = viewController as? UINavigationController {
            return topViewController(nav.visibleViewController)
        }
        if let tab = viewController as? UITabBarController {
            if let selected = tab.selectedViewController {
                return topViewController(selected)
            }
        }
        if let presented = viewController?.presentedViewController {
            return topViewController(presented)
        }
        return viewController
    }
}

วิธีใช้?

let objViewcontroller = UIApplication.topViewController()

ไม่ควรทำการทดสอบนี้presentedViewControllerก่อน, ก่อนUINavigationControllerและUITabBarControllerกรณี? มิฉะนั้นถ้าตัวควบคุมมุมมองถูกนำเสนออย่างสุภาพจากUINavigationControllerหรือUITabBarControllerจะไม่ถูกส่งคืนเป็นตัวควบคุมมุมมองด้านบนแม้ว่าจะเป็นตัวควบคุมมุมมองที่มองเห็นได้
ดึง

4

อีกโซลูชันของ Swift

func topController() -> UIViewController? {

    // recursive follow
    func follow(from:UIViewController?) -> UIViewController? {
        if let to = (from as? UITabBarController)?.selectedViewController {
            return follow(to)
        } else if let to = (from as? UINavigationController)?.visibleViewController {
            return follow(to)
        } else if let to = from?.presentedViewController {
            return follow(to)
        }
        return from
    }

    let root = UIApplication.sharedApplication().keyWindow?.rootViewController

    return follow(root)

}

4

ส่วนขยาย Swift 4.2


extension UIApplication {

    class func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
        if let navigationController = controller as? UINavigationController {
            return topViewController(controller: navigationController.visibleViewController)
        }
        if let tabController = controller as? UITabBarController {
            if let selected = tabController.selectedViewController {
                return topViewController(controller: selected)
            }
        }
        if let presented = controller?.presentedViewController {


            return topViewController(controller: presented)
        }
        return controller
    }
}

ใช้จากที่ใดก็ได้เช่น

 UIApplication.topViewController()?.present(yourController, animated: true, completion: nil)

หรือชอบ

 UIApplication.topViewController()?
                    .navigationController?
                    .popToViewController(yourController,
                                         animated: true)

พอดีกับคลาสใด ๆ เช่น UINavigationController, UITabBarController

สนุก!


1
@BalalBakhrom พูดว่า "Upvoted ฉันคิดว่าคำตอบของคุณดีที่สุดคุณไม่สามารถโทรโดยตรง topViewController () วิธีการคลาส UIApplication เป็นซิงเกิลตันใช้อินสแตนซ์ที่ชื่อว่า" shared " ในการแก้ไขที่ฉันโหวตให้ปฏิเสธ หากถูกต้องจริงโปรดคลิกที่นี่เพื่อเข้าสู่เมนูที่คุณสามารถอนุมัติการแก้ไขนั้นได้
wizzwizz4

3

นี่คือสิ่งที่ใช้ได้ผลสำหรับฉัน

ฉันพบว่าบางครั้งตัวควบคุมเป็นศูนย์บนหน้าต่างคีย์เนื่องจาก keyWindow เป็นระบบปฏิบัติการบางอย่างเช่นการแจ้งเตือน ฯลฯ

 + (UIViewController*)topMostController
 {
     UIWindow *topWndow = [UIApplication sharedApplication].keyWindow;
     UIViewController *topController = topWndow.rootViewController;

     if (topController == nil)
     {
         // The windows in the array are ordered from back to front by window level; thus,
         // the last window in the array is on top of all other app windows.
         for (UIWindow *aWndow in [[UIApplication sharedApplication].windows reverseObjectEnumerator])
         {
             topController = aWndow.rootViewController;
             if (topController)
                 break;
         }
     }

     while (topController.presentedViewController) {
         topController = topController.presentedViewController;
     }

     return topController;
 }

3

เมื่อได้คำตอบของ @ Eric คุณต้องระวังว่า keyWindow เป็นหน้าต่างที่คุณต้องการจริงๆ หากคุณพยายามใช้วิธีการนี้หลังจากแตะบางสิ่งในมุมมองการแจ้งเตือนคีย์วินโดวส์จะเป็นหน้าต่างการแจ้งเตือนจริง ๆ และนั่นจะทำให้เกิดปัญหากับคุณอย่างไม่ต้องสงสัย เรื่องนี้เกิดขึ้นกับฉันเมื่ออยู่ในป่าเมื่อต้องรับมือกับลิงก์ที่ลึกผ่านการแจ้งเตือนและทำให้ SIGABRT ไม่มีร่องรอยการติดตาม รวมตัวเมียที่จะทำการดีบั๊ก

นี่คือรหัสที่ฉันใช้ตอนนี้:

- (UIViewController *)getTopMostViewController {
    UIWindow *topWindow = [UIApplication sharedApplication].keyWindow;
    if (topWindow.windowLevel != UIWindowLevelNormal) {
        NSArray *windows = [UIApplication sharedApplication].windows;
        for(topWindow in windows)
        {
            if (topWindow.windowLevel == UIWindowLevelNormal)
                break;
        }
    }

    UIViewController *topViewController = topWindow.rootViewController;

    while (topViewController.presentedViewController) {
        topViewController = topViewController.presentedViewController;
    }

    return topViewController;
}

อย่าลังเลที่จะผสมสิ่งนี้กับรสชาติของการดึงตัวควบคุมมุมมองด้านบนที่คุณชอบจากคำตอบอื่น ๆ ของคำถามนี้


คุณคิดว่านี่เป็นวิธีการแก้ปัญหาที่สมบูรณ์หรือไม่? คำตอบอื่น ๆ อีกมากมายมีความซับซ้อนอย่างมากพยายามที่จะพิจารณากรณีขอบจำนวนมาก ฉันต้องการให้มันเป็นจริงมันเรียบง่ายและสง่างาม
Le Mot Juiced

ฉันไม่เคยมีปัญหากับมัน หากคุณไม่ได้ทำอะไรผิดปกติกับ nav nav ของคุณสิ่งนี้ควรใช้งานได้มิฉะนั้นโซลูชันอื่น ๆ จะจัดการกับกรณีที่ซับซ้อนมากขึ้น
Stakenborg

3

Alternative Swift solution:

static func topMostController() -> UIViewController {
    var topController = UIApplication.sharedApplication().keyWindow?.rootViewController
    while (topController?.presentedViewController != nil) {
        topController = topController?.presentedViewController
    }

    return topController!
}

3

โซลูชันนี้สมบูรณ์แบบที่สุด ใช้เวลาในการพิจารณา: UINavigationController UIPageViewController UITabBarController และคอนโทรลเลอร์มุมมองที่นำเสนอสูงสุดจากคอนโทรลเลอร์มุมมองด้านบน

ตัวอย่างนี้อยู่ใน Swift 3

มี 3 เกินพิกัด

//Get the topmost view controller for the current application.
public func MGGetTopMostViewController() -> UIViewController?  {

    if let currentWindow:UIWindow = UIApplication.shared.keyWindow {
        return MGGetTopMostViewController(fromWindow: currentWindow)
    }

    return nil
}

//Gets the topmost view controller from a specific window.
public func MGGetTopMostViewController(fromWindow window:UIWindow) -> UIViewController? {

    if let rootViewController:UIViewController = window.rootViewController
    {
        return MGGetTopMostViewController(fromViewController:  rootViewController)
    }

    return nil
}


//Gets the topmost view controller starting from a specific UIViewController
//Pass the rootViewController into this to get the apps top most view controller
public func MGGetTopMostViewController(fromViewController viewController:UIViewController) -> UIViewController {

    //UINavigationController
    if let navigationViewController:UINavigationController = viewController as? UINavigationController {
        let viewControllers:[UIViewController] = navigationViewController.viewControllers
        if navigationViewController.viewControllers.count >= 1 {
            return MGGetTopMostViewController(fromViewController: viewControllers[viewControllers.count - 1])
        }
    }

    //UIPageViewController
    if let pageViewController:UIPageViewController = viewController as? UIPageViewController {
        if let viewControllers:[UIViewController] = pageViewController.viewControllers {
            if viewControllers.count >= 1 {
                return MGGetTopMostViewController(fromViewController: viewControllers[0])
            }
        }
    }

    //UITabViewController
    if let tabBarController:UITabBarController = viewController as? UITabBarController {
        if let selectedViewController:UIViewController = tabBarController.selectedViewController {
            return MGGetTopMostViewController(fromViewController: selectedViewController)
        }
    }

    //Lastly, Attempt to get the topmost presented view controller
    var presentedViewController:UIViewController! = viewController.presentedViewController
    var nextPresentedViewController:UIViewController! = presentedViewController?.presentedViewController

    //If there is a presented view controller, get the top most prensentedViewController and return it.
    if presentedViewController != nil {
        while nextPresentedViewController != nil {

            //Set the presented view controller as the next one.
            presentedViewController = nextPresentedViewController

            //Attempt to get the next presented view controller
            nextPresentedViewController = presentedViewController.presentedViewController
        }
        return presentedViewController
    }

    //If there is no topmost presented view controller, return the view controller itself.
    return viewController
}

3

กระชับยังแก้ปัญหาที่ครอบคลุมในสวิฟท์ 4.2 คำนึงUINavigationControllers , UITabBarControllers , นำเสนอและเด็กมุมมองควบคุม:

extension UIViewController {
  func topmostViewController() -> UIViewController {
    if let navigationVC = self as? UINavigationController,
      let topVC = navigationVC.topViewController {
      return topVC.topmostViewController()
    }
    if let tabBarVC = self as? UITabBarController,
      let selectedVC = tabBarVC.selectedViewController {
      return selectedVC.topmostViewController()
    }
    if let presentedVC = presentedViewController {
      return presentedVC.topmostViewController()
    }
    if let childVC = children.last {
      return childVC.topmostViewController()
    }
    return self
  }
}

extension UIApplication {
  func topmostViewController() -> UIViewController? {
    return keyWindow?.rootViewController?.topmostViewController()
  }
}

การใช้งาน:

let viewController = UIApplication.shared.topmostViewController()

2

ทางออกที่ดีใน Swift นำไปใช้ใน AppDelegate

func getTopViewController()->UIViewController{
    return topViewControllerWithRootViewController(UIApplication.sharedApplication().keyWindow!.rootViewController!)
}
func topViewControllerWithRootViewController(rootViewController:UIViewController)->UIViewController{
    if rootViewController is UITabBarController{
        let tabBarController = rootViewController as! UITabBarController
        return topViewControllerWithRootViewController(tabBarController.selectedViewController!)
    }
    if rootViewController is UINavigationController{
        let navBarController = rootViewController as! UINavigationController
        return topViewControllerWithRootViewController(navBarController.visibleViewController)
    }
    if let presentedViewController = rootViewController.presentedViewController {
        return topViewControllerWithRootViewController(presentedViewController)
    }
    return rootViewController
}

1

ฉันคิดว่าคำตอบส่วนใหญ่ไม่ได้ใส่ใจอย่างสมบูรณ์UINavigationViewControllerดังนั้นฉันจึงจัดการกรณีการใช้งานนี้ด้วยการติดตั้งต่อไปนี้

+ (UIViewController *)topMostController {
    UIViewController * topController = [UIApplication sharedApplication].keyWindow.rootViewController;
    while (topController.presentedViewController || [topController isMemberOfClass:[UINavigationController class]]) {
        if([topController isMemberOfClass:[UINavigationController class]]) {
            topController = [topController childViewControllers].lastObject;
        } else {
            topController = topController.presentedViewController;
        }
    }

    return topController;
}

1

ฉันรู้ว่ามันสายมากและอาจซ้ำซ้อน แต่ต่อไปนี้เป็นข้อมูลโค้ดที่ฉันใช้งานได้สำหรับฉัน:

    static func topViewController() -> UIViewController? {
        return topViewController(vc: UIApplication.shared.keyWindow?.rootViewController)
    }

    private static func topViewController(vc:UIViewController?) -> UIViewController? {
        if let rootVC = vc {
            guard let presentedVC = rootVC.presentedViewController else {
                return rootVC
            }
            if let presentedNavVC = presentedVC as? UINavigationController {
                let lastVC = presentedNavVC.viewControllers.last
                return topViewController(vc: lastVC)
            }
            return topViewController(vc: presentedVC)
        }
        return nil
    }

0

วิธีนี้ใช้งานได้ดีสำหรับการค้นหามุมมองด้านบน viewController 1จากคอนโทรลรูทมุมมองใด ๆ

+ (UIViewController *)topViewControllerFor:(UIViewController *)viewController
{
    if(!viewController.presentedViewController)
        return viewController;
    return [MF5AppDelegate topViewControllerFor:viewController.presentedViewController];
}

/* View Controller for Visible View */

AppDelegate *app = [UIApplication sharedApplication].delegate;
UIViewController *visibleViewController = [AppDelegate topViewControllerFor:app.window.rootViewController]; 

0

ไม่แน่ใจว่าสิ่งนี้จะช่วยให้สิ่งที่คุณพยายามทำสำเร็จหรือไม่โดยการค้นหาตัวควบคุมมุมมองระดับบนสุด แต่ฉันพยายามนำเสนอตัวควบคุมมุมมองใหม่ แต่ถ้าตัวควบคุมรูทมุมมองของฉันมีกล่องโต้ตอบโมดอลอยู่ จะวนไปด้านบนของตัวควบคุมมุมมอง modal ทั้งหมดโดยใช้รหัสนี้:

UIViewController* parentController =[UIApplication sharedApplication].keyWindow.rootViewController;

while( parentController.presentedViewController &&
       parentController != parentController.presentedViewController )
{
    parentController = parentController.presentedViewController;
}

0

คุณสามารถค้นหาคอนโทรลเลอร์ที่มีผู้ดูมากที่สุดโดยใช้

NSArray *arrViewControllers=[[self navigationController] viewControllers];
UIViewController *topMostViewController=(UIViewController *)[arrViewControllers objectAtIndex:[arrViewControllers count]-1];

ยกเว้นว่าถ้าคุณอ่านคำถามจริงselfไม่มีnavigationControllerคุณสมบัติ
Hot Licks

0

วิธีแก้ปัญหาอื่น ๆ นั้นขึ้นอยู่กับห่วงโซ่การตอบกลับซึ่งอาจหรืออาจไม่ทำงานขึ้นอยู่กับสิ่งที่ผู้ตอบกลับคนแรกคือ:

  1. รับการตอบกลับครั้งแรกได้รับการตอบกลับแรก
  2. ได้รับ UIViewController ที่เกี่ยวข้องกับการที่ครั้งแรกตอบกลับ

ตัวอย่างรหัสเทียม:

+ (UIViewController *)currentViewController {
    UIView *firstResponder = [self firstResponder]; // from the first link above, but not guaranteed to return a UIView, so this should be handled more appropriately.
    UIViewController *viewController = [firstResponder viewController]; // from the second link above
    return viewController;
}

0

สวิฟท์:

extension UIWindow {

func visibleViewController() -> UIViewController? {
    if let rootViewController: UIViewController  = self.rootViewController {
        return UIWindow.getVisibleViewControllerFrom(rootViewController)
    }
    return nil
}

class func getVisibleViewControllerFrom(vc:UIViewController) -> UIViewController {
if vc.isKindOfClass(UINavigationController.self) {

    let navigationController = vc as UINavigationController
    return UIWindow.getVisibleViewControllerFrom( navigationController.visibleViewController)

} else if vc.isKindOfClass(UITabBarController.self) {

    let tabBarController = vc as UITabBarController
    return UIWindow.getVisibleViewControllerFrom(tabBarController.selectedViewController!)

} else {

    if let presentedViewController = vc.presentedViewController {

        return UIWindow.getVisibleViewControllerFrom(presentedViewController.presentedViewController!)

    } else {

        return vc;
    }
}
}

การใช้งาน:

 if let topController = window.visibleViewController() {
            println(topController)
        }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.