我有一个在UIView中的自定义按钮类,我希望将它们添加到数组中以便轻松访问。在Swift中,是否有一种方法可以获取特定类别的所有子视图并将其添加到数组中?
extension UIView {
func subviews<T: UIView>(ofType type: T.Type) -> [T] {
subviews.compactMap { $0 as? T } +
subviews.flatMap { $0.subviews(ofType: type) }
}
}
Swift 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)
我已经阅读了以上所有答案,它们涵盖了当前在窗口中显示的视图的情况,但没有提供那些在未显示在窗口中的视图控制器中的视图。
根据@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
中的视图。(可以为此进行扩展,但我无法找到一种优雅的方法来实现它,因为这段代码本身已经不够优雅 :/)
可能这种需求并不常见,但也许能帮助某些人 :)