从UIView获取UIViewController?

有没有一种内置的方式从UIView到它的UIViewController ? 我知道你可以通过[self view]UIViewController到它的UIView ,但是我想知道是否有反向引用?

既然这个问题已经被人们接受了很长时间,我觉得我需要更好的回答。

关于需求的一些评论:

  • 您的视图不需要直接访问视图控制器。
  • 视图应该独立于视图控制器,并能够在不同的环境下工作。
  • 如果您需要视图与视图控制器进行交互,推荐的方式,以及苹果在Cocoa上所做的是使用委托模式。

如何实现它的一个例子如下:

 @protocol MyViewDelegate < NSObject > - (void)viewActionHappened; @end @interface MyView : UIView @property (nonatomic, assign) MyViewDelegate delegate; @end @interface MyViewController < MyViewDelegate > @end 

视图接口与它的委托(如UITableView所做的那样),它并不关心它是否在视图控制器或其他任何你最终使用的类中实现。

我的原始答案如下: 我不build议这样做,直接访问视图控制器的其余答案都无法实现

没有内置的方法来做到这一点。 虽然可以通过在UIView上添加一个IBOutlet并在Interface Builder中连接它们来解决这个问题,但不build议这样做。 该视图不应该了解视图控制器。 相反,你应该像@Phil M所build议的那样去创build一个协议来作为委托。

使用Brock发布的例子,我修改了它,使它是一个UIView类别,而不是UIViewController,并使其recursion,以便任何子视图可以(希望)find父UIViewController。

 @interface UIView (FindUIViewController) - (UIViewController *) firstAvailableUIViewController; - (id) traverseResponderChainForUIViewController; @end @implementation UIView (FindUIViewController) - (UIViewController *) firstAvailableUIViewController { // convenience function for casting and to "mask" the recursive function return (UIViewController *)[self traverseResponderChainForUIViewController]; } - (id) traverseResponderChainForUIViewController { id nextResponder = [self nextResponder]; if ([nextResponder isKindOfClass:[UIViewController class]]) { return nextResponder; } else if ([nextResponder isKindOfClass:[UIView class]]) { return [nextResponder traverseResponderChainForUIViewController]; } else { return nil; } } @end 

要使用这个代码,将它添加到一个新的类文件(我命名为我的“UIKitCategories”)并删除类数据…将@interface复制到标题中,@implementation复制到.m文件中。 然后在你的项目中,#import“UIKitCategories.h”并在UIView代码中使用:

 // from a UIView subclass... returns nil if UIViewController not available UIViewController * myController = [self firstAvailableUIViewController]; 

UIViewUIResponder一个子类。 UIResponder用一个返回nil的实现来展示方法-nextResponderUIView覆盖这个方法,如UIResponder (出于某种原因而不是在UIView )中所述,如下所示:如果视图有一个视图控制器,它由-nextResponder返回。 如果没有视图控制器,该方法将返回超级视图。

把这个添加到你的项目中,你就可以开始了。

 @interface UIView (APIFix) - (UIViewController *)viewController; @end @implementation UIView (APIFix) - (UIViewController *)viewController { if ([self.nextResponder isKindOfClass:UIViewController.class]) return (UIViewController *)self.nextResponder; else return nil; } @end 

现在UIView有一个返回视图控制器的工作方法。

我会build议一个更轻量级的方法遍历完整的响应者链,而不必在UIView上添加一个类别:

 @implementation MyUIViewSubclass - (UIViewController *)viewController { UIResponder *responder = self; while (![responder isKindOfClass:[UIViewController class]]) { responder = [responder nextResponder]; if (nil == responder) { break; } } return (UIViewController *)responder; } @end 

结合几个已经给出的答案,我运送它以及我的实施:

 @implementation UIView (AppNameAdditions) - (UIViewController *)appName_viewController { /// Finds the view's view controller. // Take the view controller class object here and avoid sending the same message iteratively unnecessarily. Class vcc = [UIViewController class]; // Traverse responder chain. Return first found view controller, which will be the view's view controller. UIResponder *responder = self; while ((responder = [responder nextResponder])) if ([responder isKindOfClass: vcc]) return (UIViewController *)responder; // If the view controller isn't found, return nil. return nil; } @end 

该类别是我创build的每个应用程序所附带的启用ARC的静态库的一部分。 它已经过多次testing,我没有发现任何问题或泄漏。

PS:如果有关的视图是你的子类,你不需要像我这样使用类别。 在后一种情况下,只要把这个方法放在你的子类中,你就可以走了。

即使这可以从技术上来解决,因为pgbbuild议,恕我直言,这是一个devise缺陷。 视图不需要知道控制器。

不要忘记,您可以访问视图是子视图的窗口的根视图控制器。 从那里,如果你是例如使用一个导航视图控制器,并想推新视图:

  [[[[self window] rootViewController] navigationController] pushViewController:newController animated:YES]; 

但是,您需要先正确设置窗口的rootViewController属性。 当你第一次创build控制器,例如在你的应用程序委托中,这样做:

 -(void) applicationDidFinishLaunching:(UIApplication *)application { window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; RootViewController *controller = [[YourRootViewController] alloc] init]; [window setRootViewController: controller]; navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController]; [controller release]; [window addSubview:[[self navigationController] view]]; [window makeKeyAndVisible]; } 

虽然这些答案在技术上是正确的,包括Ushox,但我认为批准的方法是实施新的协议或重新使用现有的协议。 协议将观察者与观察者隔离,就像在他们之间放置邮件插槽一样。 实际上,这就是Gabriel通过pushViewController方法调用所做的事情; 该视图“知道”礼貌地要求您的navigationController推送视图是合适的协议,因为viewController符合navigationController协议。 虽然你可以创build自己的协议,但只要使用Gabriel的例子并重新使用UINavigationController协议就可以了。

我修改了答案,以便我可以传递任何视图,button,标签等,以获得它的父UIViewController 。 这是我的代码。

 +(UIViewController *)viewController:(id)view { UIResponder *responder = view; while (![responder isKindOfClass:[UIViewController class]]) { responder = [responder nextResponder]; if (nil == responder) { break; } } return (UIViewController *)responder; } 

编辑Swift 3版本

 class func viewController(_ view: UIView) -> UIViewController { var responder: UIResponder? = view while !(responder is UIViewController) { responder = responder?.next if nil == responder { break } } return (responder as? UIViewController)! } 

我不认为在某些情况下找出谁是视图控制器是“坏”的主意。 可能是一个坏主意是保存对这个控制器的引用,因为它可能随着超级视图的改变而改变。 在我的情况下,我有一个getter遍历响应者链。

//。H

 @property (nonatomic, readonly) UIViewController * viewController; 

//.m

 - (UIViewController *)viewController { for (UIResponder * nextResponder = self.nextResponder; nextResponder; nextResponder = nextResponder.nextResponder) { if ([nextResponder isKindOfClass:[UIViewController class]]) return (UIViewController *)nextResponder; } // Not found NSLog(@"%@ doesn't seem to have a viewController". self); return nil; } 

最简单的while循环来查找viewController。

 -(UIViewController*)viewController { UIResponder *nextResponder = self; do { nextResponder = [nextResponder nextResponder]; if ([nextResponder isKindOfClass:[UIViewController class]]) return (UIViewController*)nextResponder; } while (nextResponder != nil); return nil; } 

我偶然发现了一个我想要重用的PopoverController ,并且在可重用视图本身中添加了一些代码(它实际上并不比打开PopoverController的buttonPopoverController )。

虽然这在iPad中工作正常( UIPopoverController自身呈现,因此不需要引用UIViewController ),获得相同的代码工作意味着突然引用您的UIViewControllerpresentViewController 。 有点不一致的权利?

就像之前提到的那样,在UIView中使用逻辑不是最好的方法。 但是在单独的控制器中包装所需的几行代码感觉真的没用。

无论哪种方式,这是一个迅速的解决scheme,它添加一个新的属性到任何UIView:

 extension UIView { var viewController: UIViewController? { var responder: UIResponder? = self while responder != nil { if let responder = responder as? UIViewController { return responder } responder = responder?.nextResponder() } return nil } } 

这不是直接回答这个问题,而是对这个问题的意图做出假设。

如果你有一个视图,并且在这个视图中你需要调用另一个对象的方法,比如说视图控制器,那么你可以使用NSNotificationCenter。

首先在头文件中创build您的通知string

 #define SLCopyStringNotification @"ShaoloCopyStringNotification" 

在你的视图中调用postNotificationName:

 - (IBAction) copyString:(id)sender { [[NSNotificationCenter defaultCenter] postNotificationName:SLCopyStringNotification object:nil]; } 

然后在你的视图控制器中添加一个观察者。 我在viewDidLoad中做这个

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

现在(也在同一个视图控制器中)实现你的方法copyString:如上面的@selector所描述的。

 - (IBAction) copyString:(id)sender { CalculatorResult* result = (CalculatorResult*)[[PercentCalculator sharedInstance].arrayTableDS objectAtIndex:([self.viewTableResults indexPathForSelectedRow].row)]; UIPasteboard *gpBoard = [UIPasteboard generalPasteboard]; [gpBoard setString:result.stringResult]; } 

我不是说这是做这件事的正确方法,它似乎比运行第一响应者链清洁。 我使用这段代码在UITableView上实现UIMenuController,并将事件传递回UIViewController,这样我就可以对数据做些什么了。

对于菲尔的回答:

在行中: id nextResponder = [self nextResponder]; 如果self(UIView)不是ViewController视图的子视图,如果你知道自己的层次(UIView),你也可以使用: id nextResponder = [[self superview] nextResponder];

这当然是一个坏主意和错误的devise,但我相信我们都可以享受@Phil_M提出的最佳答案的Swift解决scheme:

 static func firstAvailableUIViewController(fromResponder responder: UIResponder) -> UIViewController? { func traverseResponderChainForUIViewController(responder: UIResponder) -> UIViewController? { if let nextResponder = responder.nextResponder() { if let nextResp = nextResponder as? UIViewController { return nextResp } else { return traverseResponderChainForUIViewController(nextResponder) } } return nil } return traverseResponderChainForUIViewController(responder) } 

如果你的目的是做简单的事情,如显示模式对话框或跟踪数据,这不合理的使用协议。 我个人将这个函数存储在一个实用对象中,你可以使用它来实现UIResponder协议的任何东西:

 if let viewController = MyUtilityClass.firstAvailableUIViewController(self) {} 

所有功劳归于@Phil_M

我的解决scheme可能会被认为是一种假的,但我有一个mayoneez类似的情况(我想切换视图响应在EAGLView中的手势),我得到了EAGL的视图控制器这样:

 EAGLViewController *vc = ((EAGLAppDelegate*)[[UIApplication sharedApplication] delegate]).viewController; 

我认为有这样一个观察者需要通知观察者的情况。

我看到一个类似的问题,UIViewController中的UIView响应一个情况,它需要先告诉它的父视图控制器隐藏后退button,然后在完成时告诉父视图控制器它需要从栈中popup。

我一直在试图与代表没有成功。

我不明白为什么这应该是一个坏主意?

另一个简单的方法是拥有自己的视图类,并在视图类中添加视图控制器的属性。 通常视图控制器创build视图,这是控制器可以将其自身设置为属性的位置。 基本上,它不是四处搜寻(有点黑客)的控制器,让控制器自己设置为视图 – 这很简单,但是有道理的,因为它是控制器,控制视图。

如果你的rootViewController是在AppDelegate类中设置的UINavigationViewController,那么

  + (UIViewController *) getNearestViewController:(Class) c { NSArray *arrVc = [[[[UIApplication sharedApplication] keyWindow] rootViewController] childViewControllers]; for (UIViewController *v in arrVc) { if ([v isKindOfClass:c]) { return v; } } return nil;} 

其中c需要视图控制器类。

用法:

  RequiredViewController* rvc = [Utilities getNearestViewController:[RequiredViewController class]]; 

不可能。

我所做的是将UIViewController指针传递给UIView(或适当的inheritance)。 对不起,我不能帮助IB解决这个问题,因为我不相信IB。

要回答第一个评论者:有时你需要知道是谁打电话给你的,因为它决定你能做什么。 例如,一个数据库,你可能只有读取权限或读/写…