-
-
Save isoiphone/c729437bfaa10e4450ea to your computer and use it in GitHub Desktop.
extension UIView { | |
func forEachSubviewOfType<V: UIView>(type: V.Type, @noescape apply block: V -> Void) { | |
for view in subviews { | |
if let view = view as? V { | |
block(view) | |
} else { | |
view.forEachSubviewOfType(V.self, apply: block) | |
} | |
} | |
} | |
} |
searchController.searchBar.forEachSubviewOfType(UITextField.self) { textField in | |
textField.textColor = .whiteColor() | |
} |
I feel like there should be some nice way do something like this:
subviews.filter{$0 is V}.forEach{block($0 as! V)}
but keeping the recursive behavior (using flatmap to glob things together)
Seems like you're doing two things at one: flattening the tree and operating on it. If the hierarchy was huge this might make sense (memory allocation time). In your case it probably isn't so I think I would do something like this.
func flattenSubviews(root: UIView) -> [UIView] {
return [root] + root.subviews.flatMap{ flattenSubviews($0) }
}
Note: this is a free function not in an extension.
Then use filter
and forEach
as above.
If you're going to have a flatten routine, you might as well make it generic. All we need is the type and how to get children.
func flatten<T>(root: T, children: (T) -> [T]) -> [T] {
return [root] + children(root).flatMap{ flatten($0, children: children) }
}
Then your extension method looks like this:
extension UIView {
func forEachSubviewOfType<V: UIView>(type: V.Type, @noescape apply block: UIView -> Void) {
flatten(self) { $0.subviews }.filter { $0 is V }.forEach(block)
}
}
Edit: Hah! iosdevzone chimed in while I was away and said something very similar.
I think you should split up the mapping logic and the forEach logic. Then you can recur over anything you like.
extension SequenceType {
/// Recursively map, and stop recurring at the first non-nil element in a branch
///
///- Parameter recursed: the next level of children
///- Parameter map: transform the non-nil elements
func flatMapped<Mapped>(
@noescape recursed recursed: Generator.Element -> Self,
@noescape _ map: Generator.Element -> Mapped?
) -> [Mapped] {
return reduce([]){
if let mapped = map($1) {return $0 + [mapped]}
return $0 + recursed($1).flatMapped(recursed: recursed, map)
}
}
}
UISearchBar().subviews.flatMapped(recursed: {$0.subviews}){$0 as? UITextField}
.forEach{$0.textColor = .whiteColor()}
struct Branch {
let datum: Int
let branches: [Branch]
}
let branch1 = Branch(datum: 1, branches: [])
let branch2 = Branch(datum: 2, branches: [branch1])
let branch3 = Branch(datum: 3, branches: [branch2])
let branches = [branch1, branch2, branch3].flatMapped(recursed: {$0.branches})
{$0.datum < 2 ? $0 : nil}
// branch1, thrice
branches
Here's iosdevzone's code as an extension:
extension SequenceType {
func recursed(recursed: Generator.Element -> Self) -> [Generator.Element] {
return flatMap{[$0] + recursed($0).recursed(recursed)}
}
}
UISearchBar().subviews.recursed{$0.subviews}.flatMap{$0 as? UITextField}
.forEach{$0.textColor = .whiteColor()}
That is great. I updated to something similar.
Using map for side effect generates a warning (I think its fine, but compiler disagrees) so I changed it.
Also decided to keep the else-recurse behavior I had before, but its pretty arbitrary anyway.