给定一个视图,如何得到它的 viewController?

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

139567 次浏览

是的,superview是包含您的视图的视图。您的视图不应该确切地知道哪个是它的视图控制器,因为那会破坏 MVC 原则。

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

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

我认为您可以将这个点击传播到视图控制器,并让它来处理它。这是更可接受的方法。至于从视图控制器的视图访问视图控制器,您应该维护对视图控制器的引用,因为没有其他方法。看看这个帖子,可能会有帮助: 从视图访问视图控制器

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

po [[UIWindow keyWindow] recursiveDescription]

您应该能够在这个混乱中找到视图的父级:)

仅出于调试目的,您可以对视图调用 _viewDelegate来获取它们的视图控制器。这是私有 API,所以对于 AppStore 来说不安全,但是对于调试来说是有用的。

其他有用的方法:

  • 获取第一个管理 在监督链中的一个观点。(谢谢,不重要)
  • _rootAncestorViewController - get the ancestor controller whose 视图层次结构当前在窗口中设置。

来自 nextResponderUIResponder文档:

UIResponder 类不会自动存储或设置下一个响应者,而是默认返回 null。子类必须重写此方法以设置下一个响应器。UIViewController 通过返回其视图的超视图来实现该方法; UIWindow 返回应用程序对象,而 UIApplication 返回 null。

因此,如果您递归一个视图的 nextResponder直到它的类型为 UIViewController,那么您就拥有了任何视图的父 viewController。

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

Swift 3和 Swift 4.1分机:

extension UIView {
var parentViewController: UIViewController? {
// Starts from next (As we know self is not a UIViewController).
var parentResponder: UIResponder? = self.next
while parentResponder != nil {
if let viewController = parentResponder as? UIViewController {
return viewController
}
parentResponder = parentResponder?.next
}
return nil
}
}

Swift 2扩展:

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

目标 C 类:

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


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

这种宏观规划避免了类别污染:

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

如果您不熟悉该代码,并且希望找到与给定视图对应的 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。

为了获得 UIViewController 拥有 UIView 的参考,你可以扩展 UIResponder (UIResponder 是 UIView 和 UIViewController 的超类) ,它允许向上通过响应器链,从而到达 UIViewController (否则返回 null)。

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

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

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

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

在大多数情况下——很容易解决这篇文章的原始问题,因为大多数视图控制器是程序员熟知的实体,他们负责添加任何子视图到 ViewController 的视图中; ——这就是为什么我猜想苹果从来没有费心添加这个属性。

有点晚了,但是这里有一个扩展,可以让您找到任何类型的响应者,包括 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
}
}

@ andrey 一句话回答(在 Swift 4.1测试) :

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

用途:

 let vc: UIViewController = view.parentViewController