AppDelegate.m의 화면에 현재 표시되는 UIViewController를 가져옵니다.


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를 빌드하는 방법에 엄격하게 의존하고 (보기에서 발생하는 것처럼) 명시 적 컨트롤러 계층이 없기 때문에 일반적으로 유추 할 수 없습니다. 루트 컨트롤러에 속성을 추가하고 새 컨트롤러를 "푸시"할 때마다 해당 값을 설정할 수 있습니다.
sergio

1
그 가치가 최신 상태로 유지되는 한 그것은 나에게도가는 좋은 방법 인 것 같습니다.
Dima

4
UIView인스턴스 에서 컨트롤러로 직접가는 방법은 없습니다 . 반드시 현재 표시된 컨트롤러 일 필요 rootViewController없습니다 . 뷰 계층 구조의 맨 위에 있습니다.
Gingi 2016 년

101

나는 항상 범주가 있고 쉽게 재사용 할 수있는 범주를 포함하는 솔루션을 좋아합니다.

그래서 UIWindow에 카테고리를 만들었습니다. 이제 UIWindow에서 visibleViewController를 호출 할 수 있으며 컨트롤러 계층을 검색하여 가시적 인 뷰 컨트롤러를 얻을 수 있습니다. 탐색 및 / 또는 탭 막대 컨트롤러를 사용하는 경우 작동합니다. 제안 할 다른 유형의 컨트롤러가 있다면 알려 주시면 추가 할 수 있습니다.

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

스위프트 버전

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

@Mmingming getVisibielController 메소드에서 사용자 정의 컨테이너 VC를 확인하고 "visible"컨트롤러를 반환하는지 확인하고 추가로 추가하는 것이 어렵지 않아야합니다 (대부분의 사용자 정의의 경우 일반적으로 vc.childControllers.lastObject 임) 컨테이너 VC 구현 (예상)이지만 구현 방법에 따라 다릅니다.
gadu

1
방금 업데이트 된 구문을 제외 하고이 답변과 동일한 접근 방식으로 답변을 게시했습니다. 스위치 케이스를 사용하고 있으며 스위프트 3 명명 규칙을 따르십시오 : stackoverflow.com/a/42486823/3451975
Jeehut

43

스위프트의 UIApplication에 대한 간단한 확장 (내도 moreNavigationController에 대한 관심 UITabBarController아이폰) :

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에는 더 이상 "where"가 없습니다. 또한 "sharedApplication ()"은 "공유"입니다. 별거 아니야 업데이트하는 데 1 분 밖에 걸리지 않습니다. 재귀를 사용한다는 것을 언급하는 것이 좋습니다. 또한 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 Controller에서 :

-(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 15:08에

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의 작품은 정말 복잡한 구조를 가지고에 응용 프로그램 I 작업을 구성 -, 50 만 명 이상의 사용자가 사용하고 응용 프로그램에서 사방이 작동된다. 아마도 코드 예제와 함께 왜 뷰에서 작동하지 않는지 묻는 질문을 게시해야합니까?
jungledev

jungledev 나는이 코드가 당신을 위해 작동하는 것을 기쁘게 생각하지만, 완전한 해결책은 아닙니다. @zirinisp의 대답은 내 상황에서 완벽하게 작동합니다.
levigroker

8

스위프트에서 zirinisp의 답변 :

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!스위프트 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

왜 앱 델리게이트에서 푸시 알림 코드를 처리하지 않습니까? 뷰와 직접 관련이 있습니까?

뷰의 window속성에 값이 있는지 확인하여 UIViewController의 뷰가 현재 표시되는지 확인할 수 있습니다 . 자세한 내용은 여기를 참조 하십시오 .


예, 배지보기를 표시해야하기 때문에보기와 관련이 있습니다. 링크를 확인하겠습니다. 감사합니다 :)
루 위안

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의 답변 스위프트 2.0 버전

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

1

나는를 위해 카테고리를 만들어 UIApplicationvisibleViewControllers속성입니다. 주요 아이디어는 매우 간단합니다. 나는 스위 즐링 viewDidAppearviewDidDisappear메소드 UIViewController. 에 viewDidAppear있어서의 ViewController 스택에 추가된다. 에 viewDidDisappear있어서의 ViewController 스택으로부터 제거된다. weak 의 참조 를 저장 NSPointerArray하는 대신 사용됩니다 . 이 접근 방식은 모든 viewControllers 계층에 적용됩니다.NSArrayUIViewController

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

스위프트 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.