Swift:获取特定类型的所有子视图并添加到数组中

63

我有一个在UIView中的自定义按钮类,我希望将它们添加到数组中以便轻松访问。在Swift中,是否有一种方法可以获取特定类别的所有子视图并将其添加到数组中?


2
请注意这里可能有过时的答案,请滚动到底部。 - Fattie
13个回答

117

使用is运算符的filter函数可以过滤特定类别的项目。

let myViews = view.subviews.filter{$0 is MyButtonClass}

MyButtonClass 是要过滤的自定义类。

要将视图过滤并将其转换为自定义类型,请使用 compactMap

let myViews = view.subviews.compactMap{$0 as? MyButtonClass}

2
这个方法可行,但语法有点问题:应该是 let myViews = view.subViews.filter({$0 is MyButtonClass})。 - Kenji Crosland
4
这个语法叫做“尾随闭包”语法:如果你将一个闭包表达式作为函数的唯一参数,并将其作为尾随闭包提供,那么在调用函数时,你不需要在函数名后面写一对圆括号()。 - vadian
我很好奇是否有一种方法可以使这个方法可链接。 - Trip
2
如果在“for in”循环中的“in”后面调用一个对象,似乎无法使用尾随闭包:for myButton in view.subViews.filter{$0 is MyButtonClass} {}会出现错误:“未包含在闭包中的匿名闭包参数”。 - cncool
1
这个解决方案不是递归的,它只列出直接子视图。 - Vilmir
显示剩余2条评论

46

这里是您需要的内容。

    extension UIView {

    /** This is the function to get subViews of a view of a particular type 
*/
    func subViews<T : UIView>(type : T.Type) -> [T]{
        var all = [T]()
        for view in self.subviews {
            if let aView = view as? T{
                all.append(aView)
            }
        }
        return all
    }


/** This is a function to get subViews of a particular type from view recursively. It would look recursively in all subviews and return back the subviews of the type T */
        func allSubViewsOf<T : UIView>(type : T.Type) -> [T]{
            var all = [T]()
            func getSubview(view: UIView) {
                if let aView = view as? T{
                all.append(aView)
                }
                guard view.subviews.count>0 else { return }
                view.subviews.forEach{ getSubview(view: $0) }
            }
            getSubview(view: self)
            return all
        }
    }

您可以这样调用:
let allSubviews = view.allSubViewsOf(type: UIView.self)
let allLabels = view.allSubViewsOf(type: UILabel.self)

view.subviews.forEach(getSubview) - Leo Dabus

33

这里有很多回答过度冗长或者不够普遍。以下是获取任何深度的视图中,所有想要的类别的所有子视图的方法:

extension UIView {
    func subviews<T:UIView>(ofType WhatType:T.Type) -> [T] {
        var result = self.subviews.compactMap {$0 as? T}
        for sub in self.subviews {
            result.append(contentsOf: sub.subviews(ofType:WhatType))
        }
        return result
    }
}

使用方法:

let arr = myView.subviews(ofType: MyButtonClass.self)

2
由于某些原因,我遇到了错误:实例方法'subViews(ofType:)'要求'UITextView.Type'继承自'UIView'。 - humblePilgrim
1
如果使用Swift 4.2,则@humblePilgrim为真,请改用以下变体:func subviews <T:UIView>(ofType WhatType:AnyClass) - > [T] { ... } - michael-martinez
嗨Matt...问题是,“subviews”就是指...等一下 :) ... subviews。就像children指的是children,而不是children + grandchildren。值得注意的是,苹果果断地使用“subviews”来表示subviews,具体来说,在他们的.subviews中。我想不出任何平台、API等在使用或口语中使用subviews来表示“grand”-subviews以及subviews的情况。我只是会稍微不同地命名它(实际上,我不知道最常用的表达方式是什么,也许是nestedSubviews#ofType,或者是subsubviews#ofType,我不知道)。 - undefined
1
@Fattie事实上,我实际使用的这种方法的版本有recursing作为参数。所以你可以选择是否递归。我建议你也这样做。 - undefined
啊!我喜欢那个! - undefined
显示剩余3条评论

23

要递归地执行此操作(即获取所有子视图的视图),可以使用以下通用函数:

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)

13

我现在无法测试,但这应该适用于Swift 2:

view.subviews.flatMap{ $0 as? YourView }

该函数返回一个YourView数组。

这是一个经过测试的典型示例,用于获取计数:

countDots = allDots!.view.subviews.flatMap{$0 as? Dot}.count

8
自 Swift 4.1 起,您可以使用新的 compactMap(flatMap 现在已不推荐使用): https://developer.apple.com/documentation/swift/sequence/2950916-compactmap(内部有示例)
在您的情况下,您可以使用:
let buttons:[UIButton] = stackView.subviews.compactMap{ $0 as? UIButton }

你可以使用map来执行所有按钮的操作:

let _ = stackView.subviews.compactMap{ $0 as? UIButton }.map { $0.isSelected = false }

6
如果您想更新/访问那些特定的子视图,请使用以下方法:
for (index,button) in (view.subviews.filter{$0 is UIButton}).enumerated(){
    button.isHidden = false
}

4
func allSubViews(views: [UIView]) {
    for view in views {
        if let tf = view as? UITextField {
             // Do Something
        }
        self.allSubViews(views: view.subviews)
    }
}

self.allSubViews(views: self.view.subviews)

你并没有完全回答提问者的问题。你的 func 应该返回一个视图数组,因为这是提问者所要求的;而且为什么要使用 UITextFieldMyCustomButton 更加合适。 - dirkgroten

2
对于这种情况,我认为我们可以使用Swift的first.where语法,它比filter.countfilter.isEmpty更有效率。
因为当我们使用filter时,它会创建一个底层数组,因此不太有效,想象一下我们有一个大的集合。
所以只需检查视图的subViews集合是否包含特定类型的类,我们就可以使用这个方法。
let containsBannerViewKind = view.subviews.first(where: { $0 is BannerView }) != nil

这句话的意思是:在此视图的子视图集合中查找BannerView类的第一个匹配项。如果条件成立,我们可以执行进一步的逻辑。
参考文献:https://github.com/realm/SwiftLint/blob/master/Rules.md#first-where

0

让我发布我的变体) 但是这个找到T的第一个

extension UIView {

    func firstSubView<T: UIView>(ofType type: T.Type) -> T? {
        var resultView: T?
        for view in subviews {
            if let view = view as? T {
                resultView = view
                break
            }
            else {
                if let foundView = view.firstSubView(ofType: T.self) {
                    resultView = foundView
                    break
                }
            }
        }
        return resultView
    }
}

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