รับ UIViewController ที่แสดงปัจจุบันบนหน้าจอใน AppDelegate.m


126

กระแสUIViewControllerบนหน้าจอจำเป็นต้องตอบสนองต่อการแจ้งเตือนแบบพุชจาก APN โดยการตั้งค่ามุมมองป้ายบางส่วน แต่ฉันจะหาUIViewControllerวิธีการapplication:didReceiveRemoteNotification: จากAppDelegate.m?

ฉันพยายามใช้self.window.rootViewControllerเพื่อรับการแสดงผลปัจจุบันUIViewControllerอาจเป็นUINavigationViewControllerตัวควบคุมมุมมองหรือประเภทอื่น ๆ และฉันพบว่าvisibleViewControllerคุณสมบัติของUINavigationViewControllerสามารถใช้เพื่อรับUIViewControllerบนหน้าจอ แต่จะทำอย่างไรถ้าไม่ใช่UINavigationViewController?

ขอความช่วยเหลือใด ๆ ! รหัสที่เกี่ยวข้องมีดังต่อไปนี้

AppDelegate.m

...
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {

    //I would like to find out which view controller is on the screen here.

    UIViewController *vc = [(UINavigationViewController *)self.window.rootViewController visibleViewController];
    [vc performSelector:@selector(handleThePushNotification:) withObject:userInfo];
}
...

ViewControllerA.m

- (void)handleThePushNotification:(NSDictionary *)userInfo{

    //set some badge view here

}

คำตอบ:


99

คุณสามารถใช้สิ่งrootViewControllerนี้ได้เมื่อคอนโทรลเลอร์ของคุณไม่ใช่UINavigationController:

UIViewController *vc = self.window.rootViewController;

เมื่อคุณรู้จักตัวควบคุมมุมมองรูทแล้วก็ขึ้นอยู่กับว่าคุณสร้าง UI ของคุณอย่างไร แต่คุณอาจหาวิธีนำทางผ่านลำดับชั้นของคอนโทรลเลอร์ได้

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

แก้ไข:

หากคุณต้องการมุมมองที่สูงที่สุด(ไม่ใช่ตัวควบคุมมุมมอง) คุณสามารถตรวจสอบได้

[[[[UIApplication sharedApplication] keyWindow] subviews] lastObject];

แม้ว่ามุมมองนี้อาจมองไม่เห็นหรือแม้กระทั่งมุมมองย่อยบางส่วนของมุมมอง ...

อีกครั้งขึ้นอยู่กับ UI ของคุณ แต่อาจช่วยได้ ...


19
ปัญหานี้คือถ้ามุมมองที่มองเห็นไม่ได้เป็นของตัวควบคุมมุมมองรูท (ในกรณีของมุมมองโมดอลและอื่น ๆ )
Dima

ใช่ฉันทำ. แต่อาจเป็น UITabViewController ไม่มีวิธีโดยตรงในการรับ UIViewController บนหน้าจอหรือไม่?
lu Yuan

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

1
ตราบใดที่มูลค่ายังคงอยู่เสมอนั่นก็เป็นวิธีที่ดีสำหรับฉันเช่นกัน
Dima

4
ไม่มีวิธีโดยตรงในการเข้าถึงคอนโทรลเลอร์จากUIViewอินสแตนซ์ rootViewControllerคือไม่จำเป็นต้องควบคุมการแสดงอยู่ อยู่ที่ด้านบนสุดของลำดับชั้นการดู
Gingi

101

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

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

UIWindow + PazLabs.h (ไฟล์ส่วนหัว)

#import <UIKit/UIKit.h>

@interface UIWindow (PazLabs)

- (UIViewController *) visibleViewController;

@end

UIWindow + PazLabs.m (ไฟล์การนำไปใช้งาน)

#import "UIWindow+PazLabs.h"

@implementation UIWindow (PazLabs)

- (UIViewController *)visibleViewController {
    UIViewController *rootViewController = self.rootViewController;
    return [UIWindow getVisibleViewControllerFrom:rootViewController];
}

+ (UIViewController *) getVisibleViewControllerFrom:(UIViewController *) vc {
    if ([vc isKindOfClass:[UINavigationController class]]) {
        return [UIWindow getVisibleViewControllerFrom:[((UINavigationController *) vc) visibleViewController]];
    } else if ([vc isKindOfClass:[UITabBarController class]]) {
        return [UIWindow getVisibleViewControllerFrom:[((UITabBarController *) vc) selectedViewController]];
    } else {
        if (vc.presentedViewController) {
            return [UIWindow getVisibleViewControllerFrom:vc.presentedViewController];
        } else {
            return vc;
        }
    }
}

@end

เวอร์ชัน Swift

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

2
ฉันจะใช้สิ่งนี้กับเวอร์ชันที่รวดเร็วได้อย่างไร
Vijay Singh Rana

2
ฉันไม่เข้าใจคำถามของคุณ คัดลอกและวางภายในรหัสของคุณ
zirinisp

VC คอนเทนเนอร์แบบกำหนดเองล่ะ
Mingming

@ มิงมิงไม่ควรยากที่จะเพิ่มส่วนพิเศษหากจะตรวจสอบว่า VC คอนเทนเนอร์ที่กำหนดเอง (ในเมธอด getVisibielController) และหากเป็นเช่นนั้นให้ส่งคืนคอนโทรลเลอร์ "ที่มองเห็นได้" ซึ่งโดยปกติจะเป็น vc.childControllers.lastObject สำหรับกำหนดเองส่วนใหญ่ การใช้งานคอนเทนเนอร์ VC (ฉันคิดว่า) แต่จะขึ้นอยู่กับวิธีการใช้งาน
gadu

1
ฉันเพิ่งโพสต์คำตอบด้วยวิธีการเดียวกับในคำตอบนี้ยกเว้นไวยากรณ์ที่อัปเดต: โดยใช้เคสสวิตช์และเป็นไปตามหลักการตั้งชื่อ Swift 3: stackoverflow.com/a/42486823/3451975
Jeehut

43

ส่วนขยายที่เรียบง่ายสำหรับUIA แอพพลิเคชั่นUITabBarControllerใน Swift (ใส่ใจมากยิ่งขึ้น NavigationController ภายในบน iPhone) :

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

        if let nav = base as? UINavigationController {
            return topViewController(base: nav.visibleViewController)
        }

        if let tab = base as? UITabBarController {
            let moreNavigationController = tab.moreNavigationController

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

        if let presented = base?.presentedViewController {
            return topViewController(base: presented)
        }

        return base
    }
}

ใช้งานง่าย:

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

ทำงานได้อย่างสมบูรณ์แบบ :-)

อัปเดตเพื่อทำความสะอาดรหัส:

extension UIViewController {
    var top: UIViewController? {
        if let controller = self as? UINavigationController {
            return controller.topViewController?.top
        }
        if let controller = self as? UISplitViewController {
            return controller.viewControllers.last?.top
        }
        if let controller = self as? UITabBarController {
            return controller.selectedViewController?.top
        }
        if let controller = presentedViewController {
            return controller.top
        }
        return self
    }
}

1
สิ่งนี้ดูเหมือนจะเป็นรหัสสำหรับ Swift 2.x. Swift 3.x ไม่มี "ที่ไหน" อีกต่อไป นอกจากนี้ "sharedApplication ()" ตอนนี้เป็น "shared" ไม่ใช่เรื่องใหญ่. ใช้เวลาอัปเดตเพียงนาทีเดียว อาจเป็นการดีที่จะพูดถึงว่ามันใช้การเรียกซ้ำ นอกจากนี้การเรียกไปยัง topViewController แต่ละครั้งควรต้องมีคำนำหน้า "base:"
Jeff Muir

37

คุณสามารถโพสต์การแจ้งเตือนผ่าน NSNotificationCenter สิ่งนี้ช่วยให้คุณจัดการกับสถานการณ์ต่างๆที่การข้ามลำดับชั้นของตัวควบคุมมุมมองอาจเป็นเรื่องยุ่งยากตัวอย่างเช่นเมื่อมีการนำเสนอโมดอลเป็นต้น

เช่น,

// MyAppDelegate.h
NSString * const UIApplicationDidReceiveRemoteNotification;

// MyAppDelegate.m
NSString * const UIApplicationDidReceiveRemoteNotification = @"UIApplicationDidReceiveRemoteNotification";

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {

    [[NSNotificationCenter defaultCenter]
     postNotificationName:UIApplicationDidReceiveRemoteNotification
     object:self
     userInfo:userInfo];
}

ใน View Controllers แต่ละตัวของคุณ:

-(void)viewDidLoad {
    [[NSNotificationCenter defaultCenter] 
      addObserver:self
      selector:@selector(didReceiveRemoteNotification:)                                                  
      name:UIApplicationDidReceiveRemoteNotification
      object:nil];
}

-(void)viewDidUnload {
    [[NSNotificationCenter defaultCenter] 
      removeObserver:self
      name:UIApplicationDidReceiveRemoteNotification
      object:nil];
}

-(void)didReceiveRemoteNotification:(NSDictionary *)userInfo {
    // see http://stackoverflow.com/a/2777460/305149
   if (self.isViewLoaded && self.view.window) {
      // handle the notification
   }
}

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


1
addObserver:barข้างในคืออะไรviewDidLoad? ฉันต้องแทนที่ด้วยselfหรือไม่?
CainaSouza

ขอบคุณที่ชี้ให้เห็น - ควรเป็นตัวเอง ฉันจะอัปเดตคำตอบ
Aneil Mallavarapu

เกิดข้อผิดพลาดขณะรับคีย์ทั้งหมดจาก userInfo .. มีความคิดอย่างไร [NSConcreteNotification allKeys]: ตัวเลือกที่ไม่รู้จักถูกส่งไปยังอินสแตนซ์ 0x1fd87480 2013-07-05 16: 10: 36.469 Providence [2961: 907] *** การยุติแอปเนื่องจากข้อยกเว้น "NSInvalidArgumentException ที่ไม่ถูกตรวจจับ" เหตุผล: "- [NSConcreteNotification allKeys]: ไม่รู้จัก ตัวเลือกถูกส่งไปยังอินสแตนซ์ 0x1fd87480 '
Awais Tariq

@AwaisTariq - อืม - ฉันเดาว่าออบเจ็กต์ที่ iOS ส่งไปยัง didReceiveRemoteNotification ไม่ใช่ NSDictionary ตามที่อินเทอร์เฟซระบุ
Aneil Mallavarapu

จะเกิดอะไรขึ้นถ้าผู้ใช้ยังไม่ได้ไปยังคลาสผู้สังเกตการณ์ของคุณ : /
halbano

15

รหัส

นี่คือแนวทางในการใช้ไวยากรณ์ของสวิตช์เคสที่ยอดเยี่ยมในSwift 3/4/5 :

extension UIWindow {
    /// Returns the currently visible view controller if any reachable within the window.
    public var visibleViewController: UIViewController? {
        return UIWindow.visibleViewController(from: rootViewController)
    }

    /// Recursively follows navigation controllers, tab bar controllers and modal presented view controllers starting
    /// from the given view controller to find the currently visible view controller.
    ///
    /// - Parameters:
    ///   - viewController: The view controller to start the recursive search from.
    /// - Returns: The view controller that is most probably visible on screen right now.
    public static func visibleViewController(from viewController: UIViewController?) -> UIViewController? {
        switch viewController {
        case let navigationController as UINavigationController:
            return UIWindow.visibleViewController(from: navigationController.visibleViewController ?? navigationController.topViewController)

        case let tabBarController as UITabBarController:
            return UIWindow.visibleViewController(from: tabBarController.selectedViewController)

        case let presentingViewController where viewController?.presentedViewController != nil:
            return UIWindow.visibleViewController(from: presentingViewController?.presentedViewController)

        default:
            return viewController
        }
    }
}

แนวคิดพื้นฐานเหมือนกับในคำตอบของ zirinisp เพียงแค่ใช้ Swift 3+ ที่คล้ายกับไวยากรณ์


การใช้

คุณอาจต้องการสร้างไฟล์ชื่อUIWindowExtension.swift. ตรวจสอบให้แน่ใจว่ามีimport UIKitคำสั่งตอนนี้คัดลอกรหัสส่วนขยายด้านบนคัดลอกรหัสข้างต้นส่วนขยาย

ในด้านการโทรสามารถใช้ได้โดยไม่ต้องมีตัวควบคุมมุมมองเฉพาะ :

if let visibleViewCtrl = UIApplication.shared.keyWindow?.visibleViewController {
    // do whatever you want with your `visibleViewCtrl`
}

หรือถ้าคุณทราบว่าตัวควบคุมมุมมองที่มองเห็นของคุณสามารถเข้าถึงได้จากตัวควบคุมมุมมองเฉพาะ :

if let visibleViewCtrl = UIWindow.visibleViewController(from: specificViewCtrl) {
    // do whatever you want with your `visibleViewCtrl`
}

ฉันหวังว่ามันจะช่วยได้!


กรณีที่สามจะผิดพลาดเนื่องจากการเรียกซ้ำที่ไม่สิ้นสุด การแก้ไขคือการเปลี่ยนชื่อ vc เป็นpresentingViewControllerและส่งผ่านpresentingViewController.presentedViewControllerเป็นพารามิเตอร์ไปยังเมธอดแบบเรียกซ้ำ
Ikhsan Assaat

ฉันไม่ค่อยเข้าใจขออภัย คุณหมายถึงUIWindow.visibleViewController(from: presentedViewController)ควรจะเป็นUIWindow.visibleViewController(from: presentingViewController.presentedViewController)?
Jeehut

ถูกต้องpresentedViewControllerและviewControllerเป็นวัตถุเดียวกันและจะเรียกใช้เมธอดด้วยตัวของมันเองจนกว่าสแต็กจะล้นออกมา ก็จะเป็น case let presentingViewController where viewController?.presentedViewController != nil: return UIWindow.visibleViewController(from: presentingViewController.presentedViewController)
Ikhsan Assaat

1
วิธีนี้ใช้ได้ผลเมื่อคนอื่นไม่ทำ คุณควรอัปเดตเป็น Swift 5 โดยพื้นฐานแล้วไม่มีการเปลี่ยนแปลง เพียงอัปเดตส่วนหัวสำหรับคำตอบของคุณ
TM Lynch

14

ฉันพบว่า iOS 8 ทำให้ทุกอย่างแย่ลง ใน iOS 7 มีUITransitionViewลำดับชั้นมุมมองใหม่เมื่อใดก็ตามที่คุณมีการนำเสนอแบบUINavigationControllerแยกส่วน อย่างไรก็ตามนี่คือรหัสของฉันที่พบว่าได้รับ VC อันดับต้น ๆ โทรgetTopMostViewControllerควรกลับ VC presentViewController:animated:completionว่าคุณควรจะสามารถที่จะส่งข้อความเช่น มีจุดประสงค์เพื่อให้คุณได้รับ VC ที่คุณสามารถใช้เพื่อนำเสนอ VC แบบโมดอลได้ดังนั้นจึงมักจะหยุดและส่งคืนในคลาสคอนเทนเนอร์เช่นเดียวUINavigationControllerกับ VC ที่อยู่ในนั้น ไม่ควรยากที่จะดัดแปลงโค้ดเพื่อทำเช่นนั้นด้วย ฉันได้ทดสอบโค้ดนี้ในสถานการณ์ต่างๆใน iOS 6, 7 และ 8 โปรดแจ้งให้เราทราบหากคุณพบข้อบกพร่อง

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

    for (UIView *subView in [window subviews])
    {
        UIResponder *responder = [subView nextResponder];

        //added this block of code for iOS 8 which puts a UITransitionView in between the UIWindow and the UILayoutContainerView
        if ([responder isEqual:window])
        {
            //this is a UITransitionView
            if ([[subView subviews] count])
            {
                UIView *subSubView = [subView subviews][0]; //this should be the UILayoutContainerView
                responder = [subSubView nextResponder];
            }
        }

        if([responder isKindOfClass:[UIViewController class]]) {
            return [self topViewController: (UIViewController *) responder];
        }
    }

    return nil;
}

+ (UIViewController *) topViewController: (UIViewController *) controller
{
    BOOL isPresenting = NO;
    do {
        // this path is called only on iOS 6+, so -presentedViewController is fine here.
        UIViewController *presented = [controller presentedViewController];
        isPresenting = presented != nil;
        if(presented != nil) {
            controller = presented;
        }

    } while (isPresenting);

    return controller;
}

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

13

รหัสน้อยกว่าโซลูชันอื่น ๆ ทั้งหมด:

รุ่น Objective-C:

- (UIViewController *)getTopViewController {
    UIViewController *topViewController = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
    while (topViewController.presentedViewController) topViewController = topViewController.presentedViewController;

    return topViewController;
}

เวอร์ชัน Swift 2.0: (เครดิตไปที่ Steve.B)

func getTopViewController() -> UIViewController {
    var topViewController = UIApplication.sharedApplication().delegate!.window!!.rootViewController!
    while (topViewController.presentedViewController != nil) {
        topViewController = topViewController.presentedViewController!
    }
    return topViewController
}

ทำงานได้ทุกที่ในแอปของคุณแม้จะมีโมดอล


1
สิ่งนี้ไม่ได้จัดการกับสถานการณ์ที่ตัวควบคุมมุมมองที่นำเสนอUINavigationControllerซึ่งมีลูกของตัวเอง
levigroker

@levigroker อาจเป็นวิธีที่คุณสร้างมุมมองของคุณ? ใช้งานได้ดีสำหรับฉันที่จะใช้สิ่งนี้กับ Nav (นั่นคือวิธีที่ฉันใช้)
jungledev

@jungledev ฉันแน่ใจว่าคุณถูกต้อง ที่กล่าวว่าโซลูชันที่ทำงานในการกำหนดค่าคอนโทรลเลอร์มุมมองทั้งหมดเป็นสิ่งที่จำเป็น
levigroker

@levigroker มันใช้งานได้ในการกำหนดค่า vc มาตรฐานทั้งหมด - แอปที่ฉันใช้งานมีสถาปัตยกรรมที่ซับซ้อนมากมีผู้ใช้มากกว่า 500,000 คนและใช้งานได้ทุกที่ในแอป บางทีคุณควรโพสต์คำถามเพื่อถามว่าเหตุใดจึงไม่ทำงานในมุมมองของคุณพร้อมตัวอย่างโค้ด
jungledev

jungledev ฉันดีใจที่รหัสนี้เหมาะกับคุณ แต่ดูเหมือนจะไม่ใช่วิธีแก้ปัญหาที่สมบูรณ์ คำตอบของ @ zirinisp ทำงานได้ดีในสถานการณ์ของฉัน
levigroker

8

คำตอบของ zirinisp ใน Swift:

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

มันคือas!และnavigationController.visibleViewController!สำหรับ Swift 2.0
LinusGeffarth

7

ระบุหัวเรื่องให้กับ ViewController แต่ละตัวจากนั้นรับชื่อของ ViewController ปัจจุบันตามรหัสที่ระบุด้านล่าง

-(void)viewDidUnload {
  NSString *currentController = self.navigationController.visibleViewController.title;

จากนั้นตรวจสอบตามชื่อของคุณเช่นนี้

  if([currentController isEqualToString:@"myViewControllerTitle"]){
    //write your code according to View controller.
  }
}

เป็นคำตอบที่ดีที่สุดคุณสามารถตั้งชื่อ viewController ของคุณด้วย:self.title = myPhotoView
Resty

5

ของฉันดีกว่า! :)

extension UIApplication {
    var visibleViewController : UIViewController? {
        return keyWindow?.rootViewController?.topViewController
    }
}

extension UIViewController {
    fileprivate var topViewController: UIViewController {
        switch self {
        case is UINavigationController:
            return (self as! UINavigationController).visibleViewController?.topViewController ?? self
        case is UITabBarController:
            return (self as! UITabBarController).selectedViewController?.topViewController ?? self
        default:
            return presentedViewController?.topViewController ?? self
        }
    }
}

4

ทำไมไม่จัดการกับรหัสการแจ้งเตือนแบบพุชในตัวแทนของแอป เกี่ยวข้องโดยตรงกับมุมมองหรือไม่

คุณสามารถตรวจสอบว่ามุมมองของ UIViewController มองเห็นได้หรือไม่โดยตรวจสอบว่าwindowคุณสมบัติของมุมมองนั้นมีค่าหรือไม่ ดูเพิ่มเติมได้ที่นี่


ใช่มันเกี่ยวข้องกับมุมมองเนื่องจากฉันต้องแสดงมุมมองป้าย ให้ฉันตรวจสอบลิงค์ ขอบคุณ :)
lu Yuan

4

นอกจากคำตอบของ @zirinisp แล้ว

สร้างไฟล์ตั้งชื่อ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() {

}

ขอบคุณข้อมูล @zirinisp


3

เกี่ยวกับ NSNotificationCenter โพสต์ด้านบน (ขออภัยไม่สามารถหาที่โพสต์ความคิดเห็นได้ ... )

ในกรณีที่บางคนได้รับข้อผิดพลาด - [NSConcreteNotification allKeys] ประเภทต่างๆ เปลี่ยนสิ่งนี้:

-(void)didReceiveRemoteNotification:(NSDictionary *)userInfo

สำหรับสิ่งนี้:

-(void)didReceiveRemoteNotification:(NSNotification*)notif {
NSDictionary *dict = notif.userInfo;
}

3

สิ่งนี้ได้ผลสำหรับฉัน ฉันมีเป้าหมายมากมายที่มีตัวควบคุมที่แตกต่างกันดังนั้นคำตอบก่อนหน้านี้ดูเหมือนจะไม่ได้ผล

ก่อนอื่นคุณต้องการสิ่งนี้ภายในคลาส AppDelegate ของคุณ:

var window: UIWindow?

จากนั้นในฟังก์ชันของคุณ

let navigationController = window?.rootViewController as? UINavigationController
if let activeController = navigationController!.visibleViewController {
    if activeController.isKindOfClass( MyViewController )  {
        println("I have found my controller!")    
   }
}

2

นี่เป็นวิธีที่ดีที่สุดที่ฉันได้ลองใช้ ถ้าจะช่วยใคร ...

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

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

    return topController;
}

2
extension UIApplication {
    /// The top most view controller
    static var topMostViewController: UIViewController? {
        return UIApplication.shared.keyWindow?.rootViewController?.visibleViewController
    }
}

extension UIViewController {
    /// The visible view controller from a given view controller
    var visibleViewController: UIViewController? {
        if let navigationController = self as? UINavigationController {
            return navigationController.topViewController?.visibleViewController
        } else if let tabBarController = self as? UITabBarController {
            return tabBarController.selectedViewController?.visibleViewController
        } else if let presentedViewController = presentedViewController {
            return presentedViewController.visibleViewController
        } else {
            return self
        }
    }
}

ด้วยวิธีนี้คุณสามารถรับตัวควบคุมมุมมองโพสต์ด้านบนได้อย่างง่ายดาย

let viewController = UIApplication.topMostViewController

สิ่งหนึ่งที่ควรทราบก็คือหากมี UIAlertController กำลังแสดงอยู่UIApplication.topMostViewControllerจะส่งคืนไฟล์UIAlertController.


1

คำตอบของ jungledev เวอร์ชัน Swift 2.0

func getTopViewController() -> UIViewController {
    var topViewController = UIApplication.sharedApplication().delegate!.window!!.rootViewController!
    while (topViewController.presentedViewController != nil) {
        topViewController = topViewController.presentedViewController!
    }
    return topViewController
}

1

ฉันสร้างหมวดหมู่สำหรับUIApplicationด้วยvisibleViewControllersคุณสมบัติ แนวคิดหลักนั้นค่อนข้างเรียบง่าย ฉันหมุนviewDidAppearและviewDidDisappearวิธีการในUIViewController. ในviewDidAppearเมธอด viewController ถูกเพิ่มลงใน stack ในviewDidDisappearเมธอด viewController จะถูกลบออกจากสแต็ก NSPointerArrayใช้แทนNSArrayการเก็บจุดอ่อนUIViewControllerข้อมูลอ้างอิงที่แนวทางนี้ใช้ได้กับลำดับชั้นของ viewControllers

UIApplication + VisibleViewControllers.h

#import <UIKit/UIKit.h>

@interface UIApplication (VisibleViewControllers)

@property (nonatomic, readonly) NSArray<__kindof UIViewController *> *visibleViewControllers;

@end

UIApplication + VisibleViewControllers.m

#import "UIApplication+VisibleViewControllers.h"
#import <objc/runtime.h>

@interface UIApplication ()

@property (nonatomic, readonly) NSPointerArray *visibleViewControllersPointers;

@end

@implementation UIApplication (VisibleViewControllers)

- (NSArray<__kindof UIViewController *> *)visibleViewControllers {
    return self.visibleViewControllersPointers.allObjects;
}

- (NSPointerArray *)visibleViewControllersPointers {
    NSPointerArray *pointers = objc_getAssociatedObject(self, @selector(visibleViewControllersPointers));
    if (!pointers) {
        pointers = [NSPointerArray weakObjectsPointerArray];
        objc_setAssociatedObject(self, @selector(visibleViewControllersPointers), pointers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return pointers;
}

@end

@implementation UIViewController (UIApplication_VisibleViewControllers)

+ (void)swizzleMethodWithOriginalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector {
    Method originalMethod = class_getInstanceMethod(self, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
    BOOL didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
    if (didAddMethod) {
        class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
}

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self swizzleMethodWithOriginalSelector:@selector(viewDidAppear:)
                               swizzledSelector:@selector(uiapplication_visibleviewcontrollers_viewDidAppear:)];
        [self swizzleMethodWithOriginalSelector:@selector(viewDidDisappear:)
                               swizzledSelector:@selector(uiapplication_visibleviewcontrollers_viewDidDisappear:)];
    });
}

- (void)uiapplication_visibleviewcontrollers_viewDidAppear:(BOOL)animated {
    [[UIApplication sharedApplication].visibleViewControllersPointers addPointer:(__bridge void * _Nullable)self];
    [self uiapplication_visibleviewcontrollers_viewDidAppear:animated];
}

- (void)uiapplication_visibleviewcontrollers_viewDidDisappear:(BOOL)animated {
    NSPointerArray *pointers = [UIApplication sharedApplication].visibleViewControllersPointers;
    for (int i = 0; i < pointers.count; i++) {
        UIViewController *viewController = [pointers pointerAtIndex:i];
        if ([viewController isEqual:self]) {
            [pointers removePointerAtIndex:i];
            break;
        }
    }
    [self uiapplication_visibleviewcontrollers_viewDidDisappear:animated];
}

@end

https://gist.github.com/medvedzzz/e6287b99011f2437ac0beb5a72a897f0

เวอร์ชัน Swift 3

UIApplication + VisibleViewControllers.swift

import UIKit

extension UIApplication {

    private struct AssociatedObjectsKeys {
        static var visibleViewControllersPointers = "UIApplication_visibleViewControllersPointers"
    }

    fileprivate var visibleViewControllersPointers: NSPointerArray {
        var pointers = objc_getAssociatedObject(self, &AssociatedObjectsKeys.visibleViewControllersPointers) as! NSPointerArray?
        if (pointers == nil) {
            pointers = NSPointerArray.weakObjects()
            objc_setAssociatedObject(self, &AssociatedObjectsKeys.visibleViewControllersPointers, pointers, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
        return pointers!
    }

    var visibleViewControllers: [UIViewController] {
        return visibleViewControllersPointers.allObjects as! [UIViewController]
    }
}

extension UIViewController {

    private static func swizzleFunc(withOriginalSelector originalSelector: Selector, swizzledSelector: Selector) {
        let originalMethod = class_getInstanceMethod(self, originalSelector)
        let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
        let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
        if didAddMethod {
            class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    }

    override open class func initialize() {
        if self != UIViewController.self {
            return
        }
        let swizzlingClosure: () = {
            UIViewController.swizzleFunc(withOriginalSelector: #selector(UIViewController.viewDidAppear(_:)),
                                         swizzledSelector: #selector(uiapplication_visibleviewcontrollers_viewDidAppear(_:)))
            UIViewController.swizzleFunc(withOriginalSelector: #selector(UIViewController.viewDidDisappear(_:)),
                                         swizzledSelector: #selector(uiapplication_visibleviewcontrollers_viewDidDisappear(_:)))
        }()
        swizzlingClosure
    }

    @objc private func uiapplication_visibleviewcontrollers_viewDidAppear(_ animated: Bool) {
        UIApplication.shared.visibleViewControllersPointers.addPointer(Unmanaged.passUnretained(self).toOpaque())
        uiapplication_visibleviewcontrollers_viewDidAppear(animated)
    }

    @objc private func uiapplication_visibleviewcontrollers_viewDidDisappear(_ animated: Bool) {
        let pointers = UIApplication.shared.visibleViewControllersPointers
        for i in 0..<pointers.count {
            if let pointer = pointers.pointer(at: i) {
                let viewController = Unmanaged<AnyObject>.fromOpaque(pointer).takeUnretainedValue() as? UIViewController
                if viewController.isEqual(self) {
                    pointers.removePointer(at: i)
                    break
                }
            }
        }
        uiapplication_visibleviewcontrollers_viewDidDisappear(animated)
    }
}

https://gist.github.com/medvedzzz/ee6f4071639d987793977dba04e11399


1

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

หมายเหตุสำคัญ: คุณไม่สามารถทดสอบได้โดยไม่ต้องเรียกใช้แอปของคุณในโหมดแก้ไขข้อบกพร่อง

นี่คือทางออกของฉัน

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