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

后端 未结 11 726
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: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(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 :)

提交回复
热议问题