Swift: Get all subviews of a specific type and add to an array

后端 未结 11 711
Happy的楠姐
Happy的楠姐 2020-12-13 12:25

I have a custom class of buttons in a UIView that I\'d like to add to an array so that they\'re easily accessible. Is there a way to get all subviews of a specific class and

相关标签:
11条回答
  • 2020-12-13 12:47

    If you want to update/access those specific subviews then use this,

    for (index,button) in (view.subviews.filter{$0 is UIButton}).enumerated(){
        button.isHidden = false
    }
    
    0 讨论(0)
  • 2020-12-13 12:47

    Let me post my variation of this) but this, finds the first of 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 讨论(0)
  • 2020-12-13 12:50

    To do this recursively (I.e. fetching all subview's views aswell), you can use this generic function:

    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
    }
    

    To fetch all UILabel's in a view hierarchy, just do this:

    let allLabels : [UILabel] = getSubviewsOf(view: theView)
    
    0 讨论(0)
  • 2020-12-13 12:50

    I can't test it right now but this should work in Swift 2:

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

    Which returns an array of YourView

    Here's a tested, typical example, to get a count:

    countDots = allDots!.view.subviews.flatMap{$0 as? Dot}.count
    
    0 讨论(0)
  • 2020-12-13 12:51

    From Swift 4.1, you can use new compactMap (flatMap is now depcrecated): https://developer.apple.com/documentation/swift/sequence/2950916-compactmap (see examples inside)

    In your case, you can use:

    let buttons:[UIButton] = stackView.subviews.compactMap{ $0 as? UIButton }
    

    And you can execute actions to all buttons using map:

    let _ = stackView.subviews.compactMap{ $0 as? UIButton }.map { $0.isSelected = false }
    
    0 讨论(0)
  • 2020-12-13 12:54

    I've gone through all the answers above, they cover the scenario where the views are currently displayed in the window, but don't provide those views which are in view controllers not shown in the window.

    Based on @matt answers, I wrote the following function which recursively go through all the views, including the non visible view controllers, child view controllers, navigation controller view controllers, using the next responders

    (Note: It can be definitively improved, as it adds more complexity on top of the recursion function. consider it as a proof of concept)

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

    Sample usage:

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

    Note: If a modal view controller is currently presented, the above script does not find views which are contained in the presentingViewController. (Can be expanded for that, but I could not find an elegant way to achieve it, as this code is already not elegant by itself :/ )

    Probably is not common to have this need, but maybe helps someone out there :)

    0 讨论(0)
提交回复
热议问题