给定一个视图,我如何获取它的视图控制器?
问题的出现原因是根据MVC原则,一个view不应该知道它所属的viewController,因为这样会破坏MVC的原则。但是,有时候我们需要通过一个view来获取它所属的viewController,以便传递一些消息给它。
解决方法是,我们应该在view中保持一个指向它所属的viewController的指针,而不是直接知道它所属的viewController。这样,view可以通过这个指针来调用viewController的方法或者传递消息给它。
虽然有人可能会认为这样做会破坏MVC的原则,因为一个view只应该关注于展示数据,而不需要知道它所属的viewController。但是,实际上,获取一个view所属的viewController是一个自然而然的需求,并且也有一些方法可以实现这个目标。
根据MVC原则,一个view不应该直接知道它所属的viewController,但是有时候我们需要通过一个view来获取它所属的viewController,以便传递消息给它。为了解决这个问题,我们可以在view中保持一个指向它所属的viewController的指针,以便能够方便地获取到viewController并进行操作。
问题出现的原因是,当我们有一个视图(view)时,我们想要获取它所属的视图控制器(viewController),但是在UIKit中并没有提供直接获取视图控制器的方法。因此,我们需要找到一种解决方法来获取视图所属的视图控制器。
解决方法是通过扩展(extension)UIResponder类来添加一个parentViewController属性。这个属性会尝试通过next属性(指向下一个响应者)来获取视图控制器,如果next属性指向的是一个UIViewController对象,则直接返回该对象,否则递归地调用parentViewController属性,直到找到视图控制器为止。
在Swift 4.1中,我们可以使用以下代码来实现这个扩展:
extension UIResponder {
public var parentViewController: UIViewController? {
return next as? UIViewController ?? next?.parentViewController
}
}
使用时,只需要调用视图的parentViewController属性即可获取其所属的视图控制器:
let vc: UIViewController = view.parentViewController
需要注意的是,如果在与UIView相同的文件中定义这个扩展,parentViewController属性不能定义为public,可以使用fileprivate修饰符。如果不这样做,虽然代码可以编译通过,但是无法正常工作。
在Xamarin中,可以使用以下代码来实现这个扩展:
public static class Extensions { public static UIViewController ParentViewController(this UIResponder view) { return (view.NextResponder as UIViewController) ?? view.NextResponder?.ParentViewController(); } }
给定一个视图,如何获取它的viewController?
在UIResponder文档中,关于nextResponder的说明如下:
UIResponder类不会自动存储或设置下一个响应者,而是默认返回nil。子类必须重写此方法来设置下一个响应者。UIView通过返回管理它的UIViewController对象(如果有)或其父视图(如果没有)来实现此方法;UIViewController通过返回其视图的父视图来实现此方法;UIWindow返回应用程序对象,UIApplication返回nil。
因此,如果递归一个视图的nextResponder直到它的类型为UIViewController,那么你就有了任何视图的父viewController。
请注意,它可能仍然没有父视图控制器。但只有当视图不属于viewController的视图层次结构时才会如此。
Swift 3和Swift 4.1扩展:
extension UIView {
var parentViewController: UIViewController? {
// 从next开始(因为我们知道self不是一个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
}
}
Objective-C分类:
UIView (mxcl) - (UIViewController *)parentViewController; UIView (mxcl) - (UIViewController *)parentViewController { UIResponder *responder = [self nextResponder]; while (responder != nil) { if ([responder isKindOfClass:[UIViewController class]]) { return (UIViewController *)responder; } responder = [responder nextResponder]; } return nil; }
这个宏避免了类别污染:
#define UIViewParentController(__view) ({ \ UIResponder *__responder = __view; \ while ([__responder isKindOfClass:[UIView class]]) \ __responder = [__responder nextResponder]; \ (UIViewController *)__responder; \ })
只有一点需要注意:如果你担心类别污染,只需将其定义为静态函数而不是宏。此外,强制类型转换是危险的,此外,宏可能不正确,但我不确定。
这个宏是有效的,我个人只使用宏版本。我开始了很多项目,有一个我随处放置的头文件,里面有一堆这样的宏实用程序函数。这样可以节省时间。如果你不喜欢宏,你可以将其改写为一个函数,但是静态函数似乎很麻烦,因为你必须在想要使用它的每个文件中都放置一个。似乎更好的方法是在头文件中声明一个非静态函数,在某个地方的.m文件中定义它。
很高兴避免一些委托或通知。谢谢!
你应该将其作为UIResponder的扩展;)。非常有启发的帖子。