【问题标题】:How to list out all the subviews in a uiviewcontroller in iOS?如何列出iOS中uiviewcontroller中的所有子视图?
【发布时间】:2011-08-30 13:25:14
【问题描述】:

我想列出UIViewController 中的所有子视图。我试过self.view.subviews,但并不是所有的子视图都列出来了,比如UITableViewCell中的子视图没有找到。有什么想法吗?

【问题讨论】:

  • 您可以通过递归搜索找到所有子视图。即检查子视图有子视图..

标签: ios uiview subview uiview-hierarchy


【解决方案1】:

你必须递归地迭代子视图。

- (void)listSubviewsOfView:(UIView *)view {
    
    // Get the subviews of the view
    NSArray *subviews = [view subviews];

    for (UIView *subview in subviews) {
        
        // Do what you want to do with the subview
        NSLog(@"%@", subview);

        // List the subviews of subview
        [self listSubviewsOfView:subview];
    }
}

【讨论】:

  • 从技术上讲,您不需要检查子视图计数是否为 0。
  • NSLog(@"\n%@", [(id)self.view performSelector:@selector(recursiveDescription)]);此行打印的结果与您的相同!
  • 如果您在这里,请查看来自@natbro 的更简单的recursiveDescription 答案 - stackoverflow.com/a/8962824/429521
  • 这是我的 improvement 到 @EmptyStack 的解决方案。它以递归方式显示类名和框架坐标
【解决方案2】:

转储视图层次结构的 xcode/gdb 内置方法很有用 -- recursiveDescription,每 http://developer.apple.com/library/ios/#technotes/tn2239/_index.html

它会输出一个更完整的视图层次结构,您可能会发现它很有用:

> po [_myToolbar recursiveDescription]

<UIToolbarButton: 0xd866040; frame = (152 0; 15 44); opaque = NO; layer = <CALayer: 0xd864230>>
   | <UISwappableImageView: 0xd8660f0; frame = (0 0; 0 0); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0xd86a160>>

【讨论】:

  • 在我的代码或其他地方放置 po [_myToolbar recursiveDescription] 的位置。
  • @WildPointer 他说的是控制台。您需要在某处设置断点来执行此操作。 po = 打印对象
  • +1 Apple 推荐这种方法(根据 WWDC 2013 的 Cocoa 和 Cocoa Touch 会议中的隐藏宝石,提示 #5)。
  • @पवन 见我的回答here。我重写了这个答案。
【解决方案3】:

Swift 中优雅的递归解决方案:

extension UIView {

    func subviewsRecursive() -> [UIView] {
        return subviews + subviews.flatMap { $0.subviewsRecursive() }
    }

}

您可以在任何 UIView 上调用 subviewsRecursive():

let allSubviews = self.view.subviewsRecursive()

【讨论】:

    【解决方案4】:

    你需要递归打印,这个方法也是基于视图深度的标签

    -(void) printAllChildrenOfView:(UIView*) node depth:(int) d
    {
        //Tabs are just for formatting
        NSString *tabs = @"";
        for (int i = 0; i < d; i++)
        {
            tabs = [tabs stringByAppendingFormat:@"\t"];
        }
    
        NSLog(@"%@%@", tabs, node);
    
        d++; //Increment the depth
        for (UIView *child in node.subviews)
        {
            [self printAllChildrenOfView:child depth:d];
        }
    
    }
    

    【讨论】:

      【解决方案5】:

      这里是快速版本

       func listSubviewsOfView(view:UIView){
      
          // Get the subviews of the view
          var subviews = view.subviews
      
          // Return if there are no subviews
          if subviews.count == 0 {
              return
          }
      
          for subview : AnyObject in subviews{
      
              // Do what you want to do with the subview
              println(subview)
      
              // List the subviews of subview
              listSubviewsOfView(subview as UIView)
          }
      }
      

      【讨论】:

        【解决方案6】:

        我参加聚会有点晚了,但有一个更通用的解决方案:

        @implementation UIView (childViews)
        
        - (NSArray*) allSubviews {
            __block NSArray* allSubviews = [NSArray arrayWithObject:self];
        
            [self.subviews enumerateObjectsUsingBlock:^( UIView* view, NSUInteger idx, BOOL*stop) {
                allSubviews = [allSubviews arrayByAddingObjectsFromArray:[view allSubviews]];
                           }];
                return allSubviews;
            }
        
        @end
        

        【讨论】:

          【解决方案7】:

          详情

          • Xcode 9.0.1,Swift 4
          • Xcode 10.2 (10E125)、Swift 5

          解决方案

          extension UIView {
              private func subviews(parentView: UIView, level: Int = 0, printSubviews: Bool = false) -> [UIView] {
                  var result = [UIView]()
                  if level == 0 && printSubviews {
                      result.append(parentView)
                      print("\(parentView.viewInfo)")
                  }
          
                  for subview in parentView.subviews {
                      if printSubviews { print("\(String(repeating: "-", count: level))\(subview.viewInfo)") }
                      result.append(subview)
                      if subview.subviews.isEmpty { continue }
                      result += subviews(parentView: subview, level: level+1, printSubviews: printSubviews)
                  }
                  return result
              }
              private var viewInfo: String { return "\(classForCoder), frame: \(frame))" }
              var allSubviews: [UIView] { return subviews(parentView: self) }
              func printSubviews() { _ = subviews(parentView: self, printSubviews: true) }
          }
          

          用法

           view.printSubviews()
           print("\(view.allSubviews.count)")
          

          结果

          【讨论】:

          • 这有助于识别麻烦的子视图 - 谢谢
          【解决方案8】:

          如果您想要的只是UIViews 的数组,这是一个单行解决方案(Swift 4+):

          extension UIView {
            var allSubviews: [UIView] {
              return self.subviews.reduce([UIView]()) { $0 + [$1] + $1.allSubviews }
            }
          }
          

          【讨论】:

          • 感谢您的解决方案!我花了几个小时才弄清楚,但后来你的帖子救了我!
          【解决方案9】:

          我是这样用的:

          NSLog(@"%@", [self.view subviews]);
          

          在 UIViewController 中。

          【讨论】:

            【解决方案10】:

            以我的方式,UIView 的类别或扩展比其他的要好得多 而递归是获取所有子视图的关键

            了解更多:

            https://github.com/ZhipingYang/XYDebugView

            目标-C

            @implementation UIView (Recurrence)
            
            - (NSArray<UIView *> *)recurrenceAllSubviews
            {
                NSMutableArray <UIView *> *all = @[].mutableCopy;
                void (^getSubViewsBlock)(UIView *current) = ^(UIView *current){
                    [all addObject:current];
                    for (UIView *sub in current.subviews) {
                        [all addObjectsFromArray:[sub recurrenceAllSubviews]];
                    }
                };
                getSubViewsBlock(self);
                return [NSArray arrayWithArray:all];
            }
            @end
            

            示例

            NSArray *views = [viewController.view recurrenceAllSubviews];
            

            斯威夫特 3.1

            extension UIView {
                func recurrenceAllSubviews() -> [UIView] {
                    var all = [UIView]()
                    func getSubview(view: UIView) {
                        all.append(view)
                        guard view.subviews.count>0 else { return }
                        view.subviews.forEach{ getSubview(view: $0) }
                    }
                    getSubview(view: self)
                    return all
                }
            }
            

            示例

            let views = viewController.view.recurrenceAllSubviews()
            

            直接使用sequence函数获取所有子视图

            let viewSequence = sequence(state: [viewController.view]) { (state: inout [UIView] ) -> [UIView]? in
                guard state.count > 0 else { return nil }
                defer {
                    state = state.map{ $0.subviews }.flatMap{ $0 }
                }
                return state
            }
            let views = viewSequence.flatMap{ $0 }
            

            【讨论】:

              【解决方案11】:

              简单的 Swift 示例:

               var arrOfSub = self.view.subviews
               print("Number of Subviews: \(arrOfSub.count)")
               for item in arrOfSub {
                  print(item)
               }
              

              【讨论】:

                【解决方案12】:

                未打印 UITableViewCell 中的子视图的原因是您必须输出顶层的所有子视图。单元格的子视图不是您视图的直接子视图。

                为了获取 UITableViewCell 的子视图,您需要在打印循环中确定哪些子视图属于 UITableViewCell(使用isKindOfClass:),然后循环遍历它的子视图

                编辑:Easy UIView Debugging 上的这篇博文可能会有所帮助

                【讨论】:

                  【解决方案13】:

                  我写了一个类别来列出视图控制器持有的所有视图,该视图控制器的灵感来自之前发布的答案。

                  @interface UIView (ListSubviewHierarchy)
                  - (NSString *)listOfSubviews;
                  @end
                  
                  @implementation UIView (ListSubviewHierarchy)
                  - (NSInteger)depth
                  {
                      NSInteger depth = 0;
                      if ([self superview]) {
                          deepth = [[self superview] depth] + 1;
                      }
                      return depth;
                  }
                  
                  - (NSString *)listOfSubviews
                  {
                      NSString * indent = @"";
                      NSInteger depth = [self depth];
                  
                      for (int counter = 0; counter < depth; counter ++) {
                          indent = [indent stringByAppendingString:@"  "];
                      }
                  
                      __block NSString * listOfSubviews = [NSString stringWithFormat:@"\n%@%@", indent, [self description];
                  
                      if ([self.subviews count] > 0) {
                          [self.subviews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
                              UIView * subview = obj;
                              listOfSubviews = [listOfSubviews stringByAppendingFormat:@"%@", [subview listOfSubviews]];
                          }];
                      }
                      return listOfSubviews;
                  }
                  @end
                  

                  要列出视图控制器持有的所有视图,只需NSLog("%@",[self listOfSubviews]),其中self 表示视图控制器本身。虽然效率不高。

                  另外,您可以使用NSLog(@"\n%@", [(id)self.view performSelector:@selector(recursiveDescription)]); 做同样的事情,我认为它比我的实现更有效。

                  【讨论】:

                    【解决方案14】:

                    你可以尝试一个花哨的数组技巧,比如:

                    [self.view.subviews makeObjectsPerformSelector: @selector(printAllChildrenOfView)];

                    只有一行代码。当然,您可能需要调整您的方法printAllChildrenOfView,使其不带任何参数或创建新方法。

                    【讨论】:

                      【解决方案15】:

                      兼容 Swift 2.0

                      这里有一个递归方法来获取一个通用视图的所有子视图:

                      extension UIView {
                        func subviewsList() -> [UIView] {
                            var subviews = self.subviews
                            if subviews.count == 0 {
                                return subviews + []
                            }
                            for v in subviews {
                               subviews += v.listSubviewsOfView()
                            }
                            return subviews
                        }
                      }
                      

                      所以你可以用这种方式到处调用:

                      let view = FooController.view
                      let subviews = view.subviewsList()
                      

                      【讨论】:

                        【解决方案16】:

                        最短的解决方案

                        for subview in self.view.subviews {
                            print(subview.dynamicType)
                        }
                        

                        结果

                        UIView
                        UIView
                        UISlider
                        UISwitch
                        UITextField
                        _UILayoutGuide
                        _UILayoutGuide
                        

                        备注

                        • 如您所见,此方法不会递归地列出子视图。请参阅其他一些答案。

                        【讨论】:

                          【解决方案17】:

                          这是this答案的重写:

                          您必须首先获取要打印其所有子视图的对象的指针/引用。有时您可能会发现通过其子视图访问该对象更容易找到该对象。喜欢po someSubview.superview。这会给你类似的东西:

                          Optional<UIView>
                            ▿ some : <FacebookApp.WhatsNewView: 0x7f91747c71f0; frame = (30 50; 354 636); clipsToBounds = YES; layer = <CALayer: 0x6100002370e0>>
                          
                          • FaceBookApp 是你的应用名称
                          • WhatsNewView 是您的superview类型
                          • 0x7f91747c71f0 是指向父视图的指针。

                          要打印superView,必须使用断点。


                          现在要执行此步骤,您只需单击“查看调试层次结构”。无需断点

                          那么你可以轻松做到:

                          po [0x7f91747c71f0 recursiveDescription]
                          

                          这对我来说返回了类似的东西:

                          <FacebookApp.WhatsNewView: 0x7f91747c71f0; frame = (30 50; 354 636); clipsToBounds = YES; layer = <CALayer: 0x6100002370e0>>
                             | <UIStackView: 0x7f91747c75f0; frame = (45 60; 264 93); layer = <CATransformLayer: 0x610000230ec0>>
                             |    | <UIImageView: 0x7f916ef38c30; frame = (10.6667 0; 243 58); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x61000003b840>>
                             |    | <UIStackView: 0x7f91747c8230; frame = (44.6667 58; 174.667 35); layer = <CATransformLayer: 0x6100006278c0>>
                             |    |    | <FacebookApp.CopyableUILabel: 0x7f91747a80b0; baseClass = UILabel; frame = (44 0; 86.6667 16); text = 'What's New'; gestureRecognizers = <NSArray: 0x610000c4a770>; layer = <_UILabelLayer: 0x610000085550>>
                             |    |    | <FacebookApp.CopyableUILabel: 0x7f916ef396a0; baseClass = UILabel; frame = (0 21; 174.667 14); text = 'Version 14.0.5c Oct 05, 2...'; gestureRecognizers = <NSArray: 0x610000c498a0>; layer = <_UILabelLayer: 0x610000087300>>
                             | <UITextView: 0x7f917015ce00; frame = (45 183; 264 403); text = '   •   new Adding new feature...'; clipsToBounds = YES; gestureRecognizers = <NSArray: 0x6100000538f0>; layer = <CALayer: 0x61000042f000>; contentOffset: {0, 0}; contentSize: {264, 890}>
                             |    | <<_UITextContainerView: 0x7f9170a13350; frame = (0 0; 264 890); layer = <_UITextTiledLayer: 0x6080002c0930>> minSize = {0, 0}, maxSize = {1.7976931348623157e+308, 1.7976931348623157e+308}, textContainer = <NSTextContainer: 0x610000117b20 size = (264.000000,340282346638528859811704183484516925440.000000); widthTracksTextView = YES; heightTracksTextView = NO>; exclusionPaths = 0x61000001bc30; lineBreakMode = 0>
                             |    |    | <_UITileLayer: 0x60800023f8a0> (layer)
                             |    |    | <_UITileLayer: 0x60800023f3c0> (layer)
                             |    |    | <_UITileLayer: 0x60800023f360> (layer)
                             |    |    | <_UITileLayer: 0x60800023eca0> (layer)
                             |    | <UIImageView: 0x7f9170a7d370; frame = (-39 397.667; 36 2.33333); alpha = 0; opaque = NO; autoresize = TM; userInteractionEnabled = NO; layer = <CALayer: 0x60800023f4c0>>
                             |    | <UIImageView: 0x7f9170a7d560; frame = (258.667 -39; 2.33333 36); alpha = 0; opaque = NO; autoresize = LM; userInteractionEnabled = NO; layer = <CALayer: 0x60800023f5e0>>
                             | <UIView: 0x7f916ef149c0; frame = (0 587; 354 0); layer = <CALayer: 0x6100006392a0>>
                             | <UIButton: 0x7f91747a8730; frame = (0 0; 0 0); clipsToBounds = YES; opaque = NO; layer = <CALayer: 0x610000639320>>
                             |    | <UIButtonLabel: 0x7f916ef00a80; frame = (0 -5.66667; 0 16); text = 'See More Details'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x610000084d80>>
                          

                          你一定猜到了我的超级视图有 4 个子视图:

                          • 一个stackView(stackView本身有一个图像和另一个stackView(这个stackView有2个自定义标签))
                          • 一个文本视图
                          • 一个视图
                          • 一个按钮

                          这对我来说相当新,但帮助我调试了我的视图框架(以及文本和类型)。我的一个子视图没有显示在屏幕上,所以使用了 recursiveDescription,我意识到我的一个子视图的宽度是 0... 所以我纠正了它的约束并且子视图出现了。

                          【讨论】:

                            【解决方案18】:

                            或者,如果您想从 UIView 扩展返回一个包含所有子视图(和嵌套子视图)的数组:

                            func getAllSubviewsRecursively() -> [AnyObject] {
                                var allSubviews: [AnyObject] = []
                            
                                for subview in self.subviews {
                                    if let subview = subview as? UIView {
                                        allSubviews.append(subview)
                                        allSubviews = allSubviews + subview.getAllSubviewsRecursively()
                                    }
                                }
                            
                                return allSubviews
                            }
                            

                            【讨论】:

                              【解决方案19】:

                              C# Xamarin 版本:

                              void ListSubviewsOfView(UIView view)
                              {
                                  var subviews = view.Subviews;
                                  if (subviews.Length == 0) return;
                              
                                  foreach (var subView in subviews)
                                  {
                                      Console.WriteLine("Subview of type {0}", subView.GetType());
                                      ListSubviewsOfView(subView);
                                  }
                              }
                              

                              或者,如果您想查找我使用的特定类型的所有子视图:

                              List<T> FindViews<T>(UIView view)
                              {
                                  List<T> allSubviews = new List<T>();
                                  var subviews = view.Subviews.Where(x =>  x.GetType() == typeof(T)).ToList();
                              
                                  if (subviews.Count == 0) return allSubviews;
                              
                                     foreach (var subView in subviews)
                                     {
                                          allSubviews.AddRange(FindViews<T>(subView));
                                      }
                              
                                  return allSubviews;
                              
                              }
                              

                              【讨论】:

                                【解决方案20】:

                                我已经在UIView 的类别中完成了它,只需调用传递索引的函数以使用漂亮的树格式打印它们。这只是James Webster 发布的答案的另一种选择。

                                #pragma mark - Views Tree
                                
                                - (void)printSubviewsTreeWithIndex:(NSInteger)index
                                {
                                    if (!self)
                                    {
                                        return;
                                    }
                                
                                
                                    NSString *tabSpace = @"";
                                
                                    @autoreleasepool
                                    {
                                        for (NSInteger x = 0; x < index; x++)
                                        {
                                            tabSpace = [tabSpace stringByAppendingString:@"\t"];
                                        }
                                    }
                                
                                    NSLog(@"%@%@", tabSpace, self);
                                
                                    if (!self.subviews)
                                    {
                                        return;
                                    }
                                
                                    @autoreleasepool
                                    {
                                        for (UIView *subView in self.subviews)
                                        {
                                            [subView printViewsTreeWithIndex:index++];
                                        }
                                    }
                                }
                                

                                希望对你有帮助:)

                                【讨论】:

                                  【解决方案21】:
                                  - (NSString *)recusiveDescription:(UIView *)view
                                  {
                                      NSString *s = @"";
                                      NSArray *subviews = [view subviews];
                                      if ([subviews count] == 0) return @"no subviews";
                                  
                                      for (UIView *subView in subviews) {
                                           s = [NSString stringWithFormat:@"<%@; frame = (%f %f : %f %f) \n ",NSStringFromClass([subView class]), subView.frame.origin.x, subView.frame.origin.y ,subView.frame.size.width, subView.frame.size.height];  
                                          [self recusiveDescription:subView];
                                      }
                                      return s;
                                  }
                                  

                                  【讨论】:

                                    【解决方案22】:

                                    self.view.subviews 维护视图的层次结构。要获取 uitableviewcell 的子视图,您必须执行以下操作。

                                     for (UIView *subView in self.view.subviews) {
                                          if ([subView isKindOfClass:[UITableView class]]) {
                                                for (UIView *tableSubview in subView.subviews) {
                                                    .......
                                                }
                                          }
                                     }
                                    

                                    【讨论】:

                                      猜你喜欢
                                      • 2012-08-17
                                      • 1970-01-01
                                      • 1970-01-01
                                      • 2011-09-19
                                      • 1970-01-01
                                      • 1970-01-01
                                      • 1970-01-01
                                      • 2013-11-19
                                      • 1970-01-01
                                      相关资源
                                      最近更新 更多