Answers:
由于这是很长一段时间以来一直被接受的答案,我觉得我需要用更好的答案来纠正它。
关于需要的一些评论:
如何实现它的示例如下:
@protocol MyViewDelegate < NSObject >
- (void)viewActionHappened;
@end
@interface MyView : UIView
@property (nonatomic, assign) MyViewDelegate delegate;
@end
@interface MyViewController < MyViewDelegate >
@end
视图与其委托(例如UITableView
,也是如此)相连接,并且它不在乎是否在视图控制器或最终使用的任何其他类中实现。
我的原始答案如下:我不建议这样做,也没有实现直接访问视图控制器的其余答案
没有内置的方法可以做到这一点。尽管您可以通过在IBOutlet
上添加UIView
并在Interface Builder中进行连接来解决该问题,但不建议这样做。视图不应了解视图控制器。相反,您应该按照@Phil M的建议进行操作,并创建要用作委托的协议。
使用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
要使用此代码,请将其添加到新的类文件(我将其命名为“ 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
布置方法。重写此方法,如下所述(出于某种原因而不是in中所述):如果视图具有视图控制器,则由返回。如果没有视图控制器,则该方法将返回超级视图。-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具有递归的答案是必须走的路。
我建议使用一种更轻量级的方法来遍历完整的响应者链,而不必在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
类别是我创建的每个应用程序附带的支持ARC的静态库的一部分。它已经过多次测试,我没有发现任何问题或泄漏。
PS:如果相关视图是您的子类,则不需要像我一样使用类别。在后一种情况下,只需将方法放在子类中就可以了。
我修改了de answer,以便可以传递任何视图,按钮,标签等作为其父项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:-快速扩展
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)!
}
}
不要忘记,您可以访问该视图作为其子视图的窗口的根视图控制器。从那里开始,例如,如果您正在使用导航视图控制器,并想在其上推送新视图:
[[[[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];
}
我偶然发现了一个我想重用的小组件的情况,并在可重用的视图本身中添加了一些代码(实际上只不过是一个打开a的按钮而已PopoverController
)。
尽管这在iPad上可以正常工作(目前,UIPopoverController
它本身不需要引用UIViewController
),但使相同的代码起作用意味着您突然presentViewController
从中引用了您UIViewController
。有点矛盾吧?
如前所述,在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
}
}
我认为在某些情况下找出谁是视图控制器并不是一个“坏”主意。一个坏主意是保存对该控制器的引用,因为它可能会随着超级视图的更改而更改。就我而言,我有一个遍历响应者链的吸气剂。
//。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;
}
查找viewController的最简单的while循环。
-(UIViewController*)viewController
{
UIResponder *nextResponder = self;
do
{
nextResponder = [nextResponder nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]])
return (UIViewController*)nextResponder;
} while (nextResponder != nil);
return nil;
}
(比其他答案更简洁)
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
。
这不是直接回答问题,而是假设问题的意图。
如果您有一个视图,并且在该视图中需要在另一个对象上调用方法,例如视图控制器,则可以改用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,以便可以对数据进行处理。
这肯定是一个坏主意和错误的设计,但是我敢肯定我们都可以享受@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
更快的解决方案
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”表示“功能更强大”,那么我想它会更快捷。
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{
}
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
}
}
我的解决方案可能会被认为是虚假的,但是我遇到了与mayoneez类似的情况(我想根据EAGLView中的手势切换视图),并且这样获得了EAGL的视图控制器:
EAGLViewController *vc = ((EAGLAppDelegate*)[[UIApplication sharedApplication] delegate]).viewController;
EAGLViewController *vc = [(EAGLAppDelegate *)[UIApplication sharedApplication].delegate viewController];
。
ClassName *object
-带星号。
如果您不打算将其上传到App Store,也可以使用UIView的私有方法。
@interface UIView(Private)
- (UIViewController *)_viewControllerForAncestor;
@end
// Later in the code
UIViewController *vc = [myView _viewControllerForAncestor];
如果您的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(或适当的继承)。对不起,因为我不相信IB,所以我无法通过IB方法解决该问题。
要回答第一个评论者:有时您确实需要知道谁打给您,因为它决定了您可以做什么。例如,对于数据库,您可能仅具有读访问权限或具有读/写权限。