87

How can I loop through all subviews of a UIView, and their subviews and their subviews?

Alexander Farber
  • 21,519
  • 75
  • 241
  • 416
123hal321
  • 2,080
  • 4
  • 24
  • 25
  • 7
    If you really do NEED to loop through then the accepted answer is correct, if just looking for a single view, then tag it and use viewWithTag instead - save yourself some pain! - http://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/doc/uid/TP40006816-CH3-SW26 – Norman H Apr 04 '13 at 10:30

18 Answers18

126

Use recursion:

// UIView+HierarchyLogging.h
@interface UIView (ViewHierarchyLogging)
- (void)logViewHierarchy;
@end

// UIView+HierarchyLogging.m
@implementation UIView (ViewHierarchyLogging)
- (void)logViewHierarchy
{
    NSLog(@"%@", self);
    for (UIView *subview in self.subviews)
    {
        [subview logViewHierarchy];
    }
}
@end

// In your implementation
[myView logViewHierarchy];
Ole Begemann
  • 135,006
  • 31
  • 278
  • 256
  • 2
    Is there a way to pass a function in the logViewHierarchy? That way it can suit your needs at different times. – docchang Sep 07 '11 at 13:38
  • 2
    @docchang: Obj-C blocks are a great fit for that use case. You pass a block to the method, execute the block and pass it down the hierarchy with each method call. – Ole Begemann Sep 07 '11 at 14:04
  • Nice. I posted a snippet below that adds whitespace indentation to this technique. – jaredsinclair Jul 14 '13 at 08:33
36

Well here is my solution using recursion and a wrapper(category/extension) for the UIView class.

// UIView+viewRecursion.h
@interface UIView (viewRecursion)
- (NSMutableArray*) allSubViews;
@end

// UIView+viewRecursion.m
@implementation UIView (viewRecursion)
- (NSMutableArray*)allSubViews
{
   NSMutableArray *arr=[[[NSMutableArray alloc] init] autorelease];
   [arr addObject:self];
   for (UIView *subview in self.subviews)
   {
     [arr addObjectsFromArray:(NSArray*)[subview allSubViews]];
   }
   return arr;
}
@end

Usage : Now you should be looping through all the sub views and manipulate them as needed.

//disable all text fields
for(UIView *v in [self.view allSubViews])
{
     if([v isKindOfClass:[UITextField class]])
     {
         ((UITextField*)v).enabled=NO;
     }
}
Alex Zavatone
  • 4,106
  • 36
  • 54
RamaKrishna Chunduri
  • 1,100
  • 1
  • 11
  • 15
29

Here's another Swift implementation:

extension UIView {
    var allSubviews: [UIView] {
        return self.subviews.flatMap { [$0] + $0.allSubviews }
    }
}
Cal Stephens
  • 725
  • 9
  • 20
15

A solution in Swift 3 that gives all subviews without including the view itself:

extension UIView {
var allSubViews : [UIView] {

        var array = [self.subviews].flatMap {$0}

        array.forEach { array.append(contentsOf: $0.allSubViews) }

        return array
    }
}
ielyamani
  • 17,807
  • 10
  • 55
  • 90
12

Just found an interesting way to do this through the debugger:

http://idevrecipes.com/2011/02/10/exploring-iphone-view-hierarchies/

references this Apple Technote:

https://developer.apple.com/library/content/technotes/tn2239/_index.html#SECUIKIT

Just make sure your debugger is paused (either set a break point of pause it manually) and you can ask for the recursiveDescription.

Pang
  • 9,564
  • 146
  • 81
  • 122
djibouti33
  • 12,102
  • 9
  • 83
  • 116
12

I tag everything when it's created. Then it's easy to find any subview.

view = [aView viewWithTag:tag];
bstpierre
  • 30,042
  • 15
  • 70
  • 103
11

Here is an example with actual view looping and breaking functionality.

Swift:

extension UIView {

    func loopViewHierarchy(block: (_ view: UIView, _ stop: inout Bool) -> ()) {
        var stop = false
        block(self, &stop)
        if !stop {
            self.subviews.forEach { $0.loopViewHierarchy(block: block) }
        }
    }

}

Call example:

mainView.loopViewHierarchy { (view, stop) in
    if view is UIButton {
        /// use the view
        stop = true
    }
}

Reversed looping:

extension UIView {

    func loopViewHierarchyReversed(block: (_ view: UIView, _ stop: inout Bool) -> ()) {
        for i in stride(from: self.highestViewLevel(view: self), through: 1, by: -1) {
            let stop = self.loopView(view: self, level: i, block: block)
            if stop {
                break
            }
        }
    }

    private func loopView(view: UIView, level: Int, block: (_ view: UIView, _ stop: inout Bool) -> ()) -> Bool {
        if level == 1 {
            var stop = false
            block(view, &stop)
            return stop
        } else if level > 1 {
            for subview in view.subviews.reversed() {
            let stop = self.loopView(view: subview, level: level - 1, block: block)
                if stop {
                    return stop
                }
            }
        }
        return false
    }

    private func highestViewLevel(view: UIView) -> Int {
        var highestLevelForView = 0
        for subview in view.subviews.reversed() {
            let highestLevelForSubview = self.highestViewLevel(view: subview)
            highestLevelForView = max(highestLevelForView, highestLevelForSubview)
        }
        return highestLevelForView + 1
    }
}

Call example:

mainView.loopViewHierarchyReversed { (view, stop) in
    if view is UIButton {
        /// use the view
        stop = true
    }
}

Objective-C:

typedef void(^ViewBlock)(UIView* view, BOOL* stop);

@interface UIView (ViewExtensions)
-(void) loopViewHierarchy:(ViewBlock) block;
@end

@implementation UIView (ViewExtensions)
-(void) loopViewHierarchy:(ViewBlock) block {
    BOOL stop = NO;
    if (block) {
        block(self, &stop);
    }
    if (!stop) {
        for (UIView* subview in self.subviews) {
            [subview loopViewHierarchy:block];
        }
    }
}
@end

Call example:

[mainView loopViewHierarchy:^(UIView* view, BOOL* stop) {
    if ([view isKindOfClass:[UIButton class]]) {
        /// use the view
        *stop = YES;
    }
}];
vdd
  • 362
  • 1
  • 4
  • 11
6

With the help of Ole Begemann. I added a few lines to incorporate the block concept into it.

UIView+HierarchyLogging.h

typedef void (^ViewActionBlock_t)(UIView *);
@interface UIView (UIView_HierarchyLogging)
- (void)logViewHierarchy: (ViewActionBlock_t)viewAction;
@end

UIView+HierarchyLogging.m

@implementation UIView (UIView_HierarchyLogging)
- (void)logViewHierarchy: (ViewActionBlock_t)viewAction {
    //view action block - freedom to the caller
    viewAction(self);

    for (UIView *subview in self.subviews) {
        [subview logViewHierarchy:viewAction];
    }
}
@end

Using the HierarchyLogging category in your ViewController. You are now have freedom to what you need to do.

void (^ViewActionBlock)(UIView *) = ^(UIView *view) {
    if ([view isKindOfClass:[UIButton class]]) {
        NSLog(@"%@", view);
    }
};
[self.view logViewHierarchy: ViewActionBlock];
docchang
  • 1,115
  • 15
  • 32
  • Looks like I made a very similar solution as the one you proposed here. I went ahead and posted it on github in case others might find it useful. Here's my answer w/ the link :) http://stackoverflow.com/a/10440510/553394 – Eric G May 03 '12 at 23:17
  • How could I add a way to halt the recursion from within the block? Say I'm using this to locate a specific view, once I've found it I'd like to stop there and not continue searching the rest of the view hierarchy. –  Mar 17 '14 at 17:33
4

Need not create any new function. Just do it when debugging with Xcode.

Set a breakpoint in a view controller, and make the app pause at this breakpoint.

Right click the empty area and press "Add Expression..." in Xcode's Watch window.

Input this line:

(NSString*)[self->_view recursiveDescription]

If the value is too long, right click it and choose "Print Description of ...". You will see all subviews of self.view in the console window. Change self->_view to something else if you don't want to see subviews of self.view.

Done! No gdb!

Vince Yuan
  • 10,533
  • 3
  • 32
  • 27
  • can you explain where the 'empty area' is ? I have tried all around the Xcode window once the breakpoint has fired but can't find where to input the string – SundialSoft Oct 18 '19 at 14:08
4

Here is a recursive code:-

 for (UIView *subViews in yourView.subviews) {
    [self removSubviews:subViews];

}   

-(void)removSubviews:(UIView *)subView
{
   if (subView.subviews.count>0) {
     for (UIView *subViews in subView.subviews) {

        [self removSubviews:subViews];
     }
  }
  else
  {
     NSLog(@"%i",subView.subviews.count);
    [subView removeFromSuperview];
  }
}
Leena
  • 2,678
  • 1
  • 30
  • 43
3

By the way, I made an open source project to help with this sort of task. It's really easy, and uses Objective-C 2.0 blocks to execute code on all views in a hierarchy.

https://github.com/egold/UIViewRecursion

Example:

-(void)makeAllSubviewsGreen
{
    [self.view runBlockOnAllSubviews:^(UIView *view) {

        view.backgroundColor = [UIColor greenColor];
    }];
}
Eric G
  • 1,429
  • 18
  • 26
2

Here is a variation on Ole Begemann's answer above, which adds indentation to illustrate the hierarchy:

// UIView+HierarchyLogging.h
@interface UIView (ViewHierarchyLogging)
- (void)logViewHierarchy:(NSString *)whiteSpaces;
@end

// UIView+HierarchyLogging.m
@implementation UIView (ViewHierarchyLogging)
- (void)logViewHierarchy:(NSString *)whiteSpaces {
    if (whiteSpaces == nil) {
        whiteSpaces = [NSString string];
    }
    NSLog(@"%@%@", whiteSpaces, self);

    NSString *adjustedWhiteSpaces = [whiteSpaces stringByAppendingFormat:@"    "];

    for (UIView *subview in self.subviews) {
        [subview logViewHierarchy:adjustedWhiteSpaces];
    }
}
@end
Community
  • 1
  • 1
jaredsinclair
  • 12,687
  • 5
  • 35
  • 56
1

The code posted in this answer traverses all windows and all views and all of their subviews. It was used to dump a printout of the view hierarchy to NSLog but you can use it as a basis for any traversal of the view hierarchy. It uses a recursive C function to traverse the view tree.

Community
  • 1
  • 1
progrmr
  • 75,956
  • 16
  • 112
  • 147
0

Wish I'd found this page first, but if (for some reason) you want to do this non-recursively, not in a Category, and with more lines of code

Community
  • 1
  • 1
smlxl
  • 49
  • 1
  • 4
0

I wrote a category some time back to debug some views.

IIRC, the posted code is the one that worked. If not, it will point you in the right direction. Use at own risk, etc.

TechZen
  • 64,370
  • 15
  • 118
  • 145
0

I think all of the answers using recursion (except for the debugger option) used categories. If you don't need/want a category, you can just use a instance method. For instance, if you need to get an array of all labels in your view hierarchy, you could do this.

@interface MyViewController ()
@property (nonatomic, retain) NSMutableArray* labelsArray;
@end

@implementation MyViewController

- (void)recursiveFindLabelsInView:(UIView*)inView
{
    for (UIView *view in inView.subviews)
    {
        if([view isKindOfClass:[UILabel class]])
           [self.labelsArray addObject: view];
        else
           [self recursiveFindLabelsInView:view];
    }
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    self.labelsArray = [[NSMutableArray alloc] init];
    [self recursiveFindLabelsInView:self.view];

    for (UILabel *lbl in self.labelsArray)
    {
        //Do something with labels
    }
}
Donovan
  • 301
  • 2
  • 8
0

This displays the hierarchy level as well

@implementation UIView (ViewHierarchyLogging)
- (void)logViewHierarchy:(int)level
{
    NSLog(@"%d - %@", level, self);
    for (UIView *subview in self.subviews)
    {
        [subview logViewHierarchy:(level+1)];
    }
}
@end
snez
  • 2,400
  • 23
  • 20
-1

The method below creates one or more mutable arrays, then loops through the subviews of the input view. In doing so it adds the initial subview then queries as to whether there are any subviews of that subview. If true, it calls itself again. It does so until the all the views of the hierarchy have been added.

-(NSArray *)allSubs:(UIView *)view {

    NSMutableArray * ma = [NSMutableArray new];
    for (UIView * sub in view.subviews){
        [ma addObject:sub];
        if (sub.subviews){
            [ma addObjectsFromArray:[self allSubs:sub]];
        }
    }
    return ma;
}

Call using:

NSArray * subviews = [self allSubs:someView];
Johnny Rockex
  • 4,136
  • 3
  • 35
  • 55
  • 1
    Please use the [edit] link explain how this code works and don't just give the code, as an explanation is more likely to help future readers. See also [answer]. [source](http://stackoverflow.com/users/5244995) – Jed Fox Jan 10 '17 at 23:37
  • 1
    Please see my comment above. – Jed Fox Jan 10 '17 at 23:39