How to list out all the subviews in a uiviewcontroller in iOS?
Asked Answered
P

22

94

I want to list out all the subviews in a UIViewController. I tried self.view.subviews, but not all of the subviews are listed out, for instance, the subviews in the UITableViewCell are not found. Any idea?

Prudery answered 30/8, 2011 at 13:25 Comment(1)
You can find out all subviews by recursive search. i.e check subview has subviews..Rusticate
O
176

You have to recursively iterate the sub views.

- (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];
    }
}
Orit answered 30/8, 2011 at 13:38 Comment(4)
Technically, you don't need to check if the subview count is 0.Clastic
NSLog(@"\n%@", [(id)self.view performSelector:@selector(recursiveDescription)]); This line prints the same result as yours!Jacksnipe
If you are here, PLEASE check the much simpler recursiveDescription answer from @natbro - https://mcmap.net/q/223234/-how-to-list-out-all-the-subviews-in-a-uiviewcontroller-in-iosHuddle
Here is my improvement to @Orit solution. It shows the class name & frame coordinates recursively indentedMix
K
35

The xcode/gdb built-in way to dump the view hierarchy is useful -- recursiveDescription, per http://developer.apple.com/library/ios/#technotes/tn2239/_index.html

It outputs a more complete view hierarchy which you might find useful:

> 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>>
Kussell answered 22/1, 2012 at 17:0 Comment(4)
Where to put the po [_myToolbar recursiveDescription] in my code or elsewhere.Photosphere
@WildPointer He's talking about the console. You need a breakpoint somewhere to do this. po = print objectDoorn
+1 Apple recommends this approach (per WWDC 2013's Hidden Gems in Cocoa and Cocoa Touch session, tip #5).Subclimax
@पवन see my answer here. I did a rewrite of this answer.Endocardial
C
22

Elegant recursive solution in Swift:

extension UIView {

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

}

You can call subviewsRecursive() on any UIView:

let allSubviews = self.view.subviewsRecursive()
Cessionary answered 5/9, 2016 at 12:56 Comment(0)
R
13

You need to print recursively, this method also tabs based on the depth of the view

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

}
Robber answered 30/8, 2011 at 13:53 Comment(0)
P
13

Here is the swift version

 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)
    }
}
Pomiferous answered 16/6, 2014 at 13:6 Comment(0)
F
8

Details

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

Solution

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

Usage

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

Result

enter image description here

Fondle answered 19/10, 2017 at 12:32 Comment(1)
This helped a lot to identify a troublesome subview - thanksZubkoff
B
7

I'm a bit late to the party, but a bit more general solution:

@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
Belldame answered 29/7, 2012 at 15:30 Comment(0)
S
6

If all you want is an array of UIViews, this is a one liner solution (Swift 4+):

extension UIView {
  var allSubviews: [UIView] {
    return self.subviews.reduce([UIView]()) { $0 + [$1] + $1.allSubviews }
  }
}
Shortterm answered 2/3, 2018 at 13:14 Comment(1)
thanks for this solution! I spent hours figuring out but then your post saved me!Versieversification
V
5

I use this way:

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

in the UIViewController.

Votyak answered 18/9, 2012 at 7:56 Comment(0)
A
4

In my way, UIView's category or extension is much better than others and recursive is the key point to get all subviews

learn more:

https://github.com/ZhipingYang/XYDebugView

enter image description here

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

example

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

example

let views = viewController.view.recurrenceAllSubviews()

directly, use sequence function to get all subviews

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 }
Ammoniac answered 24/5, 2017 at 11:16 Comment(0)
T
3

Simple Swift example:

 var arrOfSub = self.view.subviews
 print("Number of Subviews: \(arrOfSub.count)")
 for item in arrOfSub {
    print(item)
 }
Transceiver answered 17/11, 2014 at 18:36 Comment(0)
A
2

The reason the subviews in a UITableViewCell are not printed is because you must be outputting all the subviews in the top level. The subviews of the cell are not the direct subviews of your view.

In order to get the UITableViewCell's subviews, you need to determine the which subviews belong to a UITableViewCell (using isKindOfClass:) in your print loop and then loop through it's subviews

Edit: This blog post on Easy UIView Debugging may potentially help

Avent answered 30/8, 2011 at 13:30 Comment(0)
L
2

I wrote a category to list all views held by a view controller which inspired by the answers posted before.

@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

To list all views held by a view controller, just NSLog("%@",[self listOfSubviews]), which self means the view controller itself. Though it's not quit efficient.

Plus, you can use NSLog(@"\n%@", [(id)self.view performSelector:@selector(recursiveDescription)]); to do the same thing, and I think it's more efficient than my implementation.

La answered 10/11, 2012 at 18:10 Comment(0)
S
1

You could try a fancy array trick, like:

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

Just one line of code. Of course, you might need to adjust your method printAllChildrenOfView to not take any parameters or make a new method.

Shoat answered 30/8, 2011 at 14:15 Comment(0)
S
1

Swift 2.0 compatible

Here a recursive method to obtains all subviews of a generic view:

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

So you can call everywhere in this way:

let view = FooController.view
let subviews = view.subviewsList()
Selfpronouncing answered 9/10, 2015 at 16:8 Comment(0)
A
1

enter image description here

Shortest solution

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

Result

UIView
UIView
UISlider
UISwitch
UITextField
_UILayoutGuide
_UILayoutGuide

Notes

  • As you can see, this method does not list the subviews recursively. See some of the other answers for that.
Aton answered 8/7, 2016 at 19:48 Comment(0)
E
1

This a rewriting of this answer:

You must first get the pointer/reference to the object you intend to print all its subviews. Sometimes you may find it easier to find that object by accessing it through its subview. Like po someSubview.superview. This will give you something like:

Optional<UIView>
  ▿ some : <FacebookApp.WhatsNewView: 0x7f91747c71f0; frame = (30 50; 354 636); clipsToBounds = YES; layer = <CALayer: 0x6100002370e0>>
  • FaceBookApp is your app name
  • WhatsNewView is the type of your superview
  • 0x7f91747c71f0 is the pointer to the superview.

To print the superView, you must use breakpoints.


Now to do this step you could just click on the 'view debug hierarchy'. No need for breakpoints

enter image description here

Then you could easily do:

po [0x7f91747c71f0 recursiveDescription]

which for me returned something like:

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

as you must have guessed my superview has 4 subviews:

  • a stackView (the stackView itself has an image and another stackView(this stackView has 2 custom labels))
  • a textView
  • a view
  • a button

This is fairly new to me, but has helped me debug a my views' frames (and text and type). One of my subviews wasn't showing up on the screen, so used recursiveDescription and I realized the width of my one of my subView's was 0... so I went corrected its constraints and the subview was appearing.

Endocardial answered 25/10, 2017 at 15:24 Comment(0)
E
0

Alternatively if you wanted to return an array of all subviews (and nested subviews) from a UIView Extension:

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
}
Eyeless answered 4/6, 2015 at 12:28 Comment(0)
O
0

A C# Xamarin version:

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

Alternatively, if you want to find all the subviews of a specific type I use:

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;

}
Overthrow answered 10/10, 2016 at 12:37 Comment(0)
M
0

I have done it in a category of UIView just call the function passing the index to print them with a nice tree format. This is just another option of the answer posted by 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++];
        }
    }
}

I hope it helps :)

Mobcap answered 23/11, 2016 at 16:34 Comment(0)
S
0
- (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;
}
Sheffie answered 2/7, 2018 at 9:48 Comment(0)
B
-1

self.view.subviews maintain the heirarchy of views.To get subviews of uitableviewcell you have to do something like below.

 for (UIView *subView in self.view.subviews) {
      if ([subView isKindOfClass:[UITableView class]]) {
            for (UIView *tableSubview in subView.subviews) {
                .......
            }
      }
 }
Battue answered 30/8, 2011 at 13:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.