如何遍历UIView的所有子视图,它们的子视图和它们的子视图


Answers:


122

使用递归:

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

2
有没有办法在logViewHierarchy中传递函数?这样,它可以在不同时间满足您的需求。
docchang 2011年

2
@docchang:Obj-C块非常适合该用例。您将一个块传递给该方法,执行该块,并在每次方法调用时将其向下传递到层次结构中。
Ole Begemann 2011年

真好 我在下面发布了一个片段,为该技术添加了空格缩进。
jaredsinclair

34

好了,这是我为UIView类使用递归和包装器(类别/扩展名)的解决方案。

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

用法:现在您应该遍历所有子视图并根据需要对其进行操作。

//disable all text fields
for(UIView *v in [self.view allSubViews])
{
     if([v isKindOfClass:[UITextField class]])
     {
         ((UITextField*)v).enabled=NO;
     }
}

3
请注意,allSubViews函数中存在内存泄漏:您必须以[[[NSMutableArray alloc] init] autorelease]或形式创建数组[NSMutableArray array](相同)。
ivanzoid 2011年

1
实际上,“ UIView的包装”是指“ UIView的类别”。
Dimitris

是的,Dimitris,我的意思是类别。
RamaKrishna Chunduri 2012年


16

Swift 3中的解决方案,subviews不包含视图本身就提供了所有功能:

extension UIView {
var allSubViews : [UIView] {

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

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

        return array
    }
}

这行“ var array = [self.subviews] .flatMap {$ 0}”中的“ .flatMap {$ 0}”需要什么?
拉胡尔·帕特尔

@RahulPatel会nil从数组中删除元素,以确保在调用allSubViews子视图时的安全。
ielyamani '18



10

这是一个具有实际视图循环和中断功能的示例。

迅速:

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

}

通话示例:

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

反向循环:

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

通话示例:

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

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

通话示例:

[mainView loopViewHierarchy:^(UIView* view, BOOL* stop) {
    if ([view isKindOfClass:[UIButton class]]) {
        /// use the view
        *stop = YES;
    }
}];

7

在Ole Begemann的帮助下。我添加了几行以将块概念合并到其中。

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

在ViewController中使用HierarchyLogging类别。现在,您可以自由执行所需的操作。

void (^ViewActionBlock)(UIView *) = ^(UIView *view) {
    if ([view isKindOfClass:[UIButton class]]) {
        NSLog(@"%@", view);
    }
};
[self.view logViewHierarchy: ViewActionBlock];

看起来我提出了与您在此处提出的解决方案非常相似的解决方案。我继续将其发布在github上,以防其他人发现它有用。这是我的答案,带有链接:) stackoverflow.com/a/10440510/553394
Eric Goldberg

我如何添加一种方法来停止代码块中的递归?假设我正在使用它来定位特定的视图,一旦找到它,我想就此停下来,而不是继续搜索视图层次结构的其余部分。
Mic Pringle 2014年

4

无需创建任何新功能。使用Xcode进行调试时,只需执行此操作即可。

在视图控制器中设置一个断点,并使应用程序在此断点处暂停。

右键单击空白区域,然后在Xcode的“监视”窗口中按“添加表达式...”。

输入此行:

(NSString*)[self->_view recursiveDescription]

如果该值太长,请右键单击它,然后选择“ ...的打印说明”。您将在控制台窗口中看到self.view的所有子视图。如果不想看到self.view的子视图,请将self-> _ view更改为其他内容。

做完了!没有gdb!


您能解释一下“空白区域”在哪里吗?断点触发后,我已经在Xcode窗口附近进行了所有尝试,但是找不到在哪里输入字符串
SundialSoft

4

这是一个递归代码:

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

有用的解决方案和时间节省方法.. thanx为您加1
Brainstorm Technolabs 2015年

3

顺便说一句,我做了一个开源项目来帮助完成此类任务。这真的很容易,并且使用Objective-C 2.0块在层次结构中的所有视图上执行代码。

https://github.com/egold/UIViewRecursion

例:

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

        view.backgroundColor = [UIColor greenColor];
    }];
}

2

这是上述Ole Begemann答案的变体,其中增加了缩进以说明层次结构:

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

1

此答案中发布的代码遍历所有窗口,所有视图及其所有子视图。它用于将视图层次结构的打印输出转储到NSLog,但是您可以将其用作视图层次结构的任何遍历的基础。它使用递归C函数遍历视图树。


0

我早些时候写了一个类别来调试一些视图。

IIRC,发布的代码是有效的代码。如果没有,它将为您指明正确的方向。使用时需自担风险等。


0

这也会显示层次结构级别

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

0

希望我先找到此页面,但是如果(由于某种原因)您想要非递归地执行此操作,而不是在Category中,并且需要更多的代码行


0

我认为使用递归的所有答案(除debugger选项外)均使用类别。如果不需要/想要一个类别,则可以使用实例方法。例如,如果您需要获取视图层次结构中所有标签的数组,则可以执行此操作。

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

-1

下面的方法创建一个或多个可变数组,然后循环遍历输入视图的子视图。这样,它将添加初始子视图,然后查询该子视图是否存在任何子视图。如果为true,它将再次调用自身。这样做直到添加了层次结构的所有视图。

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

拨打电话:

NSArray * subviews = [self allSubs:someView];

1
请使用编辑链接来说明此代码的工作原理,而不仅仅是给出代码,因为这样的解释更有可能对未来的读者有所帮助。另请参阅“如何回答”消息来源
杰德·福克斯

1
请在上方查看我的评论。
杰德·福克斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.