1

I have an integer enum which I'd like to use for the viewWithTag(_:) number, but it gives me the error "Cannot convert value of type 'viewTags' to expected argument type 'Int'", even though both the enum and the tag number needed in viewWithTag(_:) is an Int.

This is pretty simple, and I can get it to work if I use the rawValue property, but that's more messy and cumbersome than I'd like.

enum viewTags: Int {
    case rotateMirroredBtn
    case iPhone6SP_7P_8P
    case iPhoneX_Xs
    case iPhoneXs_Max
    case iPhone_Xr
    case unknown
}

// error on if statement "Cannot convert value of type 'viewTags' to expected argument type 'Int'"
if let tmpButton = self.view.viewWithTag(viewTags.rotateMirroredBtn) as? UIButton { 
    tmpButton.removeFromSuperview()
}
jscs
  • 63,694
  • 13
  • 151
  • 195
Chewie The Chorkie
  • 4,896
  • 9
  • 46
  • 90

2 Answers2

2

You can easily add an extension on UIView to do the conversion for you. You just need to use a generic parameter to restrict the argument to something that you can get an Int from.

extension UIView
{
    /**
     Returns the view’s nearest descendant (including itself) whose `tag`
     matches the raw value of the given value, or `nil` if no subview
     has that tag.
     - parameter tag: A value that can be converted to an `Int`.
     */
    func firstView <Tag : RawRepresentable> (taggedBy tag: Tag) -> UIView?
        where Tag.RawValue == Int
    {
        let intValue = tag.rawValue
        return self.viewWithTag(intValue)
    }
}

The constraint T : RawRepresentable where T.RawValue == Int can be fulfilled by your Int-backed enum.

An non-generic form is easy too: func firstView(taggedBy viewTag: ViewTag) -> UIView?

Bonus, you can also add a method to apply the raw value of a "composed" value to the view's:

func applyTag <Tag : RawRepresentable> (_ tag: Tag)
    where Tag.RawValue == Int
{
    self.tag = tag.rawValue
}

(Unfortunately there's no way to write that as a property, e.g. var composedTag: Tag where Tag : RawRepresentable, Tag.RawValue == Int because a computed property can't create its own generic context like the method can.)

jscs
  • 63,694
  • 13
  • 151
  • 195
  • 1
    Bonus bonus, if you want to "do like ObjC did", that's basically just this: https://gist.github.com/woolsweater/8bfd52e7e725d82b53acb13f869d55c2 Unfortunately the compiler won't number them for you then. – jscs Jan 10 '19 at 19:38
1

I, like the original poster, do not prefer to use a case's rawValue in code, so I added computed type properties to my enums. I'm using Xcode v11.3.1 and Swift v5.1.3.

For example, many of the unit tests I've written were using "magic" values to create the IndexPath for table views, with code like this:

let activeIndexPath = IndexPath(row: 0, section: 0)
let finishedIndexPath = IndexPath(row: 0, section: 1)

I did not want to do this, even though it's an improvement over the "magic" values:

let activeIndexPath = IndexPath(row: 0, section: TableViewSection.active.rawValue)
let finishedIndexPath = IndexPath(row: 0, section: TableViewSection.finished.rawValue)

I'm mostly concerned about the section of the table view I'm testing, so I came up with this enum which uses computed type properties to get the Int rawValues:

enum TableViewSection: Int {
    case active
    case finished

    static var sectionActive: Int { return Self.active.rawValue }
    static var sectionFinished: Int { return Self.finished.rawValue }
}

Now I can create an IndexPath like this:

let indexPathActive = IndexPath(row: 0, section: TableViewSection.sectionActive)

The downside is that you need a computed property with a similar name for each case, but the end result is more descriptive at the call site (although I guess the code which uses rawValue is descriptive also), and now I don't have to remember which Int value to use for each specific section of the table view and I don't have to use "magic" values anymore, which we all know is a bad thing.

Hope that helps!