给定一个视图,如何获取其viewController?


141

我有一个指向的指针UIView。如何访问它UIViewController[self superview]是另一个UIView,但不是UIViewController,对吗?


我认为该线程具有答案:从iPhone上的UIView进入UIViewController?
Ushox

基本上,由于视图被关闭,我试图调用viewController的viewWillAppear。视图本身被检测到轻击并调用[self removeFromSuperview]的视图本身关闭了该视图。viewController本身不会调用viewWillAppear / WillDisappear / DidAppear / DidDisappear。
mahboudz

我的意思是我正在尝试调用viewWillDisappear,因为我的视图被关闭了。
mahboudz

Answers:


42

是的,superview是包含您的视图的视图。您的视图不应确切知道其视图控制器是哪一个,因为这会破坏MVC原理。

另一方面,控制器知道它负责哪个视图(self.view = myView),通常,该视图将用于处理的方法/事件委托给控制器。

通常,您应该有一个指向控制器的指针,而不是指向视图的指针,该控制器又可以执行一些控制逻辑,也可以将某些内容传递给它的视图。


24
我不确定是否会违反MVC原则。在任一点上,一个视图只有一个视图控制器。能够到达它以便将消息传递回去,应该是一项自动功能,而不是您必须努力实现的功能(通过添加属性来跟踪)。关于观点,人们可能会说同样的话:为什么你需要知道你是谁的孩子?或者是否还有其他同级视图。但是,有一些方法可以获取这些对象。
mahboudz

您对视图有些了解,知道它的父级,这不是非常清晰的设计决策,但是已经确定可以直接使用Superview成员变量来执行某些操作(检查父级类型,从父级中删除等)。最近与PureMVC一起工作后,我对设计抽象有些挑剔:)我将在iPhone的UIView和UIViewController类与PureMVC的View和Mediator类之间进行并行处理-大多数情况下,View类不需要了解其MVC处理程序/接口(UIViewController / Mediator)。
Dimitar Dimitrov

9
关键字:“最”。
Glenn Maynard

278

UIResponder文档中nextResponder

UIResponder类不会自动存储或设置下一个响应者,而是默认返回nil。子类必须重写此方法才能设置下一个响应者。UIView通过返回管理它的UIViewController对象(如果有)或它的超级视图(如果没有)来实现此方法;UIViewController通过返回其视图的超级视图来实现该方法。UIWindow返回应用程序对象,而UIApplication返回nil。

因此,如果递归一个视图,nextResponder直到它的类型为UIViewController,则您将拥有任何视图的父视图控制器。

请注意,它可能仍然没有父视图控制器。但仅当视图不属于viewController的视图层次结构的一部分时。

Swift 3和Swift 4.1扩展:

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

Swift 2扩展名:

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

Objective-C类别:

@interface UIView (mxcl)
- (UIViewController *)parentViewController;
@end

@implementation UIView (mxcl)
- (UIViewController *)parentViewController {
    UIResponder *responder = self;
    while ([responder isKindOfClass:[UIView class]])
        responder = [responder nextResponder];
    return (UIViewController *)responder;
}
@end

此宏避免类别污染:

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

只是一件事:如果您担心类别污染,只需将其定义为静态函数而不是宏即可。野蛮的类型转换也是危险的,最重要的是,宏可能不正确,但不确定。
mojuba 2015年

宏有效,我仅个人使用宏版本。我开始了许多项目,并有了一个标头,就随处都插入了这些宏实用程序函数,为我节省了时间。如果您不喜欢宏,则可以将其改编为一个函数,但是静态函数似乎很繁琐,因为您必须在要使用它的每个文件中放入一个。似乎相反,您想要在标头中声明并在某个地方.m中定义的非静态函数吗?
mxcl 2015年

很高兴避免出现一些委托或通知。谢谢!
Ferran Maylinch 2015年

您应该将其扩展为UIResponder;)。非常具有启发性的帖子。
ScottyBlades

32

@andrey答案在一行中(在Swift 4.1中进行了测试):

extension UIResponder {
    public var parentViewController: UIViewController? {
        return next as? UIViewController ?? next?.parentViewController
    }
}

用法:

 let vc: UIViewController = view.parentViewController

parentViewController不能被定义public扩展名是否在同一个文件,你UIView可以将其设置fileprivate,它编译,但它不工作!😐–

一切正常。我已将其用于通用标题.xib文件中的后退按钮动作。
McDonal_11

23

仅出于调试目的,您可以调用_viewDelegate视图以获取其视图控制器。这是私有API,因此对于App Store而言并不安全,但对于调试它很有用。

其他有用的方法:

  • _viewControllerForAncestor-获得管理超级视图链中视图的第一个控制器。(感谢n00neimp0rtant)
  • _rootAncestorViewController -获取当前在窗口中设置其视图层次的祖先控制器。

这正是我提出这个问题的原因。显然,“ nextResponder”执行相同的操作,但是我很欣赏此答案提供的见解。我理解并喜欢MVC,但是调试是另一回事!
mbm29414

4
看来这仅适用于视图控制器的主视图,不适用于其任何子视图。_viewControllerForAncestor将遍历超级视图,直到找到属于视图控制器的第一个。
n00neimp0rtant

谢谢@ n00neimp0rtant!我赞成这个答案,以便其他人看到您的评论。
eyuelt

使用更多方法更新答案。
Leo Natan

1
这在调试时很有用。
evanchin

10

要获得具有UIView的UIViewController的引用,可以扩展UIResponder(这是UIView和UIViewController的超类),该类允许遍历响应者链并到达UIViewController(否则返回nil)。

extension UIResponder {
    func getParentViewController() -> UIViewController? {
        if self.nextResponder() is UIViewController {
            return self.nextResponder() as? UIViewController
        } else {
            if self.nextResponder() != nil {
                return (self.nextResponder()!).getParentViewController()
            }
            else {return nil}
        }
    }
}

//Swift 3
extension UIResponder {
    func getParentViewController() -> UIViewController? {
        if self.next is UIViewController {
            return self.next as? UIViewController
        } else {
            if self.next != nil {
                return (self.next!).getParentViewController()
            }
            else {return nil}
        }
    }
}

let vc = UIViewController()
let view = UIView()
vc.view.addSubview(view)
view.getParentViewController() //provide reference to vc

4

Swift 3中的快速通用方法:

extension UIResponder {
    func parentController<T: UIViewController>(of type: T.Type) -> T? {
        guard let next = self.next else {
            return nil
        }
        return (next as? T) ?? next.parentController(of: T.self)
    }
}

//Use:
class MyView: UIView {
    ...
    let parentController = self.parentController(of: MyViewController.self)
}

2

如果您不熟悉代码,并且想要查找与给定视图相对应的ViewController核心,则可以尝试:

  1. 在调试中运行应用
  2. 导航到屏幕
  3. 启动视图检查器
  4. 获取您要查找的视图(或者更好的子视图)
  5. 从右侧窗格中获取地址(例如0x7fe523bd3000)
  6. 在调试控制台中开始编写命令:
    po(UIView *)0x7fe523bd3000
    po [(UIView *)0x7fe523bd3000 nextResponder]
    po [[(UIView *)0x7fe523bd3000 nextResponder] nextResponder]
    po [[[((UIView *)0x7fe523bd3000 nextResponder] nextResponder] nextResponder]
    ...

在大多数情况下,您将获得UIView,但有时会有基于UIViewController的类。


1

我认为您可以将水龙头传播到视图控制器并让其处理。这是更可接受的方法。至于从视图控制器访问视图控制器,您应该维护对视图控制器的引用,因为没有其他方法。请参阅此线程,它可能会有所帮助: 从视图访问视图控制器


如果您只有少数几个视图,并且关闭了所有视图,并且需要调用viewWillDisappear,那么将该视图检测到轻敲要比将轻敲交给视图控制器并检查视图控制器要容易得多用所有视图来查看是哪一个?
mahboudz

0

Swift 3.0的更多类型安全代码

extension UIResponder {
    func owningViewController() -> UIViewController? {
        var nextResponser = self
        while let next = nextResponser.next {
            nextResponser = next
            if let vc = nextResponser as? UIViewController {
                return vc
            }
        }
        return nil
    }
}

0

las,除非您将视图子类化并为其提供实例属性或类似属性,一旦将视图添加到场景中,该属性就将视图控制器引用存储在视图中,否则这是不可能的...

在大多数情况下,由于大多数视图控制器是程序员的知名实体,程序员负责将所有子视图添加到ViewController的View中,因此很容易解决本文的原始问题;-)这就是为什么我认为Apple从不费心添加该属性。


0

有点晚了,但是这里有一个扩展,使您能够找到任何类型的响应器,包括ViewController。

extension NSObject{
func findNext(type: AnyClass) -> Any{
    var resp = self as! UIResponder

    while !resp.isKind(of: type.self) && resp.next != nil
    {
        resp = resp.next!
    }

    return resp
  }                       
}

-1

如果设置了断点,则可以将其粘贴到调试器中以打印视图层次结构:

po [[UIWindow keyWindow] recursiveDescription]

您应该可以在混乱中的某个地方找到视图的父级:)


recursiveDescription仅打印视图层次结构,不打印视图控制器。
艾伦·泽伊诺

1
从中,您可以确定viewcontroller @AlanZeino
Akshay
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.