Skip to content

Instantly share code, notes, and snippets.

@isoiphone
Last active April 26, 2016 07:15
Show Gist options
  • Save isoiphone/c729437bfaa10e4450ea to your computer and use it in GitHub Desktop.
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()
}
@isoiphone
Copy link
Author

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.

@isoiphone
Copy link
Author

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)

@iosdevzone
Copy link

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

@JessyCatterwaul
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment