从UIView获取UIViewController?
有没有一种内置的方式从UIView
到它的UIViewController
? 我知道你可以通过[self view]
从UIViewController
到它的UIView
,但是我想知道是否有反向引用?
既然这个问题已经被人们接受了很长时间,我觉得我需要更好的回答。
关于需求的一些评论:
- 您的视图不需要直接访问视图控制器。
- 视图应该独立于视图控制器,并能够在不同的环境下工作。
- 如果您需要视图与视图控制器进行交互,推荐的方式,以及苹果在Cocoa上所做的是使用委托模式。
如何实现它的一个例子如下:
@protocol MyViewDelegate < NSObject > - (void)viewActionHappened; @end @interface MyView : UIView @property (nonatomic, assign) MyViewDelegate delegate; @end @interface MyViewController < MyViewDelegate > @end
视图接口与它的委托(如UITableView
所做的那样),它并不关心它是否在视图控制器或其他任何你最终使用的类中实现。
我的原始答案如下: 我不build议这样做,直接访问视图控制器的其余答案都无法实现
没有内置的方法来做到这一点。 虽然可以通过在UIView
上添加一个IBOutlet
并在Interface Builder中连接它们来解决这个问题,但不build议这样做。 该视图不应该了解视图控制器。 相反,你应该像@Phil M所build议的那样去创build一个协议来作为委托。
使用Brock发布的例子,我修改了它,使它是一个UIView类别,而不是UIViewController,并使其recursion,以便任何子视图可以(希望)find父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
要使用这个代码,将它添加到一个新的类文件(我命名为我的“UIKitCategories”)并删除类数据…将@interface复制到标题中,@implementation复制到.m文件中。 然后在你的项目中,#import“UIKitCategories.h”并在UIView代码中使用:
// from a UIView subclass... returns nil if UIViewController not available UIViewController * myController = [self firstAvailableUIViewController];
UIView
是UIResponder
一个子类。 UIResponder
用一个返回nil
的实现来展示方法-nextResponder
。 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
有一个返回视图控制器的工作方法。
我会build议一个更轻量级的方法遍历完整的响应者链,而不必在UIView上添加一个类别:
@implementation MyUIViewSubclass - (UIViewController *)viewController { UIResponder *responder = self; while (![responder isKindOfClass:[UIViewController class]]) { responder = [responder nextResponder]; if (nil == responder) { break; } } return (UIViewController *)responder; } @end
结合几个已经给出的答案,我运送它以及我的实施:
@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
该类别是我创build的每个应用程序所附带的启用ARC的静态库的一部分。 它已经过多次testing,我没有发现任何问题或泄漏。
PS:如果有关的视图是你的子类,你不需要像我这样使用类别。 在后一种情况下,只要把这个方法放在你的子类中,你就可以走了。
即使这可以从技术上来解决,因为pgbbuild议,恕我直言,这是一个devise缺陷。 视图不需要知道控制器。
不要忘记,您可以访问视图是子视图的窗口的根视图控制器。 从那里,如果你是例如使用一个导航视图控制器,并想推新视图:
[[[[self window] rootViewController] navigationController] pushViewController:newController animated:YES];
但是,您需要先正确设置窗口的rootViewController属性。 当你第一次创build控制器,例如在你的应用程序委托中,这样做:
-(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]; }
虽然这些答案在技术上是正确的,包括Ushox,但我认为批准的方法是实施新的协议或重新使用现有的协议。 协议将观察者与观察者隔离,就像在他们之间放置邮件插槽一样。 实际上,这就是Gabriel通过pushViewController方法调用所做的事情; 该视图“知道”礼貌地要求您的navigationController推送视图是合适的协议,因为viewController符合navigationController协议。 虽然你可以创build自己的协议,但只要使用Gabriel的例子并重新使用UINavigationController协议就可以了。
我修改了答案,以便我可以传递任何视图,button,标签等,以获得它的父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)! }
我不认为在某些情况下找出谁是视图控制器是“坏”的主意。 可能是一个坏主意是保存对这个控制器的引用,因为它可能随着超级视图的改变而改变。 在我的情况下,我有一个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; }
最简单的while循环来查找viewController。
-(UIViewController*)viewController { UIResponder *nextResponder = self; do { nextResponder = [nextResponder nextResponder]; if ([nextResponder isKindOfClass:[UIViewController class]]) return (UIViewController*)nextResponder; } while (nextResponder != nil); return nil; }
我偶然发现了一个我想要重用的PopoverController
,并且在可重用视图本身中添加了一些代码(它实际上并不比打开PopoverController
的buttonPopoverController
)。
虽然这在iPad中工作正常( UIPopoverController
自身呈现,因此不需要引用UIViewController
),获得相同的代码工作意味着突然引用您的UIViewController
的presentViewController
。 有点不一致的权利?
就像之前提到的那样,在UIView中使用逻辑不是最好的方法。 但是在单独的控制器中包装所需的几行代码感觉真的没用。
无论哪种方式,这是一个迅速的解决scheme,它添加一个新的属性到任何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 } }
这不是直接回答这个问题,而是对这个问题的意图做出假设。
如果你有一个视图,并且在这个视图中你需要调用另一个对象的方法,比如说视图控制器,那么你可以使用NSNotificationCenter。
首先在头文件中创build您的通知string
#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视图的子视图,如果你知道自己的层次(UIView),你也可以使用: id nextResponder = [[self superview] nextResponder];
…
这当然是一个坏主意和错误的devise,但我相信我们都可以享受@Phil_M提出的最佳答案的Swift解决scheme:
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
我的解决scheme可能会被认为是一种假的,但我有一个mayoneez类似的情况(我想切换视图响应在EAGLView中的手势),我得到了EAGL的视图控制器这样:
EAGLViewController *vc = ((EAGLAppDelegate*)[[UIApplication sharedApplication] delegate]).viewController;
我认为有这样一个观察者需要通知观察者的情况。
我看到一个类似的问题,UIViewController中的UIView响应一个情况,它需要先告诉它的父视图控制器隐藏后退button,然后在完成时告诉父视图控制器它需要从栈中popup。
我一直在试图与代表没有成功。
我不明白为什么这应该是一个坏主意?
另一个简单的方法是拥有自己的视图类,并在视图类中添加视图控制器的属性。 通常视图控制器创build视图,这是控制器可以将其自身设置为属性的位置。 基本上,它不是四处搜寻(有点黑客)的控制器,让控制器自己设置为视图 – 这很简单,但是有道理的,因为它是控制器,控制视图。
如果你的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]];
不可能。
我所做的是将UIViewController指针传递给UIView(或适当的inheritance)。 对不起,我不能帮助IB解决这个问题,因为我不相信IB。
要回答第一个评论者:有时你需要知道是谁打电话给你的,因为它决定你能做什么。 例如,一个数据库,你可能只有读取权限或读/写…