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
}
모달에서도 앱의 어느 곳에서나 작동합니다.