Swift: Recursively cycle through all subviews to find a specific class and append to an array

前端 未结 6 654
故里飘歌
故里飘歌 2020-12-06 04:46

Having a devil of a time trying to figure this out. I asked a similar question here: Swift: Get all subviews of a specific type and add to an array

While this works,

相关标签:
6条回答
  • 2020-12-06 04:55

    Based on Aaron Brager and ullstrm answers

    Details

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

    Solution

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

    Usage sample

    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])")
    

    Output of the sample

    ==============================================
    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]
    

    Storyboard of the sample

    Other info

    Also, I suggest to work with weak references. Array with weak references to objects

    0 讨论(0)
  • 2020-12-06 04:58

    Based on Vasily Bodnarchuk's, Aaron Brager's, and ullstrm's answers.

    Why another one?

    I personally do not like to have as [XXX] and let specific: [Type], all over but rather pass the type into function calls, e.g.

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

    I also renamed All to Nested because it conveys the recursive nature of the function to the API caller better.

    Details

    Swift 4.x, Xcode 9.1+

    Solution

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

    Usage

    let scrollViews = view.getNestedSubviews(ofType: UIScrollView.self)
    print(scrollViews) // outputs: [UIScrollView]
    
    0 讨论(0)
  • 2020-12-06 05:01

    Your main problem is that when you call getSubviewsOfView(subview as! UIView) (recursively, within the function), you aren't doing anything with the result.

    You also can delete the count == 0 check, since in that case the for…in loop will just be skipped. You also have a bunch of unnecessary casts

    Assuming your desire is to get a flat array of CheckCircle instances, I think this adaptation of your code should work:

    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
    }
    
    0 讨论(0)
  • 2020-12-06 05:01

    My approach with swift 3 and generics!

    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-06 05:07

    the UITextField is no longer on the top layer of subviews, so I use this method:

    @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;
                            }
                        }
                    }
                }
            }
        }
    }
    

    a bit long winded, but should account for the textfield moving deeper in the future

    0 讨论(0)
  • 2020-12-06 05:16

    You can implement it simply by extending UIView and defining the following functions.

    Swift4 Code

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

    Usage

    findViews(subclassOf: UILabel.self)
    findViews(subclassOf: CheckCircle.self)
    
    0 讨论(0)
提交回复
热议问题