ios 从 UIView 到 UIViewController?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1340434/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Get to UIViewController from UIView?
提问by bertrandom
Is there a built-in way to get from a UIView
to its UIViewController
? I know you can get from UIViewController
to its UIView
via [self view]
but I was wondering if there is a reverse reference?
有没有内置的方法可以从 aUIView
到 its UIViewController
?我知道你可以从UIViewController
它的UIView
通过,[self view]
但我想知道是否有反向引用?
采纳答案by pgb
Since this has been the accepted answer for a long time, I feel I need to rectify it with a better answer.
由于长期以来这一直是公认的答案,我觉得我需要用更好的答案来纠正它。
Some comments on the need:
关于需求的一些评论:
- Your view should not need to access the view controller directly.
- The view should instead be independent of the view controller, and be able to work in different contexts.
- Should you need the view to interface in a way with the view controller, the recommended way, and what Apple does across Cocoa is to use the delegate pattern.
- 您的视图不需要直接访问视图控制器。
- 视图应该独立于视图控制器,并且能够在不同的上下文中工作。
- 如果您需要视图以某种方式与视图控制器交互,推荐的方式以及 Apple 在 Cocoa 中所做的就是使用委托模式。
An example of how to implement it follows:
如何实现它的示例如下:
@protocol MyViewDelegate < NSObject >
- (void)viewActionHappened;
@end
@interface MyView : UIView
@property (nonatomic, assign) MyViewDelegate delegate;
@end
@interface MyViewController < MyViewDelegate >
@end
The view interfaces with its delegate (as UITableView
does, for instance) and it doesn't care if its implemented in the view controller or in any other class that you end up using.
视图与其委托接口(UITableView
例如),它并不关心它是在视图控制器中还是在您最终使用的任何其他类中实现的。
My original answer follows: I don't recommend this, neither the rest of the answers where direct access to the view controller is achieved
我的原始答案如下:我不建议这样做,其余的答案都不推荐直接访问视图控制器
There is no built-in way to do it. While you can get around it by adding a IBOutlet
on the UIView
and connecting these in Interface Builder, this is not recommended. The view should not know about the view controller. Instead, you should do as @Phil M suggests and create a protocol to be used as the delegate.
没有内置的方法可以做到这一点。虽然您可以通过在 Interface BuilderIBOutlet
上添加UIView
并连接它们来解决这个问题,但不建议这样做。视图不应该知道视图控制器。相反,您应该按照@Phil M 的建议进行操作,并创建一个用作委托的协议。
回答by Phil M
Using the example posted by Brock, I modified it so that it is a category of UIView instead UIViewController and made it recursive so that any subview can (hopefully) find the parent UIViewController.
使用 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
To use this code, add it into an new class file (I named mine "UIKitCategories") and remove the class data... copy the @interface into the header, and the @implementation into the .m file. Then in your project, #import "UIKitCategories.h" and use within the UIView code:
要使用此代码,请将其添加到一个新的类文件中(我将其命名为“UIKitCategories”)并删除类数据...将@interface 复制到标题中,将 @implementation 复制到 .m 文件中。然后在您的项目中,#import "UIKitCategories.h" 并在 UIView 代码中使用:
// from a UIView subclass... returns nil if UIViewController not available
UIViewController * myController = [self firstAvailableUIViewController];
回答by Brock
UIView
is a subclass of UIResponder
. UIResponder
lays out the method -nextResponder
with an implementation that returns nil
. UIView
overrides this method, as documented in UIResponder
(for some reason instead of in UIView
) as follows: if the view has a view controller, it is returned by -nextResponder
. If there is no view controller, the method will return the superview.
UIView
是 的子类UIResponder
。UIResponder
勾画出方法-nextResponder
与收益的实现nil
。UIView
重写此方法,如UIResponder
(出于某种原因而不是 in UIView
)中所述:如果视图具有视图控制器,则由 返回-nextResponder
。如果没有视图控制器,该方法将返回超级视图。
Add this to your project and you're ready to roll.
将此添加到您的项目中,您就可以开始使用了。
@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
Now UIView
has a working method for returning the view controller.
现在UIView
有一个返回视图控制器的工作方法。
回答by de.
I would suggest a more lightweight approach for traversing the complete responder chain without having to add a category on UIView:
我建议使用更轻量级的方法来遍历完整的响应者链,而无需在 UIView 上添加类别:
@implementation MyUIViewSubclass
- (UIViewController *)viewController {
UIResponder *responder = self;
while (![responder isKindOfClass:[UIViewController class]]) {
responder = [responder nextResponder];
if (nil == responder) {
break;
}
}
return (UIViewController *)responder;
}
@end
回答by Constantino Tsarouhas
Combining several already given answers, I'm shipping on it as well with my implementation:
结合几个已经给出的答案,我也将其与我的实现一起发布:
@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
The category is part of my ARC-enabledstatic library that I ship on every application I create. It's been tested several times and I didn't find any problems or leaks.
该类别是我在我创建的每个应用程序中提供的启用 ARC 的静态库的一部分。它已经过多次测试,我没有发现任何问题或泄漏。
P.S.: You don't need to use a category like I did if the concerned view is a subclass of yours. In the latter case, just put the method in your subclass and you're good to go.
PS:如果相关视图是您的子类,则您不需要像我一样使用类别。在后一种情况下,只需将该方法放在您的子类中即可。
回答by Ushox
Even though this can technically be solved as pgbrecommends, IMHO, this is a design flaw. The view should not need to be aware of the controller.
尽管这在技术上可以按照pgb 的建议解决,恕我直言,这是一个设计缺陷。视图不需要知道控制器。
回答by Varun Naharia
I modified deanswer so I can pass any view, button, label etc. to get it's parent UIViewController
. Here is my code.
我修改德回答,所以我可以传递任何视图,按钮,标签等,以得到它的母公司UIViewController
。这是我的代码。
+(UIViewController *)viewController:(id)view {
UIResponder *responder = view;
while (![responder isKindOfClass:[UIViewController class]]) {
responder = [responder nextResponder];
if (nil == responder) {
break;
}
}
return (UIViewController *)responder;
}
Edit Swift 3 Version
编辑 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)!
}
Edit 2:- Swift Extention
编辑 2:- Swift 扩展
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)!
}
}
回答by Gabriel
Don't forget that you can get access to the root view controller for the window that the view is a subview of. From there, if you are e.g. using a navigation view controller and want to push a new view onto it:
不要忘记,您可以访问视图是其子视图的窗口的根视图控制器。从那里,如果您使用导航视图控制器并希望将新视图推送到它上面:
[[[[self window] rootViewController] navigationController] pushViewController:newController animated:YES];
You will need to set up the rootViewController property of the window properly first, however. Do this when you first create the controller e.g. in your app delegate:
但是,您首先需要正确设置窗口的 rootViewController 属性。在您第一次创建控制器时执行此操作,例如在您的应用程序委托中:
-(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];
}
回答by PapaSmurf
While these answers are technically correct, including Ushox, I think the approvedway is to implement a new protocol or re-use an existing one. A protocol insulates the observer from the observed, sort of like putting a mail slot in between them. In effect, that is what Gabriel does via the pushViewController method invocation; the view "knows" that it is proper protocol to politely ask your navigationController to push a view, since the viewController conforms to the navigationController protocol. While you can create your own protocol, just using Gabriel's example and re-using the UINavigationController protocol is just fine.
虽然这些答案在技术上是正确的,包括 Ushox,但我认为批准的方法是实施新协议或重用现有协议。协议将观察者与被观察者隔离开来,有点像在它们之间放置一个邮槽。实际上,这就是 Gabriel 通过 pushViewController 方法调用所做的;视图“知道”礼貌地要求您的 navigationController 推送视图是正确的协议,因为 viewController 符合 navigationController 协议。虽然您可以创建自己的协议,但仅使用 Gabriel 的示例并重新使用 UINavigationController 协议就可以了。
回答by Kevin R
I stumbled upon a situation where I have a small component I want to reuse, and added some code in a reusable view itself(it's really not much more than a button that opens a PopoverController
).
我偶然发现了一种情况,我有一个要重用的小组件,并在可重用视图本身中添加了一些代码(它实际上只不过是一个打开 的按钮PopoverController
)。
While this works fine in the iPad (the UIPopoverController
presents itself, therefor needs no reference to a UIViewController
), getting the same code to work means suddenly referencing your presentViewController
from your UIViewController
. Kinda inconsistent right?
虽然这在 iPad 上运行良好(UIPopoverController
呈现本身,因此不需要引用 a UIViewController
),但让相同的代码工作意味着突然presentViewController
从你的UIViewController
. 有点不一致吧?
Like mentioned before, it's not the best approach to have logic in your UIView. But it felt really useless to wrap the few lines of code needed in a separate controller.
如前所述,这不是在 UIView 中设置逻辑的最佳方法。但是将所需的几行代码包装在单独的控制器中感觉真的没用。
Either way, here's a swift solution, which adds a new property to any 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
}
}