从UIView到UIViewController?

ber*_*dom 180 cocoa-touch objective-c uiviewcontroller uiview ios

是否有内置的方式从a UIView到它UIViewController?我知道你可以从UIViewControllerUIView通过它,[self view]但我想知道是否有反向参考?

Phi*_*l M 200

使用Brock发布的示例,我对它进行了修改,使其成为UIView的一个类,而不是UIViewController并使其递归,以便任何子视图都可以(希望)找到父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
Run Code Online (Sandbox Code Playgroud)

要使用此代码,请将其添加到新的类文件中(我将其命名为"UIKitCategories")并删除类数据...将@interface复制到标头中,并将@implementation复制到.m文件中.然后在你的项目中,#import"UIKitCategories.h"并在UIView代码中使用:

// from a UIView subclass... returns nil if UIViewController not available
UIViewController * myController = [self firstAvailableUIViewController];
Run Code Online (Sandbox Code Playgroud)

  • 您需要允许UIView知道其UIViewController的一个原因是您有自定义UIView子类需要推送模态视图/对话框. (45认同)
  • 我只是喜欢有多少SO问题得到一个"智者",学术,接受的答案只有几点和第二个答案是实用的,肮脏的和违反规则,十倍的点数:-) (8认同)
  • Phil,你的自定义视图应该调用视图控制器监听的委托方法,然后从那里推送. (6认同)
  • 对于UIView推动模态视图,这不是一个坏习惯吗?我现在正在这样做,但我觉得这不是正确的事情. (2认同)

小智 112

UIView是.的子类UIResponder.UIResponder勾画出方法-nextResponder与返回的实现nil.UIView覆盖此方法,如UIResponder(由于某种原因而不是in 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
Run Code Online (Sandbox Code Playgroud)

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

  • 只有在接收`UIView`和`UIViewController`之间的响应器链中没有任何内容时,这才有效.菲尔M的回答是回归的方法. (5认同)

pgb*_*pgb 44

由于长期以来一直是公认的答案,我觉得我需要用更好的答案来纠正它.

关于需求的一些评论:

  • 您的视图不需要直接访问视图控制器.
  • 视图应该独立于视图控制器,并且能够在不同的上下文中工作.
  • 您是否需要视图以与视图控制器的方式接口,推荐的方式以及Apple在Cocoa中执行的操作是使用委托模式.

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

@protocol MyViewDelegate < NSObject >

- (void)viewActionHappened;

@end

@interface MyView : UIView

@property (nonatomic, assign) MyViewDelegate delegate;

@end

@interface MyViewController < MyViewDelegate >

@end
Run Code Online (Sandbox Code Playgroud)

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

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

没有内置的方法来做到这一点.虽然你可以通过添加避过它IBOutletUIView和在Interface Builder连接这些,不建议这样做.视图不应该知道视图控制器.相反,您应该按照@Phil M建议并创建一个用作委托的协议.

  • 这是非常糟糕的建议.您不应该从视图引用视图控制器 (26认同)
  • @Phillipe Leybaert我很想知道你对按钮点击事件的最佳设计的想法,该事件应该在控制器上调用某些动作,而视图没有控制器的引用.谢谢 (23认同)
  • 关于"你不应该这样做"的所有这些就是这个.如果视图想知道它的视图控制器,那由程序员来决定.期. (10认同)
  • @MattDiPasquale:是的,这是糟糕的设计. (6认同)
  • @PhilippeLeybaert Apple的示例项目倾向于演示特定API功能的使用.许多我提到牺牲良好或可扩展的设计,以提供该主题的简洁演示.我花了很长时间才意识到这一点,虽然它有道理,但我发现它很不幸.我认为很多开发人员将这些实用的项目作为Apple最佳设计实践的指南,我很确定他们不是. (3认同)

de.*_*de. 33

我建议使用更轻量级的方法来遍历完整的响应者链,而无需在UIView上添加类别:

@implementation MyUIViewSubclass

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

@end
Run Code Online (Sandbox Code Playgroud)


Con*_*has 22

结合几个已经给出的答案,我正在发布我的实现:

@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
Run Code Online (Sandbox Code Playgroud)

该类别是我在我创建的每个应用程序上发布的支持ARC的静态库的一部分.它已经过多次测试,我没有发现任何问题或泄漏.

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


Ush*_*hox 12

虽然这可以在技术上解决,因为pgb建议,恕我直言,这是一个设计缺陷.视图不需要知道控制器.

  • 这是观察者模式背后的想法.观察到的(本例中的视图)不应直接了解其观察者.观察者应该只收到他们感兴趣的回调. (2认同)

Var*_*ria 12

我修改了de answer所以我可以传递任何视图,按钮,标签等来获取它的父级UIViewController.这是我的代码.

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

编辑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)!
    }
Run Code Online (Sandbox Code Playgroud)

编辑2: - 快速延伸

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)!
    }
}
Run Code Online (Sandbox Code Playgroud)


Gab*_*iel 7

不要忘记,您可以访问视图是其子视图的窗口的根视图控制器.从那里,如果您正在使用导航视图控制器并想要将新视图推送到它:

    [[[[self window] rootViewController] navigationController] pushViewController:newController animated:YES];
Run Code Online (Sandbox Code Playgroud)

但是,您需要首先正确设置窗口的rootViewController属性.首次创建控制器时执行此操作,例如在您的app委托中:

-(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];
}
Run Code Online (Sandbox Code Playgroud)


小智 6

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


Kev*_*n R 6

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

虽然这在iPad中运行良好(UIPopoverController礼物本身,因此不需要参考a UIViewController),使相同的代码工作意味着突然引用你presentViewControllerUIViewController.有点不一致吧?

如前所述,在你的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
    }
}
Run Code Online (Sandbox Code Playgroud)


jas*_*n z 6

从Swift 5.2开始有两种解决方案:

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

解决方案一:

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

解决方案2:

extension UIView {
    var parentViewController: UIViewController? {
        sequence(first: self) { $0.next }
            .compactMap{ $0 as? UIViewController }
            .first
    }
}
Run Code Online (Sandbox Code Playgroud)
  • 此解决方案需要首先迭代每个响应者,因此可能不是性能最佳的。


Riv*_*era 5

我不认为在某些情况下找出谁是视图控制器是"坏"的想法.可能不错的主意是保存对该控制器的引用,因为它可能会随着超级视图的变化而改变.在我的例子中,我有一个遍历响应者链的getter.

//.H

@property (nonatomic, readonly) UIViewController * viewController;
Run Code Online (Sandbox Code Playgroud)

//.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;
}
Run Code Online (Sandbox Code Playgroud)


fro*_*ouo 5

雨燕4

(比其他答案更简洁)

fileprivate extension UIView {

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

}
Run Code Online (Sandbox Code Playgroud)

我需要首先访问视图的用例UIViewController:我有一个围绕AVPlayer/的对象AVPlayerViewController,我想提供一个show(in view: UIView)嵌入AVPlayerViewControllerview. 为此,我需要访问view's UIViewController