Swift:递归遍历所有子视图以查找特定类并将其添加到数组中

34

我正在尝试解决这个问题,但是进展缓慢。我在这里提出了一个类似的问题:Swift: Get all subviews of a specific type and add to an array

虽然这种方法可以工作,但是我意识到有许多子视图和子子视图,因此我需要一个函数从主UIView开始,循环遍历所有子视图(以及它们的子视图,直到没有剩下任何一个),并将它添加到数组中,这个数组用于存储自定义按钮类CheckCircle。

本质上,我希望最终得到的是一个CheckCircles数组,其中包含程序动态添加到该视图中的所有CheckCircle。

有什么想法吗?以下是我一直在努力的东西。它似乎没有向数组中添加任何Checkcircles:

    func getSubviewsOfView(v:UIView) -> [CheckCircle] {
        var circleArray = [CheckCircle]()
        // Get the subviews of the view

        var subviews = v.subviews

        if subviews.count == 0 {
            return circleArray
        }

        for subview : AnyObject in subviews{
  if let viewToAppend = subview as? CheckCircle {
        circleArray.append(viewToAppend as CheckCircle)
      }
            getSubviewsOfView(subview as! UIView)
        }
        return circleArray
    }

1
http://mattgemmell.com/what-have-you-tried/ - Aaron Brager
用我拼凑出来的东西进行了编辑。 - Kenji Crosland
@KenjiCrosland 在每次递归调用中,您都在设置一个新的 circleArray 变量,您是否意识到这一点?这是一个非常糟糕的做法。 - Victor Sigler
不知道,我会尽力避免。 - Kenji Crosland
@VictorSigler 我不确定这里真的很重要,因为子视图通常不会超过几个层级。 - Aaron Brager
@AaronBrager 是的,但错误在于没有像你的答案中使用 += 运算符将递归调用与之前的 circleArray 连接起来,这是上述代码的主要问题,因为每次调用函数时都会浪费每个结果。 - Victor Sigler
7个回答

43

根据 Aaron Bragerullstrm 的回答

细节

  • Xcode 9.1,Swift 4
  • Xcode 版本 10.3 (10G8),Swift 5

解决方案

extension UIView {

    class func getAllSubviews<T: UIView>(from parenView: UIView) -> [T] {
        return parenView.subviews.flatMap { subView -> [T] in
            var result = getAllSubviews(from: subView) as [T]
            if let view = subView as? T { result.append(view) }
            return result
        }
    }

    class func getAllSubviews(from parenView: UIView, types: [UIView.Type]) -> [UIView] {
        return parenView.subviews.flatMap { subView -> [UIView] in
            var result = getAllSubviews(from: subView) as [UIView]
            for type in types {
                if subView.classForCoder == type {
                    result.append(subView)
                    return result
                }
            }
            return result
        }
    }

    func getAllSubviews<T: UIView>() -> [T] { return UIView.getAllSubviews(from: self) as [T] }
    func get<T: UIView>(all type: T.Type) -> [T] { return UIView.getAllSubviews(from: self) as [T] }
    func get(all types: [UIView.Type]) -> [UIView] { return UIView.getAllSubviews(from: self, types: types) }
}

使用示例

var allViews = UIView.getAllSubviews(from: simpleView)
func printResult(with text: String) {
    print("\n==============================================")
    print("\(text):\n\(allViews.map { $0.classForCoder } )")
}
printResult(with: "UIView.getAllSubviews(from: simpleView)")

allViews = UIView.getAllSubviews(from: simpleView) as [UILabel]
printResult(with: "UIView.getAllSubviews(from: simpleView) as [UILabel]")

allViews = UIView.getAllSubviews(from: simpleView, types: [UIStackView.self, UILabel.self])
printResult(with: "UIView.getAllSubviews(from: simpleView, types: [UIStackView.self, UILabel.self])")

allViews = simpleView.getAllSubviews()
printResult(with: "simpleView.getAllSubviews()")

allViews = simpleView.getAllSubviews() as [UILabel]
printResult(with: "simpleView.getAllSubviews() as [UILabel]")

allViews = simpleView.get(all: UILabel.self)
printResult(with: "simpleView.get(all: UILabel.self)")

allViews = simpleView.get(all: [UIStackView.self, UILabel.self])
printResult(with: "simpleView.get(all: [UIStackView.self, UILabel.self])")

示例的输出结果

==============================================
UIView.getAllSubviews(from: simpleView):
[UILabel, UIButton, UILabel, UILabel, UILabel, UIStackView]

==============================================
UIView.getAllSubviews(from: simpleView) as [UILabel]:
[UILabel, UILabel, UILabel, UILabel]

==============================================
UIView.getAllSubviews(from: simpleView, types: [UIStackView.self, UILabel.self]):
[UILabel, UILabel, UILabel, UILabel, UIStackView]

==============================================
simpleView.getAllSubviews():
[UILabel, UIButton, UILabel, UILabel, UILabel, UIStackView]

==============================================
simpleView.getAllSubviews() as [UILabel]:
[UILabel, UILabel, UILabel, UILabel]

==============================================
simpleView.get(all: UILabel.self):
[UILabel, UILabel, UILabel, UILabel]

==============================================
simpleView.get(all: [UIStackView.self, UILabel.self]):
[UILabel, UILabel, UILabel, UILabel, UIStackView]

示例的故事板

进入图像描述

其他信息

此外,我建议使用弱引用。 具有指向对象的弱引用的数组


最佳答案使用泛型的非常好! - nayem
这太棒了。谢谢。 - nodebase

37
您可以通过扩展UIView并定义以下函数来简单实现它。
Swift4 代码
extension UIView {
    func findViews<T: UIView>(subclassOf: T.Type) -> [T] {
        return recursiveSubviews.compactMap { $0 as? T }
    }

    var recursiveSubviews: [UIView] {
        return subviews + subviews.flatMap { $0.recursiveSubviews }
    }
}

使用方法

findViews(subclassOf: UILabel.self)
findViews(subclassOf: CheckCircle.self)

33

你的主要问题是,在调用 getSubviewsOfView(subview as! UIView) 函数时(在函数内部进行递归调用),你并没有对结果做任何处理。

此外,你可以删除 count == 0 检查,因为在这种情况下,for…in 循环将被跳过。还有一些不必要的强制类型转换。

假设你想获取一个平铺的 CheckCircle 实例数组,那么我认为下面是你的代码应该适应的改变:

func getSubviewsOfView(v:UIView) -> [CheckCircle] {
    var circleArray = [CheckCircle]()

    for subview in v.subviews as! [UIView] {
        circleArray += getSubviewsOfView(subview)

        if subview is CheckCircle {
            circleArray.append(subview as! CheckCircle)
        }
    }

    return circleArray
}

谢谢。它起作用了。我决定更改一个小部分:如果让viewToAppend = subview as? CheckCircle { circleArray.append(viewToAppend as CheckCircle) } - Kenji Crosland
1
如果你想这么做,我会建议你只需要写 if let subview = subview as? CheckCircleappend(subview)。不需要在 if let 语句中重新命名或者强制转换。 - Aaron Brager

19

我的方法:Swift 3 和泛型!

private func getSubviewsOf<T: UIView>(view: UIView) -> [T] {
    var subviews = [T]()

    for subview in view.subviews {
        subviews += getSubviewsOf(view: subview) as [T]

        if let subview = subview as? T {
            subviews.append(subview)
        }
    }

    return subviews
}

为了获取视图层次结构中的所有UILabel,请按照以下步骤进行操作:
let allLabels: [UILabel] = getSubviewsOf(view: theView)

0
基于Vasily Bodnarchuk、Aaron Brager和ullstrm的答案。
为什么要再写一个?
我个人不喜欢在代码中到处使用“as [XXX]”和“let specific: [Type]”,而是更喜欢将类型传递给函数调用。
let scrollViews = view.getNestedSubviews(ofType: UIScrollView.self)
print(scrollViews) // outputs: [UIScrollView]

我还将All重命名为Nested,因为它更好地传达了函数的递归性质给API调用者。

细节

Swift 4.x,Xcode 9.1+

解决方案

extension UIView {

    class func getNestedSubviews<T: UIView>(view: UIView) -> [T] {
        return view.subviews.flatMap { subView -> [T] in
            var result = getNestedSubviews(view: subView) as [T]
            if let view = subView as? T {
                result.append(view)
            }
            return result
        }
    }

    func getNestedSubviews<T: UIView>() -> [T] {
        return UIView.getNestedSubviews(view: self) as [T]
    }
}

使用方法

let scrollViews = view.getNestedSubviews(ofType: UIScrollView.self)
print(scrollViews) // outputs: [UIScrollView]

0

通过字符串查找(私有)子视图:

我想要增加检测即使被苹果隐藏的私有类(在我们开发者中),通常通过给定的字符串查找类的可能性:

func recursiveSubviews() -> [UIView] {

    if subviews.isEmpty {
        return subviews
    }
    return subviews + subviews.flatMap { $0.recursiveSubviews() }
}

func recursiveSubviews(className: String) -> [UIView] {
    let all = recursiveSubviews()
    let filtered = all.filter {
        String(describing: type(of: $0)) == className
    }
    return filtered
}

现在你可以运行:

activeWindow.recursiveSubviews(className: "_UIContextMenuContainerView")

-1

UITextField不再位于子视图的顶层,因此我使用了这种方法:

@implementation UISearchBar (changeFont)

- (void)setFont:(UIFont *)font {
    for (UIView *v in [self subviews]) {
        if ([v isKindOfClass:[UITextField class]]) {
            UITextField *tf = (UITextField *)v;
            tf.font = font;
            UILabel *l = (UILabel *)[tf valueForKey:@"placeholderLabel"];
            l.font = font;
            break;
        } else if (v.subviews.count) {
            for (UIView *v1 in v.subviews) {
                if ([v1 isKindOfClass:[UITextField class]]) {
                    UITextField *tf = (UITextField *)v1;
                    tf.font = font;
                    UILabel *l = (UILabel *)[tf valueForKey:@"placeholderLabel"];
                    l.font = font;
                    break;
                } else if (v1.subviews.count) {
                    for (UIView *v2 in v1.subviews) {
                        if ([v2 isKindOfClass:[UITextField class]]) {
                            UITextField *tf = (UITextField *)v2;
                            tf.font = font;
                            UILabel *l = (UILabel *)[tf valueForKey:@"placeholderLabel"];
                            l.font = font;
                            break;
                        }
                    }
                }
            }
        }
    }
}

有点啰嗦,但应该考虑到将来文本字段会更深入。

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