繁体   English   中英

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

[英]Given a view, how do I get its viewController?

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

来自nextResponderUIResponder文档:

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

斯威夫特 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; \
})

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

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

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

@andrey 一行回答(在Swift 4.1 中测试):

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

用法:

 let vc: UIViewController = view.parentViewController

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

其他有用的方法:

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

要获得对具有 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

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,那么你可以尝试:

  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 的类。

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

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

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

在大多数情况下 - 很容易解决这篇文章的原始问题,因为大多数视图控制器对于负责向 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
  }                       
}

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

po [[UIWindow keyWindow] recursiveDescription]

您应该能够在混乱的某处找到您的视图的父级:)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM