【发布时间】:2010-11-23 08:23:41
【问题描述】:
是否有从UIView 到它的UIViewController 的内置方法?我知道你可以通过[self view] 从UIViewController 到它的UIView,但我想知道是否有反向引用?
【问题讨论】:
标签: ios objective-c cocoa-touch uiview uiviewcontroller
是否有从UIView 到它的UIViewController 的内置方法?我知道你可以通过[self view] 从UIViewController 到它的UIView,但我想知道是否有反向引用?
【问题讨论】:
标签: ios objective-c cocoa-touch uiview uiviewcontroller
由于这是很长时间以来被接受的答案,我觉得我需要用更好的答案来纠正它。
关于需要的一些cmets:
如何实现它的示例如下:
@protocol MyViewDelegate < NSObject >
- (void)viewActionHappened;
@end
@interface MyView : UIView
@property (nonatomic, assign) MyViewDelegate delegate;
@end
@interface MyViewController < MyViewDelegate >
@end
视图与其委托接口(例如UITableView 所做的那样)并且它不关心它是在视图控制器中实现还是在您最终使用的任何其他类中实现。
我的原始答案如下:我不推荐这个,其他可以直接访问视图控制器的答案也不推荐
没有内置的方法可以做到这一点。虽然您可以通过在 UIView 上添加 IBOutlet 并在 Interface Builder 中连接它们来绕过它,但不建议这样做。视图不应该知道视图控制器。相反,您应该按照@Phil M 的建议进行操作,并创建一个用作委托的协议。
【讨论】:
没有办法。
我所做的是将 UIViewController 指针传递给 UIView(或适当的继承)。很抱歉,我无法通过 IB 方法解决问题,因为我不相信 IB。
回答第一个评论者:有时您确实需要知道谁给您打电话,因为它决定了您可以做什么。例如,对于数据库,您可能只有读取权限或读/写权限...
【讨论】:
尽管这可以按照 pgb 的建议在技术上解决,恕我直言,这是一个设计缺陷。视图不需要知道控制器。
【讨论】:
我的解决方案可能会被认为是虚假的,但我遇到了与 mayoneez 类似的情况(我想切换视图以响应 EAGLView 中的手势),并且我通过这种方式获得了 EAGL 的视图控制器:
EAGLViewController *vc = ((EAGLAppDelegate*)[[UIApplication sharedApplication] delegate]).viewController;
【讨论】:
EAGLViewController *vc = [(EAGLAppDelegate *)[UIApplication sharedApplication].delegate viewController];.
ClassName *object - 带有星号。
UIView 是UIResponder 的子类。 UIResponder 列出了方法 -nextResponder 以及返回 nil 的实现。 UIView 覆盖此方法,如 UIResponder 中所述(出于某种原因而不是 UIView 中),如下所示:如果视图具有视图控制器,则由 -nextResponder 返回。如果没有视图控制器,该方法将返回父视图。
将此添加到您的项目中,您就可以开始使用了。
@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
现在UIView 有一个返回视图控制器的工作方法。
【讨论】:
UIView 和 UIViewController 之间的响应者链中没有任何内容时才有效。 Phil M 以递归为特色的答案是要走的路。
使用 Brock 发布的示例,我对其进行了修改,使其成为 UIView 的类别,而不是 UIViewController,并使其递归,以便任何子视图都可以(希望)找到父 UIViewController。
@interface UIView (FindUIViewController)
- (UIViewController *) firstAvailableUIViewController;
@end
@implementation UIView (FindUIViewController)
- (UIViewController *) firstAvailableUIViewController {
UIResponder *responder = [self nextResponder];
while (responder != nil) {
if ([responder isKindOfClass:[UIViewController class]]) {
return (UIViewController *)responder;
}
responder = [responder nextResponder];
}
return nil;
}
@end
要使用此代码,请将其添加到一个新的类文件中(我将其命名为“UIKitCategories”)并删除类数据...将@interface 复制到标题中,并将@implementation 复制到.m 文件中。然后在您的项目中,#import "UIKitCategories.h" 并在 UIView 代码中使用:
// from a UIView subclass... returns nil if UIViewController not available
UIViewController * myController = [self firstAvailableUIViewController];
【讨论】:
不要忘记,您可以访问该视图是其子视图的窗口的根视图控制器。从那里,如果你是例如使用导航视图控制器并希望将新视图推送到其上:
[[[[self window] rootViewController] navigationController] pushViewController:newController animated:YES];
但是,您需要先正确设置窗口的 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];
}
【讨论】:
[[self navigationController] view]是窗口的“主”(子)视图,所以窗口的rootViewController属性必须设置为@ 987654326@ 立即控制“主”视图。
虽然这些答案在技术上是正确的,包括 Ushox,但我认为已批准的方法是实施新协议或重用现有协议。协议将观察者与被观察者隔离开来,有点像在它们之间放置一个邮件槽。实际上,这就是 Gabriel 通过 pushViewController 方法调用所做的事情;视图“知道”礼貌地要求您的 navigationController 推送视图是正确的协议,因为 viewController 符合 navigationController 协议。虽然您可以创建自己的协议,但只需使用 Gabriel 的示例并重新使用 UINavigationController 协议就可以了。
【讨论】:
我认为有一种情况,被观察者需要通知观察者。
我看到一个类似的问题,即 UIViewController 中的 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
【讨论】:
我不认为在某些情况下找出谁是视图控制器是“坏”的主意。保存对这个控制器的引用可能是一个坏主意,因为它可能会随着超级视图的变化而改变。 在我的例子中,我有一个遍历响应者链的 getter。
//.h
@property (nonatomic, readonly) UIViewController * viewController;
//.m
- (UIViewController *)viewController
{
for (UIResponder * nextResponder = self.nextResponder;
nextResponder;
nextResponder = nextResponder.nextResponder)
{
if ([nextResponder isKindOfClass:[UIViewController class]])
return (UIViewController *)nextResponder;
}
// Not found
NSLog(@"%@ doesn't seem to have a viewController". self);
return nil;
}
【讨论】:
结合几个已经给出的答案,我也将它与我的实现一起发布:
@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
该类别是我的启用 ARC 静态库的一部分,我在我创建的每个应用程序中都提供了该库。经过多次测试,我没有发现任何问题或泄漏。
P.S.:如果相关视图是您的子类,您不需要像我一样使用类别。在后一种情况下,只需将方法放入您的子类中即可。
【讨论】:
另一种简单的方法是拥有自己的视图类并在视图类中添加视图控制器的属性。通常,视图控制器创建视图,控制器可以在其中将自己设置为属性。基本上,它不是搜索控制器(有点黑客),而是让控制器将自己设置为视图 - 这很简单但很有意义,因为它是“控制”视图的控制器。
【讨论】:
这并不直接回答问题,而是对问题的意图做出假设。
如果你有一个视图并且在那个视图中你需要调用另一个对象的方法,比如视图控制器,你可以使用 NSNotificationCenter 来代替。
首先在头文件中创建通知字符串
#define SLCopyStringNotification @"ShaoloCopyStringNotification"
在您看来调用 postNotificationName:
- (IBAction) copyString:(id)sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:SLCopyStringNotification object:nil];
}
然后在您的视图控制器中添加一个观察者。我在 viewDidLoad 中这样做
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(copyString:)
name:SLCopyStringNotification
object:nil];
}
现在(也在同一个视图控制器中)实现你的方法 copyString: 如上面的@selector 所示。
- (IBAction) copyString:(id)sender
{
CalculatorResult* result = (CalculatorResult*)[[PercentCalculator sharedInstance].arrayTableDS objectAtIndex:([self.viewTableResults indexPathForSelectedRow].row)];
UIPasteboard *gpBoard = [UIPasteboard generalPasteboard];
[gpBoard setString:result.stringResult];
}
我并不是说这是正确的方法,它只是看起来比运行第一响应者链更干净。我使用此代码在 UITableView 上实现 UIMenuController 并将事件传回 UIViewController,以便我可以对数据做一些事情。
【讨论】:
菲尔的回答:
在线:id nextResponder = [self nextResponder]; 如果 self(UIView) 不是 ViewController 视图的子视图,如果您知道 self(UIView) 的层次结构,您也可以使用:id nextResponder = [[self superview] nextResponder];...
【讨论】:
查找viewController的最简单的do while循环。
-(UIViewController*)viewController
{
UIResponder *nextResponder = self;
do
{
nextResponder = [nextResponder nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]])
return (UIViewController*)nextResponder;
} while (nextResponder != nil);
return nil;
}
【讨论】:
如果你的rootViewController是在AppDelegate类中设置的UINavigationViewController,那么
+ (UIViewController *) getNearestViewController:(Class) c {
NSArray *arrVc = [[[[UIApplication sharedApplication] keyWindow] rootViewController] childViewControllers];
for (UIViewController *v in arrVc)
{
if ([v isKindOfClass:c])
{
return v;
}
}
return nil;}
其中 c 需要视图控制器类。
用法:
RequiredViewController* rvc = [Utilities getNearestViewController:[RequiredViewController class]];
【讨论】:
这肯定是一个坏主意和错误的设计,但我相信我们都可以享受@Phil_M 提出的最佳答案的 Swift 解决方案:
static func firstAvailableUIViewController(fromResponder responder: UIResponder) -> UIViewController? {
func traverseResponderChainForUIViewController(responder: UIResponder) -> UIViewController? {
if let nextResponder = responder.nextResponder() {
if let nextResp = nextResponder as? UIViewController {
return nextResp
} else {
return traverseResponderChainForUIViewController(nextResponder)
}
}
return nil
}
return traverseResponderChainForUIViewController(responder)
}
如果您的意图是做一些简单的事情,例如显示模式对话框或跟踪数据,那并不能证明使用协议是合理的。我个人将此函数存储在实用程序对象中,您可以从任何实现 UIResponder 协议的对象中使用它:
if let viewController = MyUtilityClass.firstAvailableUIViewController(self) {}
感谢@Phil_M
【讨论】:
我修改了de 答案,因此我可以传递任何视图、按钮、标签等来获取它的父UIViewController。这是我的代码。
+(UIViewController *)viewController:(id)view {
UIResponder *responder = view;
while (![responder isKindOfClass:[UIViewController class]]) {
responder = [responder nextResponder];
if (nil == responder) {
break;
}
}
return (UIViewController *)responder;
}
编辑 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)!
}
编辑 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)!
}
}
【讨论】:
我偶然发现了一个我想要重用的小组件,并在可重用视图本身中添加了一些代码(它实际上只不过是一个打开 PopoverController 的按钮)。
虽然这在 iPad 上运行良好(UIPopoverController 出现,因此不需要引用 UIViewController),让相同的代码工作意味着突然从您的 UIViewController 引用您的 presentViewController。有点不一致吧?
如前所述,这不是在 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
}
}
【讨论】:
也许我来晚了。但在这种情况下,我不喜欢类别(污染)。我喜欢这种方式:
#define UIViewParentController(__view) ({ \
UIResponder *__responder = __view; \
while ([__responder isKindOfClass:[UIView class]]) \
__responder = [__responder nextResponder]; \
(UIViewController *)__responder; \
})
【讨论】:
swift 4 的更新版本:感谢@Phil_M 和@paul-slm
static func firstAvailableUIViewController(fromResponder responder: UIResponder) -> UIViewController? {
func traverseResponderChainForUIViewController(responder: UIResponder) -> UIViewController? {
if let nextResponder = responder.next {
if let nextResp = nextResponder as? UIViewController {
return nextResp
} else {
return traverseResponderChainForUIViewController(responder: nextResponder)
}
}
return nil
}
return traverseResponderChainForUIViewController(responder: responder)
}
【讨论】:
Swift 4 版本
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
}
使用示例
if let parent = self.view.parentViewController{
}
【讨论】:
更快捷的解决方案
extension UIView {
var parentViewController: UIViewController? {
for responder in sequence(first: self, next: { $0.next }) {
if let viewController = responder as? UIViewController {
return viewController
}
}
return nil
}
}
【讨论】:
sequence 位)。所以如果“swifty”的意思是“更实用”,那么我想它更快捷。
(比其他答案更简洁)
fileprivate extension UIView {
var firstViewController: UIViewController? {
let firstViewController = sequence(first: self, next: { $0.next }).first(where: { $0 is UIViewController })
return firstViewController as? UIViewController
}
}
我需要首先访问视图的用例UIViewController:我有一个环绕AVPlayer / AVPlayerViewController 的对象,我想提供一个简单的show(in view: UIView) 方法,该方法将嵌入AVPlayerViewController进入view。为此,我需要访问view 的UIViewController。
【讨论】:
如果你不打算将它上传到 App Store,你也可以使用 UIView 的私有方法。
@interface UIView(Private)
- (UIViewController *)_viewControllerForAncestor;
@end
// Later in the code
UIViewController *vc = [myView _viewControllerForAncestor];
【讨论】:
var parentViewController: UIViewController? {
let s = sequence(first: self) { $0.next }
return s.compactMap { $0 as? UIViewController }.first
}
【讨论】:
return 关键字?解决方案 1:
extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) { $0.next }
.first(where: { $0 is UIViewController })
.flatMap { $0 as? UIViewController }
}
}
解决方案 2:
extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) { $0.next }
.compactMap{ $0 as? UIViewController }
.first
}
}
【讨论】:
要获取给定视图的控制器,可以使用 UIFirstResponder 链。
customView.target(forAction: Selector("viewDidLoad"), withSender: nil)
【讨论】: