Как перечислить все подвиды в uiviewcontroller в iOS?

Я хочу перечислить все подвиды в UIViewController. Я пытался!--1-->, но не все подвиды перечислены, например, подвиды в UITableViewCell не нашли. Есть идеи?

22 ответов


вы должны рекурсивно перебирать представления sub.

- (void)listSubviewsOfView:(UIView *)view {

    // Get the subviews of the view
    NSArray *subviews = [view subviews];

    // Return if there are no subviews
    if ([subviews count] == 0) return; // COUNT CHECK LINE

    for (UIView *subview in subviews) {

        // Do what you want to do with the subview
        NSLog(@"%@", subview);

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

Как прокомментировал @Greg Meletic, вы можете пропустить строку проверки количества выше.


встроенный способ Xcode/gdb для сброса иерархии представлений полезен -- recursiveDescription, per 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>>

вам нужно печатать рекурсивно, этот метод также вкладки на основе глубины представления

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

}

вот версия swift

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

Я немного опоздала на вечеринку, но немного более общее решение:

@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

элегантное рекурсивное решение в Swift:

extension UIView {

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

}

вы можете вызвать subviewsRecursive () на любом UIView:

let allSubviews = self.view.subviewsRecursive()

Я использую таким образом:

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

в UIViewController.


Если все, что вам нужно, это массив UIViews, это одно линейное решение (Swift 4+):

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

причина, по которой подвиды в UITableViewCell не печатаются, заключается в том, что вы должны выводить все подвиды на верхнем уровне. Подвиды ячейки не являются прямыми подвидами вашего представления.

чтобы получить подвиды UITableViewCell, вам нужно определить, какие подвиды принадлежат UITableViewCell (используя isKindOfClass:) в вашем цикле печати, а затем цикл через его subviews

Edit: это сообщение в блоге на Легкая Отладка UIView может потенциально помогите


Я написал категорию, чтобы перечислить все виды, удерживаемые контроллером вида, который вдохновлен ответами, опубликованными ранее.

@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)]); сделать то же самое, и я думаю, что это более эффективно, чем моя реализация.


простой пример Swift:

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

по-моему, категория или расширение UIView намного лучше, чем другие и рекурсивный является ключевым моментом для получения всех подвидов

подробнее:

https://github.com/ZhipingYang/XYDebugView

С

@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];

Swift 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: ) }
        }
        getSubview(view: self)
        return all
    }
}

пример

let views = viewController.view.recurrenceAllSubviews()

напрямую, используйте последовательность.subviews }.flatMap{ } } return state } let views = Array(viewSequence).flatMap{ }


подробности

xCode 9.0.1, Swift 4

решение

extension UIView {

    private var viewInfo: String {
        return "\(classForCoder), frame: \(frame))"
    }

    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.count != 0 {
                result += subviews(parentView: subview, level: level+1, printSubviews: printSubviews)
            }
        }
        return result
    }

    var allSubviews: [UIView] {
        return subviews(parentView: self)
    }

    func printSubviews() {
        _ = subviews(parentView: self, printSubviews: true)
    }

}

использование

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

результат

enter image description here


вы можете попробовать причудливый трюк с массивом, например:

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

только одна строка кода. Конечно, вам может потребоваться настроить свой метод printAllChildrenOfView чтобы не принимать никаких параметров или создать новый метод.


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

это переписывание этой ответ:

сначала вы должны получить указатель / ссылку на объект, который вы собираетесь распечатать все его подвиды. Иногда вам может быть проще найти этот объект, обратившись к нему через его подзадачу. Как po someSubview.superview. Это даст вам что-то вроде:

Optional<UIView>
  ▿ some : <FacebookApp.WhatsNewView: 0x7f91747c71f0; frame = (30 50; 354 636); clipsToBounds = YES; layer = <CALayer: 0x6100002370e0>>
  • FaceBookApp это код название
  • WhatsNewView является тип вашего superview
  • 0x7f91747c71f0 является указателем на superview.

для печати супервизора необходимо использовать точки останова.


теперь, чтобы сделать этот шаг, вы можете просто нажать на "просмотр иерархии отладки". Нет необходимости в точках останова

enter image description here

потом вы могли бы легко сделать:

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

как вы должно быть, догадался, что мой superview имеет 4 подвида:

  • a stackView (сам stackView имеет изображение и другой stackView (этот stackView имеет 2 таможни ярлыки))
  • textView
  • вид
  • на

это довольно ново для меня, но помогло мне отладить фреймы моих представлений (и текст и тип). Один из моих подвидов не отображался на экране, поэтому я использовал recursiveDescription и понял, что ширина моего одного из моих подвидов была 0... поэтому я пошел исправлять его ограничения, и появилось подвидение.


альтернативно, если вы хотите вернуть массив всех подвидов (и вложенных подвидов) из расширения 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
}

enter image description here

кратчайшего решения

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

результат

UIView
UIView
UISlider
UISwitch
UITextField
_UILayoutGuide
_UILayoutGuide

Примечания

  • как вы можете видеть, этот метод не перечисляет подвиды рекурсивно. См. некоторые другие ответы на этот вопрос.

версия 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;

}

Я сделал это в категории UIView просто вызовите функцию, передающую индекс, чтобы напечатать их с хорошим форматом дерева. Это просто еще один вариант ответа, опубликованного Джеймс Уэбстер.

#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++];
        }
    }
}

надеюсь, это поможет:)


- (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;
}

self.вид.подвиды поддерживают наследственность взглядов.Чтобы получить подвиды uitableviewcell, вам нужно сделать что-то вроде ниже.

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