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

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

}



답변

rootViewController컨트롤러가 아닌 경우에도 사용할 수 있습니다 UINavigationController.

UIViewController *vc = self.window.rootViewController;

루트 뷰 컨트롤러를 알고 나면 UI를 구축 한 방법에 따라 다르지만 컨트롤러 계층 구조를 탐색하는 방법을 찾을 수 있습니다.

앱을 정의한 방식에 대한 자세한 내용을 알려면 힌트를 줄 수 있습니다.

편집하다:

뷰 컨트롤러가 아닌 최상위 를 원한다면 확인할 수 있습니다.

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

이 견해는 보이지 않거나 일부 하위 견해로 덮여있을 수도 있지만 …

다시 말하지만 UI에 따라 다르지만 도움이 될 수 있습니다 …


답변

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

그래서 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
            }
        }
    }
}


답변

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


답변

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 메소드에서 각각 추가 / 제거 옵저버 호출을 처리하십시오.


답변

암호

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

도움이 되길 바랍니다!


답변

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


답변

다른 모든 솔루션보다 코드가 적습니다.

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
}

모달에서도 앱의 어느 곳에서나 작동합니다.