【问题标题】:Get to UIViewController from UIView?从 UIView 到 UIViewController?
【发布时间】:2010-11-23 08:23:41
【问题描述】:

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

【问题讨论】:

标签: ios objective-c cocoa-touch uiview uiviewcontroller


【解决方案1】:

由于这是很长时间以来被接受的答案,我觉得我需要用更好的答案来纠正它。

关于需要的一些cmets:

  • 您的视图不需要直接访问视图控制器。
  • 视图应该独立于视图控制器,并且能够在不同的上下文中工作。
  • 如果您需要视图以某种方式与视图控制器交互,推荐的方式以及 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 的建议进行操作,并创建一个用作委托的协议。

【讨论】:

  • 这是非常糟糕的建议。您不应该从视图中引用视图控制器
  • @MattDiPasquale:是的,这是糟糕的设计。
  • @Phillipe Leybaert 我很想知道您对按钮单击事件的最佳设计的想法,该事件应该在控制器上调用一些操作,而视图没有对控制器的引用。谢谢
  • @PhilippeLeybaert Apple 的示例项目倾向于演示特定 API 功能的使用。我提到了许多牺牲良好或可扩展的设计,以提供对该主题的简明演示。我花了很长时间才意识到这一点,虽然这很有意义,但我觉得很不幸。我认为很多开发人员将这些务实的项目作为 Apple 最佳设计实践的指南,我很确定他们不是。
  • 所有这些关于“你不应该这样做”的废话就是这样。如果一个视图想知道它的视图控制器,那是由程序员来决定的。期间。
【解决方案2】:

没有办法。

我所做的是将 UIViewController 指针传递给 UIView(或适当的继承)。很抱歉,我无法通过 IB 方法解决问题,因为我不相信 IB。

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

【讨论】:

  • 这是什么意思 - “我不相信 IB”?我已经启动了它,它确实存在。
  • 您需要更好地掌握乐趣和抽象,尤其是在英语方面。这意味着我不喜欢它。
  • 我不喜欢鳄梨。但我敢打赌我可以帮助别人做鳄梨酱。它可以在 IB 中完成,所以显然你的“没有办法”的答案是不正确的。你喜不喜欢IB无关紧要。
【解决方案3】:

尽管这可以按照 pgb 的建议在技术上解决,恕我直言,这是一个设计缺陷。视图不需要知道控制器。

【讨论】:

  • 我只是不确定如何告诉 viewController 它的一个视图正在消失,它需要调用它的 viewXXXAppear/viewXXXDisappear 方法之一。
  • 这就是观察者模式背后的理念。被观察者(在这种情况下是视图)不应该直接知道它的观察者。观察者应该只接收他们感兴趣的回调。
【解决方案4】:

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

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

【讨论】:

  • 请将您的代码改写如下:EAGLViewController *vc = [(EAGLAppDelegate *)[UIApplication sharedApplication].delegate viewController];.
  • 问题不在于点语法,而在于类型。在 Objective C 中声明一个你写的对象 ClassName *object - 带有星号。
  • 哎呀...这实际上是我所拥有的,但是 StackOverflow HTML 小部件看起来像是认为星号表示斜体...我将其更改为代码块,现在它可以正确显示。谢谢!
【解决方案5】:

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

【讨论】:

  • 这只有在接收 UIViewUIViewController 之间的响应者链中没有任何内容时才有效。 Phil M 以递归为特色的答案是要走的路。
【解决方案6】:

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

【讨论】:

  • 您需要让 UIView 了解其 UIViewController 的一个原因是当您有自定义 UIView 子类需要推送模式视图/对话框时。
  • 太棒了,我必须访问我的 ViewController 才能显示由子视图创建的自定义弹出窗口
  • 这不是 UIView 推送模态视图的坏习惯吗?我现在正在这样做,但我觉得这不是正确的做法..
  • Phil,您的自定义视图应该调用视图控制器监听的委托方法,然后从那里推送。
  • 我只是喜欢有多少 SO 问题得到了一个“智者”、学术、接受的答案,只有几分,第二个答案是实际的、肮脏的和违反规则的,有十倍的点:-)
【解决方案7】:

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

    [[[[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];
}

【讨论】:

  • 在我看来,根据Apple docs,因为[[self navigationController] view]是窗口的“主”(子)视图,所以窗口的rootViewController属性必须设置为@ 987654326@ 立即控制“主”视图。
【解决方案8】:

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

【讨论】:

    【解决方案9】:

    我认为有一种情况,被观察者需要通知观察者。

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

    我一直在与代表一起尝试这个,但没有成功。

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

    【讨论】:

      【解决方案10】:

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

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

      【讨论】:

        【解决方案11】:

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

        【讨论】:

          【解决方案12】:

          结合几个已经给出的答案,我也将它与我的实现一起发布:

          @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.:如果相关视图是您的子类,您不需要像我一样使用类别。在后一种情况下,只需将方法放入您的子类中即可。

          【讨论】:

          • 这是最好的答案。不需要递归,这个版本优化的很好
          【解决方案13】:

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

          【讨论】:

            【解决方案14】:

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

            如果你有一个视图并且在那个视图中你需要调用另一个对象的方法,比如视图控制器,你可以使用 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,以便我可以对数据做一些事情。

            【讨论】:

              【解决方案15】:

              菲尔的回答:

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

              【讨论】:

                【解决方案16】:

                查找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;
                }
                

                【讨论】:

                  【解决方案17】:

                  如果你的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]];
                  

                  【讨论】:

                    【解决方案18】:

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

                    【讨论】:

                      【解决方案19】:

                      我修改了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)!
                          }
                      }
                      

                      【讨论】:

                        【解决方案20】:

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

                        【讨论】:

                          【解决方案21】:

                          也许我来晚了。但在这种情况下,我不喜欢类别(污染)。我喜欢这种方式:

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

                          【讨论】:

                          • 我认为不限于特定类的全局宏类别更污染:P
                          【解决方案22】:

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

                          【讨论】:

                            【解决方案23】:

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

                            【讨论】:

                              【解决方案24】:

                              更快捷的解决方案

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

                              【讨论】:

                              • 我喜欢这个答案。我不确定它是否更迅速。 Swift 是另一种无法决定的多范式中的哪一个,它想要结婚的语言。在这种情况下,您可以很好地演示更实用的方法(sequence 位)。所以如果“swifty”的意思是“更实用”,那么我想它更快捷。
                              【解决方案25】:

                              斯威夫特 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

                              【讨论】:

                                【解决方案26】:

                                如果你不打算将它上传到 App Store,你也可以使用 UIView 的私有方法。

                                @interface UIView(Private)
                                - (UIViewController *)_viewControllerForAncestor;
                                @end
                                
                                // Later in the code
                                UIViewController *vc = [myView _viewControllerForAncestor];
                                

                                【讨论】:

                                  【解决方案27】:
                                  var parentViewController: UIViewController? {
                                      let s = sequence(first: self) { $0.next }
                                      return s.compactMap { $0 as? UIViewController }.first
                                  }
                                  

                                  【讨论】:

                                  • 虽然这段代码可能会回答这个问题,但一个好的答案还应该解释代码的作用以及它如何解决问题。
                                  【解决方案28】:

                                  Swift 5.2 的两种解决方案:

                                  • 更多功能方面
                                  • 现在不需要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
                                      }
                                  }
                                  
                                  • 此解决方案需要先遍历每个响应者,因此可能不是性能最高的。

                                  【讨论】:

                                    【解决方案29】:

                                    要获取给定视图的控制器,可以使用 UIFirstResponder 链。

                                    customView.target(forAction: Selector("viewDidLoad"), withSender: nil)
                                    

                                    【讨论】:

                                      猜你喜欢
                                      • 1970-01-01
                                      • 1970-01-01
                                      • 1970-01-01
                                      • 1970-01-01
                                      • 1970-01-01
                                      • 2018-09-14
                                      • 1970-01-01
                                      • 1970-01-01
                                      • 1970-01-01
                                      相关资源
                                      最近更新 更多