Working easier with tags

:  ~ 1 min read

One usual approach is to create an enum, so your tags are more expressive by having a name:

enum ViewTag: Int {
  case none
  case titleLabel
  case loginButton
}

Then tagging and retrieving views by tag will be safer, and easier to remember:

let container = UIView()

let titleLabel = UILabel()
container.addSubview(titleLabel)
titleLabel.tag = ViewTag.titleLabel.rawValue

let loginButton = UIButton(type: .Custom)
container.addSubview(loginButton)
loginButton.tag = ViewTag.loginButton.rawValue

if
  let titleLabel = container.viewWithTag(ViewTag.titleLabel.rawValue) as? UILabel,
  let loginButton = container.viewWithTag(ViewTag.loginButton.rawValue) as? UIButton {
    // do stuff
}

This is already much better than triple checking that the titleLabel's tag really is 1 and not 2, but what if we can go a step further and improve this, by extending UIView?

extension UIView {

  // Swift 3 doesn't allow this anymore, so it would need another name.
  var tag: ViewTag {
    get { return ViewTag(rawValue: tag) ?? .none}
    set { tag = newValue.rawValue }
  }

  // But we can have a really short function:
  func tag(_ value: ViewTag) {
    tag = value.rawValue
  }

  // Or a slightly longer one, but more Swifty:
  func tag(with value: ViewTag) {
    tag = value.rawValue
  }

  func viewWithTag(_ tag: ViewTag) -> UIView? {
    return viewWithTag(tag.rawValue)
  }

  // Speaking of Swifty, we could also change the above in:
  func view(tagged tag: ViewTag) -> UIView? {
    return viewWithTag(tag.rawValue)
  }
}

So all of our code can be shorter, cleaner and prettier:

let titleLabel = UILabel()
view.addSubview(titleLabel)
titleLabel.tag = .titleLabel // Obsolete in Swift 3.
titleLabel.tag(.titleLabel)
// or
titleLabel.tag(with: .titleLabel)

let loginButton = UIButton(type: .Custom)
view.addSubview(loginButton)
loginButton.tag = .loginButton // Obsolete in Swift 3.
loginButton.tag(.loginLabel)
// or
loginButton.tag(with: .loginLabel)

[...] // somewhere else

if
  let titleLabel = view.viewWithTag(.titleLabel) as? UILabel,
  let loginButton = view.view(tagged: .loginButton) as? UIButton {
    // do stuff
}

The awesome part is that the compiler is smart enough to use the correct version of tag (not anymore, in Swift 3) and viewWithTag (still works), depending on the value we pass in:

titleLabel.tag  = .titleLabel // extension [Obsolete in Swift 3]
loginButton.tag = 2 // built-in

view.viewWithTag(1) // built-in
view.viewWithTag(.loginButton) // extension
view.view(tagged: .loginButton)