Swift tricks

Swea......ft Swift

If you went on an other planet for a while, know that Apple has introduced Swift, a new programming language for both iOS and OSX.

Recently, the Swift language has reached version 1.0. So it is now a good time to get into it.

Note that the code written here is in no way an example of full understanding of the language as Swift is a new language for most of us.

Get an interesting view

The context

I'm in the middle of a UICollectionViewController within the implementation of the override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?). A button in one of the Collection's Cells triggers a segue.

I wish to find the indexPath of the cell containing the button that triggered the segue.

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
  // Find out the indexPath of the cell containing the sender
  // Set the data related to the indexPath for the destinationViewController
}

Now, there is more than one way to do it...

At first I thought okay so the button (a UIButton) is placed inside the cell (a UICollectionViewCell) so I just need to find the button's superview and cast it to the cell type:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
  // Find out the indexPath of the cell containing the sender
  if let cell: UICollectionViewCell = sender?.superview? as? UICollectionViewCell {

  }
  // Set the data related to the indexPath for the destinationViewController
}

But as a matter of fact, the cell is two (parent) levels under the button so I could write:

if let cell: UICollectionViewCell = sender?.superview??.superview as? UICollectionViewCell {
  // ...
}

However, if I had put the button in a subview I could end up with an ugly code like this:

sender?.superview??.superview?.superview

So, I figured I could write an extension to UIView and add a little parentView() function that would cycle through each superview until It finds a view matching specific criteria.

First shot

extension UIView {
  func parentView(match: (view: UIView) -> Bool) -> UIView? {
    var currentView = self
    while currentView.superview != nil {
      if match(view: currentView) {
        return currentView
      }
      currentView = currentView.superview!
    }
    return nil
  }
}

Which I could use like this:

if let cell = sender?.parentView({ $0 is UICollectionViewCell }) as? UICollectionViewCell {
  let indexPath = collectionView.indexPathForCell(cell)
}

Not so bad. But, I'd prefer not to have to cast it to UICollectionViewCell.

Second shot

So if I don't want to cast the return value then I have to specify the type of the superview I'm looking for like this:

func parentOfType(type: aType) -> UIView?

But this would give me back a UIView? type... which I would need to cast again to UICollectionViewCell in our case.

Too bad.

How about using generics? Then I could write something like:

func parentViewOfType<T>(type: T) -> T? {
  // Return some T or nil
}

Which could be used like this:

if let cell = sender?.parentViewOfType(UICollectionViewCell) {
  // Get the indexPath of the cell
}

However, it is not really what we want since the type T would be a class type and the returned value would be an object of that type.

Last shot

Turns out that we need to pass a class Type which could be done like this:

func parentViewOfType<T>(type: T.Type) -> T? {
  // Return some T or nil
}

Now, since the sender type of prepareForSegue() is AnyObject? we cannot directly call this generic on the sender, we need to cast it to UIView optional.

if let cell = (sender? as UIView?)?.parentViewOfType(UICollectionViewCell) {
  let indexPath = collectionView.indexPathForCell(cell)
}

Or, you could unwrap and cast the sender:

if let sender = sender? as? UIView {

  if let cell = sender.parentViewOfType(UICollectionViewCell) {
    let indexPath = collectionView.indexPathForCell(cell)
  }

}

Which is much more readable I think.

Here is the full code of the extension:

extension UIView {

  func parentViewOfType<T>(type: T.Type) -> T? {
    var currentView = self
    while currentView.superview != nil {
      if currentView is T {
        return currentView as? T
      }
      currentView = currentView.superview!
    }
    return nil
  }

}

Take care of the world.