首页 文章

从UIView到UIViewController?

提问于
浏览
171

是否有内置的方法从 UIViewUIViewController ?我知道你可以通过 [self view]UIViewControllerUIView ,但我想知道是否有反向引用?

25 回答

  • 40

    我的解决方案可能被认为是一种虚假的但我有类似mayoneez的情况(我想切换视图以响应EAGLView中的手势),我以这种方式获得了EAGL的视图控制器:

    EAGLViewController *vc = ((EAGLAppDelegate*)[[UIApplication sharedApplication] delegate]).viewController;
    
  • 1

    虽然这些答案在技术上是正确的,包括Ushox,但我认为批准的方法是实施新协议或重用现有协议 . 协议使观察者与被观察者隔离,就像在他们之间放置一个邮件槽一样 . 实际上,这就是Gabriel通过pushViewController方法调用所做的事情;视图"knows",礼貌地请求您的navigationController推送视图是合适的协议,因为viewController符合navigationController协议 . 虽然您可以创建自己的协议,但只需使用Gabriel的示例并重新使用UINavigationController协议即可 .

  • 5

    由于长期以来一直是公认的答案,我觉得我需要用更好的答案来纠正它 .

    关于需求的一些评论:

    • 您的视图不需要直接访问视图控制器 .

    • 视图应该独立于视图控制器,并且能够在不同的上下文中工作 .

    • 您是否需要视图以与视图控制器的方式接口,推荐的方式以及Apple在Cocoa中的作用是使用委托模式 .

    以下是如何实现它的示例:

    @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建议并创建一个用作委托的协议 .

  • -5

    使用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];
    
  • 198

    UIViewUIResponder 的子类 . 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 有一个返回视图控制器的工作方法 .

  • 3

    我建议使用更轻量级的方法来遍历完整的响应者链,而无需在UIView上添加类别:

    @implementation MyUIViewSubclass
    
    - (UIViewController *)viewController {
        UIResponder *responder = self;
        while (![responder isKindOfClass:[UIViewController class]]) {
            responder = [responder nextResponder];
            if (nil == responder) {
                break;
            }
        }
        return (UIViewController *)responder;
    }
    
    @end
    
  • 1

    结合几个已经给出的答案,我正在发布我的实现:

    @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-enabled 静态库的一部分 . 它's been tested several times and I didn'吨发现任何问题或泄漏 .

    P.S . :如果相关视图是您的子类,则不需要像我一样使用类别 . 在后一种情况下,只需将方法放在子类中,就可以了 .

  • 4

    虽然这可以在技术上解决,因为pgb建议,恕我直言,这是一个设计缺陷 . 视图不需要知道控制器 .

  • 0

    我修改了de答案,所以我可以通过任何视图,按钮,标签等来获取它的父级 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

    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

    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)!
        }
    }
    
  • 1

    不要忘记,您可以访问视图是其子视图的窗口的根视图控制器 . 从那里,如果你是使用导航视图控制器并想要将新视图推送到它上面:

    [[[[self window] rootViewController] navigationController] pushViewController:newController animated:YES];
    

    但是,您需要首先正确设置窗口的rootViewController属性 . 首次创建控制器时执行此操作,例如在你的app委托中:

    -(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];
    }
    
  • 5

    Swift 4 version

    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{
    
     }
    
  • 7

    我不认为在某些情况下找出谁是视图控制器是“坏”的想法 . 可能不错的主意是保存对该控制器的引用,因为它可能会随着超级视图的变化而改变 . 在我的例子中,我有一个遍历响应者链的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;
    }
    
  • 111

    我偶然发现了一个我想要重用的小组件的情况,并在可重用的视图中添加了一些代码(它实际上只不过是一个打开 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
        }
    }
    
  • 0

    用于查找viewController的最简单的while while循环 .

    -(UIViewController*)viewController
    {
        UIResponder *nextResponder =  self;
    
        do
        {
            nextResponder = [nextResponder nextResponder];
    
            if ([nextResponder isKindOfClass:[UIViewController class]])
                return (UIViewController*)nextResponder;
    
        } while (nextResponder != nil);
    
        return nil;
    }
    
  • 0

    这不直接回答问题,而是对问题的意图做出假设 .

    如果您有视图并且在该视图中需要在另一个对象上调用方法,例如视图控制器,则可以使用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,以便我可以对数据执行某些操作 .

  • 3

    这肯定是一个坏主意和错误的设计,但我相信我们都可以享受由@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

  • 10

    也许我迟到了 . 但在这种情况下,我不喜欢类别(污染) . 我喜欢这样:

    #define UIViewParentController(__view) ({ \
    UIResponder *__responder = __view; \
    while ([__responder isKindOfClass:[UIView class]]) \
    __responder = [__responder nextResponder]; \
    (UIViewController *)__responder; \
    })
    
  • 2

    致菲尔的回答:

    在行: id nextResponder = [self nextResponder]; 如果self(UIView)不是ViewController视图的子视图,如果您知道self(UIView)的层次结构,您也可以使用: id nextResponder = [[self superview] nextResponder]; ...

  • 1

    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)
    }
    
  • 6

    更快捷的解决方案

    extension UIView {
        var parentViewController: UIViewController? {
            for responder in sequence(first: self, next: { $0.next }) {
                if let viewController = responder as? UIViewController {
                    return viewController
                }
            }
            return nil
        }
    }
    
  • 22

    我认为有观察到需要通知观察者的情况 .

    我看到一个类似的问题,其中UIViewController中的UIView响应一种情况,它需要首先告诉其父视图控制器隐藏后退按钮,然后在完成时告诉父视图控制器它需要从堆栈中弹出自己 .

    我一直在与代表们一起努力,但没有成功 .

    我不明白为什么这应该是一个坏主意?

  • -1

    另一种简单的方法是拥有自己的视图类,并在视图类中添加视图控制器的属性 . 通常视图控制器创建视图,控制器可以将其自身设置为属性 . 基本上它不是为控制器搜索(有点黑客攻击),而是让控制器将自己设置为视图 - 这很简单但有意义,因为它是“控制”视图的控制器 .

  • 32

    斯威夫特4

    (比其他答案更简洁)

    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 . 为此,我需要访问 viewUIViewController .

  • 0

    如果你的rootViewController是UINavigationViewController,它是在中设置的然后是AppDelegate类

    + (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]];
    
  • 12

    没有办法 .

    我所做的是将UIViewController指针传递给UIView(或适当的继承) . 对不起,我无法帮助解决问题的IB方法,因为我不相信IB .

    回答第一个评论者:有时你需要知道是谁打电话给你,因为它决定了你能做什么 . 例如,对于数据库,您可能只有读访问权限或读/写...

相关问题