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

问题描述 投票:0回答:13

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

ios arrays swift class subview
13个回答
117
投票

使用

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

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

MyButtonClass
是要过滤的自定义类。

要过滤视图并将其投射为自定义类型,请使用

compactMap

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

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)

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)

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 }

您可以使用地图对所有按钮执行操作:

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)

2
投票

对于这种情况,我认为我们可以使用 Swift 的

first.where
语法,它比
filter.count
filter.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
    }
}

0
投票

针对那些喜欢函数式风格的人的递归单行:

extension UIView {
    func subviews<T: UIView>(ofType type: T.Type) -> [T] {
        subviews.compactMap { $0 as? T } +
            subviews.flatMap { $0.subviews(ofType: type) }
    }
}

-1
投票

斯威夫特5

func findViewInside<T>(views: [UIView]?, findView: [T] = [], findType: T.Type = T.self) -> [T] {
    var findView = findView
    let views = views ?? []
    guard views.count > .zero else { return findView }
    let firstView = views[0]
    var loopViews = views.dropFirst()
    
    if let typeView = firstView as? T {
        findView = findView + [typeView]
        return findViewInside(views: Array(loopViews), findView: findView)
    } else if firstView.subviews.count > .zero {
        firstView.subviews.forEach { loopViews.append($0) }
        return findViewInside(views: Array(loopViews), findView: findView)
    } else {
        return findViewInside(views: Array(loopViews), findView: findView)
    }
}

使用方法:

findViewInside(views: (YourViews), findType: (YourType).self)

-2
投票

我已经浏览了上面的所有答案,它们涵盖了视图当前显示在窗口中的场景,但不提供视图控制器中未显示在窗口中的视图。

基于@matt的答案,我编写了以下函数,它使用下一个响应者递归地遍历所有视图,包括不可见视图控制器、子视图控制器、导航控制器视图控制器

注意:它可以得到明确的改进,因为它在递归函数之上增加了更多的复杂性。将其视为概念证明)

    /// Returns the array of subviews in the view hierarchy which match the provided type, including any hidden
    /// - Parameter type: the type filter
    /// - Returns: the resulting array of elements matching the given type
    func allSubviews<T:UIView>(of type:T.Type) -> [T] {
        var result = self.subviews.compactMap({$0 as? T})
        var subviews = self.subviews

        // *********** Start looking for non-visible view into view controllers ***********
        // Inspect also the non visible views on the same level
        var notVisibleViews = [UIView]()
        subviews.forEach { (v) in
            if let vc = v.next as? UIViewController {
                let childVCViews = vc.children.filter({$0.isViewLoaded && $0.view.window == nil }).compactMap({$0.view})
                notVisibleViews.append(contentsOf: childVCViews)
            }
            if let vc = v.next as? UINavigationController {
                let nvNavVC = vc.viewControllers.filter({$0.isViewLoaded && $0.view.window == nil })
                let navVCViews = nvNavVC.compactMap({$0.view})
                notVisibleViews.append(contentsOf: navVCViews)
                // detect child vc in not visible vc in the nav controller
                let childInNvNavVC = nvNavVC.compactMap({$0.children}).reduce([],+).compactMap({$0.view})
                notVisibleViews.append(contentsOf: childInNvNavVC)
            }
            if let vc = v.next as? UITabBarController {
                let tabViewControllers = vc.viewControllers?.filter({$0.isViewLoaded && $0.view.window == nil }) ?? [UIViewController]()
                // detect navigation controller in the hidden tab bar view controllers
                let vc1 = tabViewControllers.compactMap({$0 as? UINavigationController})
                vc1.forEach { (vc) in
                    let nvNavVC = vc.viewControllers.filter({$0.isViewLoaded && $0.view.window == nil })
                    let navVCViews = nvNavVC.compactMap({$0.view})
                    notVisibleViews.append(contentsOf: navVCViews)
                    // detect child vc in not visible vc in the nav controller
                    let childInNvNavVC = nvNavVC.compactMap({$0.children}).reduce([],+).compactMap({$0.view})
                    notVisibleViews.append(contentsOf: childInNvNavVC)
                }
                // ad non-navigation controller in the hidden tab bar view controllers
                let tabVCViews = tabViewControllers.compactMap({($0 as? UINavigationController) == nil ? $0.view : nil})
                notVisibleViews.append(contentsOf: tabVCViews)
            }
        }
        subviews.append(contentsOf: notVisibleViews.removingDuplicates())

        // *********** End looking for non-visible view into view controllers ***********

        subviews.forEach({result.append(contentsOf: $0.allSubviews(of: type))})

        return result.removingDuplicates()
    }

    extension Array where Element: Hashable {
        func removingDuplicates() -> [Element] {
            var dict = [Element: Bool]()
            return filter { dict.updateValue(true, forKey: $0) == nil }
        }
    }

使用示例:

let allButtons = keyWindow.allSubviews(of: UIButton.self)

注意:如果当前存在模态视图控制器,则上述脚本找不到

presentingViewController
中包含的视图。 (可以对此进行扩展,但我找不到一种优雅的方式来实现它,因为这段代码本身已经不优雅了:/)

这种需求可能并不常见,但也许可以帮助别人:)

© www.soinside.com 2019 - 2024. All rights reserved.