【问题标题】:Swift: Recursively cycle through all subviews to find a specific class and append to an arraySwift:递归循环遍历所有子视图以查找特定类并附加到数组
【发布时间】:2015-11-24 21:40:21
【问题描述】:

花时间试图弄清楚这一点。我在这里问了一个类似的问题:Swift: Get all subviews of a specific type and add to an array

虽然这可行,但我意识到有很多子视图和子子视图,所以我需要一个从主 UIView 开始的函数,循环遍历所有子视图(及其子视图,直到没有任何剩余)和将其添加到我命名为 CheckCircle 的自定义按钮类的数组中。

基本上,我希望得到一个 CheckCircles 数组,它们构成以编程方式添加到该视图的所有 CheckCircles。

有什么想法吗?这是我一直在做的事情。它似乎没有将任何 Checkcircles 附加到数组中:

    func getSubviewsOfView(v:UIView) -> [CheckCircle] {
        var circleArray = [CheckCircle]()
        // Get the subviews of the view

        var subviews = v.subviews

        if subviews.count == 0 {
            return circleArray
        }

        for subview : AnyObject in subviews{
  if let viewToAppend = subview as? CheckCircle {
        circleArray.append(viewToAppend as CheckCircle)
      }
            getSubviewsOfView(subview as! UIView)
        }
        return circleArray
    }

【问题讨论】:

  • 用我拼凑的东西编辑。
  • @KenjiCrosland 在每次递归调用中,您都在设置一个新的circleArray 变量,您知道这一点吗?这是一个非常糟糕的做法
  • 不知道。我会尽量避免。
  • @VictorSigler 我不确定这是否真的很重要,因为子视图通常不会超过几个级别。

标签: arrays swift recursion uiview


【解决方案1】:

基于 Aaron Bragerullstrm 的答案

详情

  • Xcode 9.1,Swift 4,
  • Xcode 版本 10.3 (10G8),Swift 5

解决方案

extension UIView {

    class func getAllSubviews<T: UIView>(from parenView: UIView) -> [T] {
        return parenView.subviews.flatMap { subView -> [T] in
            var result = getAllSubviews(from: subView) as [T]
            if let view = subView as? T { result.append(view) }
            return result
        }
    }

    class func getAllSubviews(from parenView: UIView, types: [UIView.Type]) -> [UIView] {
        return parenView.subviews.flatMap { subView -> [UIView] in
            var result = getAllSubviews(from: subView) as [UIView]
            for type in types {
                if subView.classForCoder == type {
                    result.append(subView)
                    return result
                }
            }
            return result
        }
    }

    func getAllSubviews<T: UIView>() -> [T] { return UIView.getAllSubviews(from: self) as [T] }
    func get<T: UIView>(all type: T.Type) -> [T] { return UIView.getAllSubviews(from: self) as [T] }
    func get(all types: [UIView.Type]) -> [UIView] { return UIView.getAllSubviews(from: self, types: types) }
}

使用示例

var allViews = UIView.getAllSubviews(from: simpleView)
func printResult(with text: String) {
    print("\n==============================================")
    print("\(text):\n\(allViews.map { $0.classForCoder } )")
}
printResult(with: "UIView.getAllSubviews(from: simpleView)")

allViews = UIView.getAllSubviews(from: simpleView) as [UILabel]
printResult(with: "UIView.getAllSubviews(from: simpleView) as [UILabel]")

allViews = UIView.getAllSubviews(from: simpleView, types: [UIStackView.self, UILabel.self])
printResult(with: "UIView.getAllSubviews(from: simpleView, types: [UIStackView.self, UILabel.self])")

allViews = simpleView.getAllSubviews()
printResult(with: "simpleView.getAllSubviews()")

allViews = simpleView.getAllSubviews() as [UILabel]
printResult(with: "simpleView.getAllSubviews() as [UILabel]")

allViews = simpleView.get(all: UILabel.self)
printResult(with: "simpleView.get(all: UILabel.self)")

allViews = simpleView.get(all: [UIStackView.self, UILabel.self])
printResult(with: "simpleView.get(all: [UIStackView.self, UILabel.self])")

样本输出

==============================================
UIView.getAllSubviews(from: simpleView):
[UILabel, UIButton, UILabel, UILabel, UILabel, UIStackView]

==============================================
UIView.getAllSubviews(from: simpleView) as [UILabel]:
[UILabel, UILabel, UILabel, UILabel]

==============================================
UIView.getAllSubviews(from: simpleView, types: [UIStackView.self, UILabel.self]):
[UILabel, UILabel, UILabel, UILabel, UIStackView]

==============================================
simpleView.getAllSubviews():
[UILabel, UIButton, UILabel, UILabel, UILabel, UIStackView]

==============================================
simpleView.getAllSubviews() as [UILabel]:
[UILabel, UILabel, UILabel, UILabel]

==============================================
simpleView.get(all: UILabel.self):
[UILabel, UILabel, UILabel, UILabel]

==============================================
simpleView.get(all: [UIStackView.self, UILabel.self]):
[UILabel, UILabel, UILabel, UILabel, UIStackView]

样本故事板

其他信息

另外,我建议使用弱引用。 Array with weak references to objects

【讨论】:

  • 使用泛型的最佳答案!
  • 这是史诗。谢谢。
【解决方案2】:

基于 Vasily BodnarchukAaron Bragerullstrm 的回答。

为什么是另一个?

我个人不喜欢 as [XXX]let specific: [Type],而是将类型传递给函数调用,例如

let scrollViews = view.getNestedSubviews(ofType: UIScrollView.self)
print(scrollViews) // outputs: [UIScrollView]

我还将All 重命名为Nested,因为它可以更好地向 API 调用者传达函数的递归性质。

详情

Swift 4.x、Xcode 9.1+

解决方案

extension UIView {

    class func getNestedSubviews<T: UIView>(view: UIView) -> [T] {
        return view.subviews.flatMap { subView -> [T] in
            var result = getNestedSubviews(view: subView) as [T]
            if let view = subView as? T {
                result.append(view)
            }
            return result
        }
    }

    func getNestedSubviews<T: UIView>() -> [T] {
        return UIView.getNestedSubviews(view: self) as [T]
    }
}

用法

let scrollViews = view.getNestedSubviews(ofType: UIScrollView.self)
print(scrollViews) // outputs: [UIScrollView]

【讨论】:

    【解决方案3】:

    你可以通过扩展 UIView 并定义以下函数来实现它。

    Swift4 代码

    extension UIView {
        func findViews<T: UIView>(subclassOf: T.Type) -> [T] {
            return recursiveSubviews.compactMap { $0 as? T }
        }
    
        var recursiveSubviews: [UIView] {
            return subviews + subviews.flatMap { $0.recursiveSubviews }
        }
    }
    

    用法

    findViews(subclassOf: UILabel.self)
    findViews(subclassOf: CheckCircle.self)
    

    【讨论】:

      【解决方案4】:

      UITextField 不再在子视图的顶层,所以我使用这种方法:

      @implementation UISearchBar (changeFont)
      
      - (void)setFont:(UIFont *)font {
          for (UIView *v in [self subviews]) {
              if ([v isKindOfClass:[UITextField class]]) {
                  UITextField *tf = (UITextField *)v;
                  tf.font = font;
                  UILabel *l = (UILabel *)[tf valueForKey:@"placeholderLabel"];
                  l.font = font;
                  break;
              } else if (v.subviews.count) {
                  for (UIView *v1 in v.subviews) {
                      if ([v1 isKindOfClass:[UITextField class]]) {
                          UITextField *tf = (UITextField *)v1;
                          tf.font = font;
                          UILabel *l = (UILabel *)[tf valueForKey:@"placeholderLabel"];
                          l.font = font;
                          break;
                      } else if (v1.subviews.count) {
                          for (UIView *v2 in v1.subviews) {
                              if ([v2 isKindOfClass:[UITextField class]]) {
                                  UITextField *tf = (UITextField *)v2;
                                  tf.font = font;
                                  UILabel *l = (UILabel *)[tf valueForKey:@"placeholderLabel"];
                                  l.font = font;
                                  break;
                              }
                          }
                      }
                  }
              }
          }
      }
      

      有点啰嗦,但应该考虑到文本字段将来会更深入

      【讨论】:

        【解决方案5】:

        我使用 swift 3 和泛型的方法!

        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)
        

        【讨论】:

          【解决方案6】:

          您的主要问题是,当您调用 getSubviewsOfView(subview as! UIView)(在函数内递归)时,您没有对结果做任何事情。

          您也可以删除count == 0 检查,因为在这种情况下,for…in 循环将被跳过。你还有一堆不必要的演员表

          假设您希望获得CheckCircle 实例的平面数组,我认为您的代码的这种改编应该可以工作:

          func getSubviewsOfView(v:UIView) -> [CheckCircle] {
              var circleArray = [CheckCircle]()
          
              for subview in v.subviews as! [UIView] {
                  circleArray += getSubviewsOfView(subview)
          
                  if subview is CheckCircle {
                      circleArray.append(subview as! CheckCircle)
                  }
              }
          
              return circleArray
          }
          

          【讨论】:

          • 谢谢。有效。我决定改变一小部分: if let viewToAppend = subview as? CheckCircle { circleArray.append(viewToAppend as CheckCircle) }
          • @KenjiCrosland 如果你想这样做,我会这样做 if let subview = subview as? CheckCircleappend(subview)。无需重命名或在if let 范围内强制转换。
          猜你喜欢
          • 2012-03-10
          • 2014-09-25
          • 1970-01-01
          • 2011-02-05
          • 1970-01-01
          • 2012-06-03
          • 2021-01-18
          • 2015-11-16
          • 2016-06-22
          相关资源
          最近更新 更多