How can I loop through all subviews of a UIView, and their subviews and their subviews
Asked Answered
M

18

88

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

Meat answered 30/4, 2010 at 17:35 Comment(1)
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! - developer.apple.com/library/ios/documentation/UIKit/Reference/…Croesus
G
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];
Grant answered 30/4, 2010 at 17:41 Comment(3)
Is there a way to pass a function in the logViewHierarchy? That way it can suit your needs at different times.Clinch
@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.Grant
Nice. I posted a snippet below that adds whitespace indentation to this technique.Latoshalatouche
E
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;
     }
}
Enlargement answered 15/12, 2010 at 6:45 Comment(3)
Note there are memory leak in allSubViews function: you must either create array as [[[NSMutableArray alloc] init] autorelease] or as [NSMutableArray array] (which is the same).Autophyte
By "a wrapper for UIView" you actually mean "a category for UIView".Countertype
Yes Dimitris, i meant category.Enlargement
G
30

Here's another Swift implementation:

extension UIView {
    var allSubviews: [UIView] {
        return self.subviews.flatMap { [$0] + $0.allSubviews }
    }
}
Gutbucket answered 5/6, 2018 at 4:57 Comment(2)
Simple and Best oneLusatia
Sexy code. Made my dayConsociate
H
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
    }
}
Hernandez answered 20/9, 2016 at 20:56 Comment(2)
What is need of ".flatMap {$0}" in this line "var array = [self.subviews].flatMap {$0}" ?Ottie
@RahulPatel it removes nil elements from an array, for safety when calling allSubViews on subviews.Hernandez
P
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.

Phatic answered 18/2, 2011 at 23:25 Comment(0)
O
12

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

view = [aView viewWithTag:tag];
Ongoing answered 7/7, 2011 at 16:15 Comment(0)
R
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;
    }
}];
Roil answered 16/9, 2014 at 19:57 Comment(0)
C
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];
Clinch answered 16/9, 2011 at 9:50 Comment(2)
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 :) https://mcmap.net/q/236621/-how-can-i-loop-through-all-subviews-of-a-uiview-and-their-subviews-and-their-subviewsFomentation
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.Scrubby
N
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];
  }
}
Nonary answered 20/1, 2012 at 11:4 Comment(1)
helpful solution and time saver.. thanx 1 plus for uIhram
M
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!

Maurine answered 24/10, 2012 at 4:29 Comment(1)
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 stringRetrieve
F
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];
    }];
}
Fomentation answered 3/5, 2012 at 23:3 Comment(0)
L
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
Latoshalatouche answered 14/7, 2013 at 8:32 Comment(0)
C
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.

Cantina answered 1/5, 2010 at 1:6 Comment(0)
K
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.

Knotweed answered 30/4, 2010 at 17:42 Comment(0)
P
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
Promisee answered 18/11, 2011 at 11:14 Comment(0)
R
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

Rightist answered 15/5, 2012 at 21:49 Comment(0)
S
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
    }
}
Subtemperate answered 3/3, 2016 at 16:57 Comment(0)
T
-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];
Tabescent answered 10/1, 2017 at 18:0 Comment(2)
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 How to Answer. sourceHyacinth
Please see my comment above.Hyacinth

© 2022 - 2024 — McMap. All rights reserved.