Swift:获取特定类型的所有子视图并添加到数组中
Swift: Get all subviews of a specific type and add to an array
我在 UIView 中有一个自定义 class 按钮,我想将其添加到数组中以便于访问它们。有没有办法获取特定 class 的所有子视图并将其添加到 Swift 中的数组?
我现在无法测试,但这应该可以在 Swift 2:
view.subviews.flatMap{ [=10=] as? YourView }
其中returns一个数组YourView
这是一个经过测试的典型示例,用于计算:
countDots = allDots!.view.subviews.flatMap{[=11=] as? Dot}.count
使用 is
运算符的 filter
函数可以过滤特定 class 的项目。
let myViews = view.subviews.filter{[=10=] is MyButtonClass}
MyButtonClass
是要过滤的自定义 class。
要过滤并投射 视图到自定义类型,请使用compactMap
let myViews = view.subviews.compactMap{[=11=] as? MyButtonClass}
如果你想update/access那些特定的子视图然后使用这个,
for (index,button) in (view.subviews.filter{[=10=] is UIButton}).enumerated(){
button.isHidden = false
}
要递归执行此操作(即同时获取所有子视图的视图),您可以使用此通用函数:
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)
给你
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: [=10=]) }
}
getSubview(view: self)
return all
}
}
你可以这样称呼它
let allSubviews = view.allSubViewsOf(type: UIView.self)
let allLabels = view.allSubViewsOf(type: UILabel.self)
对于这种情况,我认为我们可以使用Swift的first.where
语法,它比filter.count
、filter.isEmpty
更有效率。
因为当我们使用filter
时,它会创建一个底层数组,因此无效,假设我们有一个大集合。
所以只需检查视图的 subViews
集合是否包含特定种类的 class,我们可以使用这个
let containsBannerViewKind = view.subviews.first(where: { [=10=] is BannerView }) != nil
这相当于:在该视图的子视图集合中找到与 BannerView class 的第一个匹配项。所以如果这是真的,我们就可以进行进一步的推理了。
参考:https://github.com/realm/SwiftLint/blob/master/Rules.md#first-where
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)
从 Swift 4.1 开始,您可以使用新的 compactMap(flatMap 现已弃用):https://developer.apple.com/documentation/swift/sequence/2950916-compactmap
(见里面的例子)
在你的情况下,你可以使用:
let buttons:[UIButton] = stackView.subviews.compactMap{ [=10=] as? UIButton }
并且您可以使用地图对所有按钮执行操作:
let _ = stackView.subviews.compactMap{ [=11=] as? UIButton }.map { [=11=].isSelected = false }
让我post我的变体)但是这个,找到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
}
}
这里的许多答案不必要地冗长或不够笼统。以下是如何获取一个视图的所有子视图,在 任何深度,它们是 任何 所需的 class:
extension UIView {
func subviews<T:UIView>(ofType WhatType:T.Type) -> [T] {
var result = self.subviews.compactMap {[=10=] as? T}
for sub in self.subviews {
result.append(contentsOf: sub.subviews(ofType:WhatType))
}
return result
}
}
使用方法:
let arr = myView.subviews(ofType: MyButtonClass.self)
我已经看完了上面的所有答案,它们涵盖了视图当前显示在 window 中的场景,但不提供视图控制器中未显示在 window 中的那些视图=31=].
根据@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({[=10=] 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({[=10=].isViewLoaded && [=10=].view.window == nil }).compactMap({[=10=].view})
notVisibleViews.append(contentsOf: childVCViews)
}
if let vc = v.next as? UINavigationController {
let nvNavVC = vc.viewControllers.filter({[=10=].isViewLoaded && [=10=].view.window == nil })
let navVCViews = nvNavVC.compactMap({[=10=].view})
notVisibleViews.append(contentsOf: navVCViews)
// detect child vc in not visible vc in the nav controller
let childInNvNavVC = nvNavVC.compactMap({[=10=].children}).reduce([],+).compactMap({[=10=].view})
notVisibleViews.append(contentsOf: childInNvNavVC)
}
if let vc = v.next as? UITabBarController {
let tabViewControllers = vc.viewControllers?.filter({[=10=].isViewLoaded && [=10=].view.window == nil }) ?? [UIViewController]()
// detect navigation controller in the hidden tab bar view controllers
let vc1 = tabViewControllers.compactMap({[=10=] as? UINavigationController})
vc1.forEach { (vc) in
let nvNavVC = vc.viewControllers.filter({[=10=].isViewLoaded && [=10=].view.window == nil })
let navVCViews = nvNavVC.compactMap({[=10=].view})
notVisibleViews.append(contentsOf: navVCViews)
// detect child vc in not visible vc in the nav controller
let childInNvNavVC = nvNavVC.compactMap({[=10=].children}).reduce([],+).compactMap({[=10=].view})
notVisibleViews.append(contentsOf: childInNvNavVC)
}
// ad non-navigation controller in the hidden tab bar view controllers
let tabVCViews = tabViewControllers.compactMap({([=10=] as? UINavigationController) == nil ? [=10=].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: [=10=].allSubviews(of: type))})
return result.removingDuplicates()
}
extension Array where Element: Hashable {
func removingDuplicates() -> [Element] {
var dict = [Element: Bool]()
return filter { dict.updateValue(true, forKey: [=10=]) == nil }
}
}
示例用法:
let allButtons = keyWindow.allSubviews(of: UIButton.self)
注意:如果当前呈现模态视图控制器,则上述脚本不会找到包含在presentingViewController
中的视图。 (可以为此进行扩展,但我找不到实现它的 优雅 方法,因为这段代码本身已经不够优雅了:/)
这种需求可能并不常见,但也许可以帮助那里的人 :)
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([=10=]) }
return findViewInside(views: Array(loopViews), findView: findView)
} else {
return findViewInside(views: Array(loopViews), findView: findView)
}
}
使用方法:
findViewInside(views: (YourViews), findType: (YourType).self)
我在 UIView 中有一个自定义 class 按钮,我想将其添加到数组中以便于访问它们。有没有办法获取特定 class 的所有子视图并将其添加到 Swift 中的数组?
我现在无法测试,但这应该可以在 Swift 2:
view.subviews.flatMap{ [=10=] as? YourView }
其中returns一个数组YourView
这是一个经过测试的典型示例,用于计算:
countDots = allDots!.view.subviews.flatMap{[=11=] as? Dot}.count
使用 is
运算符的 filter
函数可以过滤特定 class 的项目。
let myViews = view.subviews.filter{[=10=] is MyButtonClass}
MyButtonClass
是要过滤的自定义 class。
要过滤并投射 视图到自定义类型,请使用compactMap
let myViews = view.subviews.compactMap{[=11=] as? MyButtonClass}
如果你想update/access那些特定的子视图然后使用这个,
for (index,button) in (view.subviews.filter{[=10=] is UIButton}).enumerated(){
button.isHidden = false
}
要递归执行此操作(即同时获取所有子视图的视图),您可以使用此通用函数:
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)
给你
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: [=10=]) }
}
getSubview(view: self)
return all
}
}
你可以这样称呼它
let allSubviews = view.allSubViewsOf(type: UIView.self)
let allLabels = view.allSubViewsOf(type: UILabel.self)
对于这种情况,我认为我们可以使用Swift的first.where
语法,它比filter.count
、filter.isEmpty
更有效率。
因为当我们使用filter
时,它会创建一个底层数组,因此无效,假设我们有一个大集合。
所以只需检查视图的 subViews
集合是否包含特定种类的 class,我们可以使用这个
let containsBannerViewKind = view.subviews.first(where: { [=10=] is BannerView }) != nil
这相当于:在该视图的子视图集合中找到与 BannerView class 的第一个匹配项。所以如果这是真的,我们就可以进行进一步的推理了。
参考:https://github.com/realm/SwiftLint/blob/master/Rules.md#first-where
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)
从 Swift 4.1 开始,您可以使用新的 compactMap(flatMap 现已弃用):https://developer.apple.com/documentation/swift/sequence/2950916-compactmap (见里面的例子)
在你的情况下,你可以使用:
let buttons:[UIButton] = stackView.subviews.compactMap{ [=10=] as? UIButton }
并且您可以使用地图对所有按钮执行操作:
let _ = stackView.subviews.compactMap{ [=11=] as? UIButton }.map { [=11=].isSelected = false }
让我post我的变体)但是这个,找到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
}
}
这里的许多答案不必要地冗长或不够笼统。以下是如何获取一个视图的所有子视图,在 任何深度,它们是 任何 所需的 class:
extension UIView {
func subviews<T:UIView>(ofType WhatType:T.Type) -> [T] {
var result = self.subviews.compactMap {[=10=] as? T}
for sub in self.subviews {
result.append(contentsOf: sub.subviews(ofType:WhatType))
}
return result
}
}
使用方法:
let arr = myView.subviews(ofType: MyButtonClass.self)
我已经看完了上面的所有答案,它们涵盖了视图当前显示在 window 中的场景,但不提供视图控制器中未显示在 window 中的那些视图=31=].
根据@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({[=10=] 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({[=10=].isViewLoaded && [=10=].view.window == nil }).compactMap({[=10=].view})
notVisibleViews.append(contentsOf: childVCViews)
}
if let vc = v.next as? UINavigationController {
let nvNavVC = vc.viewControllers.filter({[=10=].isViewLoaded && [=10=].view.window == nil })
let navVCViews = nvNavVC.compactMap({[=10=].view})
notVisibleViews.append(contentsOf: navVCViews)
// detect child vc in not visible vc in the nav controller
let childInNvNavVC = nvNavVC.compactMap({[=10=].children}).reduce([],+).compactMap({[=10=].view})
notVisibleViews.append(contentsOf: childInNvNavVC)
}
if let vc = v.next as? UITabBarController {
let tabViewControllers = vc.viewControllers?.filter({[=10=].isViewLoaded && [=10=].view.window == nil }) ?? [UIViewController]()
// detect navigation controller in the hidden tab bar view controllers
let vc1 = tabViewControllers.compactMap({[=10=] as? UINavigationController})
vc1.forEach { (vc) in
let nvNavVC = vc.viewControllers.filter({[=10=].isViewLoaded && [=10=].view.window == nil })
let navVCViews = nvNavVC.compactMap({[=10=].view})
notVisibleViews.append(contentsOf: navVCViews)
// detect child vc in not visible vc in the nav controller
let childInNvNavVC = nvNavVC.compactMap({[=10=].children}).reduce([],+).compactMap({[=10=].view})
notVisibleViews.append(contentsOf: childInNvNavVC)
}
// ad non-navigation controller in the hidden tab bar view controllers
let tabVCViews = tabViewControllers.compactMap({([=10=] as? UINavigationController) == nil ? [=10=].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: [=10=].allSubviews(of: type))})
return result.removingDuplicates()
}
extension Array where Element: Hashable {
func removingDuplicates() -> [Element] {
var dict = [Element: Bool]()
return filter { dict.updateValue(true, forKey: [=10=]) == nil }
}
}
示例用法:
let allButtons = keyWindow.allSubviews(of: UIButton.self)
注意:如果当前呈现模态视图控制器,则上述脚本不会找到包含在presentingViewController
中的视图。 (可以为此进行扩展,但我找不到实现它的 优雅 方法,因为这段代码本身已经不够优雅了:/)
这种需求可能并不常见,但也许可以帮助那里的人 :)
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([=10=]) }
return findViewInside(views: Array(loopViews), findView: findView)
} else {
return findViewInside(views: Array(loopViews), findView: findView)
}
}
使用方法:
findViewInside(views: (YourViews), findType: (YourType).self)