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,
Based on Aaron Brager and ullstrm answers
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]
Also, I suggest to work with weak references. Array with weak references to objects
Based on Vasily Bodnarchuk's, Aaron Brager's, and ullstrm's answers.
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.
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]
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
}
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)
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
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)