如何循环遍历一个UIView的所有子视图,以及它们的子视图和它们的子视图

87

我该如何循环遍历UIView的所有子视图、它们的子视图和它们的子视图?


7
如果你真的需要循环遍历,则接受的答案是正确的;如果只是寻找单个视图,则给它打标签并使用 viewWithTag,这样可以避免一些麻烦!- http://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/doc/uid/TP40006816-CH3-SW26 - Norman H
18个回答

126

使用递归:

// 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
2
@docchang:Obj-C块非常适合这种用例。您可以将块传递给方法,执行该块并随着每个方法调用将其向下传递到层次结构中。 - Ole Begemann
不错。我在下面发布了一段添加空格缩进到这个技术的代码片段。 - jaredsinclair

36

这里是我的解决方案,使用递归和一个UIView类的包装器(category/extension)。

// 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
1
你所说的“UIView的包装器”实际上是指“UIView的分类”。 - Dimitris
是的,Dimitris,我指的是类别。 - RamaKrishna Chunduri

29

这里是另一个 Swift 实现:

extension UIView {
    var allSubviews: [UIView] {
        return self.subviews.flatMap { [$0] + $0.allSubviews }
    }
}

简单而且最好的一个 - Balaji Ramakrishnan
1
性感的代码。让我的一天变得美好。 - Alexander Langer

15

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}"? - Rahul Patel
@RahulPatel 它从数组中删除 nil 元素,以确保在对子视图调用 allSubViews 时安全。 - ielyamani

12

我创建东西时都会打上标记,这样就很容易找到任何子视图了。

view = [aView viewWithTag:tag];

12

11

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

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

}

调用示例:

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

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

调用示例:

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

6

在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上,以防其他人可能会发现它有用。这是我的答案和链接 :) https://dev59.com/unE85IYBdhLWcg3wgDpI#10440510 - Eric G
我该如何在块内添加停止递归的方法?比如说,我正在使用这个方法来定位特定的视图,一旦找到它,我想停止搜索其余的视图层次结构。 - user814037

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

有用的解决方案和时间节省者。谢谢你,加一分给你。 - Ashish Ramani

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接