从 UIView 获取 UIViewController 的信息

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

151130 次浏览

因为这个答案已经被接受了很长一段时间,我觉得我需要用一个更好的答案来纠正它。

以下是对必要性的一些评论:

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

下面是如何实现它的示例:

@protocol MyViewDelegate < NSObject >


- (void)viewActionHappened;


@end


@interface MyView : UIView


@property (nonatomic, assign) MyViewDelegate delegate;


@end


@interface MyViewController < MyViewDelegate >


@end

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

我原来的答案如下:我不推荐这个,也不推荐其他直接访问视图控制器的答案

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

没有办法。

我所做的是将UIViewController指针传递给UIView(或一个适当的继承)。很抱歉我不能用IB的方法来解决这个问题,因为我不相信IB。

回答第一个评论:有时候你确实需要知道是谁打电话给你,因为这决定了你能做什么。例如,对于数据库,您可能只有读访问权或读/写权限…

尽管这在技术上可以像的调整建议的那样解决,恕我直言,这是一个设计缺陷。视图应该不需要知道控制器。

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

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

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有一个返回视图控制器的工作方法。

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

@interface UIView (FindUIViewController)
- (UIViewController *) firstAvailableUIViewController;
@end


@implementation UIView (FindUIViewController)
- (UIViewController *) firstAvailableUIViewController {
UIResponder *responder = [self nextResponder];
while (responder != nil) {
if ([responder isKindOfClass:[UIViewController class]]) {
return (UIViewController *)responder;
}
responder = [responder nextResponder];
}
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];

别忘了你可以访问根视图控制器对于视图是子视图的窗口。从那里,如果你正在使用一个导航视图控制器,并想要将一个新视图推到它上面:

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

不过,您需要首先正确地设置窗口的rootViewController属性。当你第一次创建控制器时,例如在你的应用委托中:

-(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协议。虽然您可以创建自己的协议,但只需使用Gabriel的示例并重用UINavigationController协议就可以了。

我认为存在被观察者需要告知观察者的情况。

我看到一个类似的问题,UIViewController中的UIView在响应一个情况时,它需要首先告诉它的父视图控制器去隐藏后退按钮,然后在完成时告诉父视图控制器它需要把自己从堆栈中弹出。

我一直在和代表们这样做,但没有成功。

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

我建议一个更轻量级的方法来遍历完整的responder链,而不必在UIView上添加一个类别:

@implementation MyUIViewSubclass


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


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

/ / . h

@property (nonatomic, readonly) UIViewController * viewController;

/ / 00

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

结合几个已经给出的答案,我将在我的实现中附带它:

@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

类别是我的ARC-enabled静态库的一部分,我在我创建的每个应用程序中都附带这个库。经过多次测试,我没有发现任何问题或漏洞。

附注:如果所关注的视图是您的子类,则不需要像我那样使用类别。在后一种情况下,只需将方法放在子类中,就可以了。

另一种简单的方法是拥有自己的视图类,并在视图类中添加视图控制器的属性。通常是视图控制器创建视图这就是控制器可以将自身设置为属性的地方。基本上,它不是到处寻找控制器,而是让控制器将自己设置为视图——这很简单,但很有意义,因为它是控制器“控制”视图。

这并没有直接回答问题,而是对问题的意图做了一个假设。

如果你有一个视图,在那个视图中你需要调用另一个对象的方法,比如视图控制器,你可以用NSNotificationCenter代替。

首先在头文件中创建通知字符串

#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的视图的子视图,如果你知道self(UIView)的层次结构,你也可以使用:id nextResponder = [[self superview] nextResponder];

最简单的do while循环用于查找viewController。

-(UIViewController*)viewController
{
UIResponder *nextResponder =  self;


do
{
nextResponder = [nextResponder nextResponder];


if ([nextResponder isKindOfClass:[UIViewController class]])
return (UIViewController*)nextResponder;


} while (nextResponder != nil);


return nil;
}

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

    + (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]];

这肯定是一个糟糕的想法和错误的设计,但我相信我们都可以享受@Phil_M提出的最佳答案的快速解决方案:

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

我修改了答案,所以我可以传递任何视图,按钮,标签等,以获得它的父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)!
}

编辑2:- Swift扩展

extension UIView
{
//Get Parent View Controller from any view
func parentViewController() -> UIViewController {
var responder: UIResponder? = self
while !(responder is UIViewController) {
responder = responder?.next
if nil == responder {
break
}
}
return (responder as? UIViewController)!
}
}

我偶然发现了这样一种情况:我有一个想要重用的小组件,并在可重用视图本身中添加了一些代码(它实际上只不过是一个打开PopoverController的按钮)。

虽然这在iPad中工作得很好(UIPopoverController呈现自己,因此不需要引用UIViewController),但让相同的代码工作意味着突然从你的UIViewController引用你的presentViewController。有点矛盾,对吧?

就像之前提到的,在UIView中有逻辑不是最好的方法。但是在一个单独的控制器中封装所需的几行代码真的没什么用。

不管怎样,这里有一个快速的解决方案,它添加了一个新的属性到任何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
}
}

也许我来晚了。但在这种情况下,我不喜欢分类(污染)。我喜欢这样:

#define UIViewParentController(__view) ({ \
UIResponder *__responder = __view; \
while ([__responder isKindOfClass:[UIView class]]) \
__responder = [__responder nextResponder]; \
(UIViewController *)__responder; \
})

swift 4的更新版本:感谢@Phil_M和@paul-slm

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


return traverseResponderChainForUIViewController(responder: responder)
}

Swift 4版本

extension UIView {
var parentViewController: UIViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.next
if let viewController = parentResponder as? UIViewController {
return viewController
}
}
return nil
}

使用的例子

 if let parent = self.view.parentViewController{


}

中高阶层的解决方案

extension UIView {
var parentViewController: UIViewController? {
for responder in sequence(first: self, next: { $0.next }) {
if let viewController = responder as? UIViewController {
return viewController
}
}
return nil
}
}

斯威夫特4

(比其他答案更简洁)

fileprivate extension UIView {


var firstViewController: UIViewController? {
let firstViewController = sequence(first: self, next: { $0.next }).first(where: { $0 is UIViewController })
return firstViewController as? UIViewController
}


}

我需要首先访问视图的用例UIViewController:我有一个包裹着AVPlayer / AVPlayerViewController的对象,我想提供一个简单的show(in view: UIView)方法,将AVPlayerViewController嵌入到view中。为此,我需要访问viewUIViewController

如果你不打算上传到App Store,你也可以使用UIView的私有方法。

@interface UIView(Private)
- (UIViewController *)_viewControllerForAncestor;
@end


// Later in the code
UIViewController *vc = [myView _viewControllerForAncestor];
var parentViewController: UIViewController? {
let s = sequence(first: self) { $0.next }
return s.compactMap { $0 as? UIViewController }.first
}

斯威夫特5.2的两个解决方案:

  • 更多的是在功能方面
  • 现在不需要return关键字🤓

解决方案1:

extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) { $0.next }
.first(where: { $0 is UIViewController })
.flatMap { $0 as? UIViewController }
}
}

解决方案2:

extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) { $0.next }
.compactMap{ $0 as? UIViewController }
.first
}
}
  • 此解决方案需要首先遍历每个响应器,因此可能不是性能最好的。

要获得给定视图的控制器,可以使用UIFirstResponder chain。

customView.target(forAction: Selector("viewDidLoad"), withSender: nil)