Get Swift enum type from string that matches type, not rawValue

Viewed 432

I'm using a third-party library that has an enum class defining some Media Device Types. I also have an API that can provide the Media Device Type to the Swift code as a string. I need to somehow get the Media Device Type from that string, so that I can pass it to another method.

Third-Party Enum

@objc public enum MediaDeviceType: Int, CustomStringConvertible {
    case audioBluetooth
    case audioWiredHeadset
    case audioBuiltInSpeaker
    case audioHandset
    case videoFrontCamera
    case videoBackCamera
    case other

    public var description: String {
        switch self {
        case .audioBluetooth:
            return "audioBluetooth"
        case .audioWiredHeadset:
            return "audioWiredHeadset"
        case .audioBuiltInSpeaker:
            return "audioBuiltInSpeaker"
        case .audioHandset:
            return "audioHandset"
        case .videoFrontCamera:
            return "videoFrontCamera"
        case .videoBackCamera:
            return "videoBackCamera"
        case .other:
            return "other"
        }
    }
}

Things I've Tried:

As you can see, the .rawValue is an Int which means that I can't access the value using the initialiser for the enum:

let stringType = "audioBluetooth"
let type = MediaDeviceType(rawValue: stringType)

This gives me the error:

Cannot convert value of type 'String' to expected argument type 'Int'

I thought that maybe I could switch the stringType and return the correct media device type, but it seems that it doesn't actually work as expected when passing to the next method along.

private func getMediaTypeStringToEnum(type: String) -> MediaDeviceType? {
    switch (type) {
    case "audioBluetooth":
      return .audioBluetooth
    case "audioWiredHeadset":
      return .audioWiredHeadset
    case "audioBuiltInSpeaker":
      return .audioBuiltInSpeaker
    case "audioHandset":
      return .audioHandset
    case "videoFrontCamera":
      return .videoFrontCamera
    case "videoBackCamera":
      return .videoBackCamera
    case "other":
      return .other
    default:
      return nil
    }
  }

The next method takes two parameters, a String, and an optional MediaDeviceType. If the second parameter doesn't match, the method switches the init method. Documentation.

My basic implementation:

let stringType = "audioBluetooth"
let label = "Media Device Label"
let hopefullyEnumType = getMediaTypeStringToEnum(stringType)
let mediaDevice = MediaDevice(label, hopefullyEnumType)

I'm very new to Swift, so maybe there's something obvious I'm missing here?

2 Answers

If you are absolutely sure that the enum cases will not be extended with new cases in the future, then one solution would be to iterate through all and see which ones match:

private func getMediaTypeStringToEnum(type: String) -> MediaDeviceType? {
    let allMediaTypes: [MediaDeviceType] = [.audioBluetooth, .audioWiredHeadset, .audioBuiltInSpeaker, .audioHandset, .videoFrontCamera, .videoBackCamera, .other]
    return allMediaTypes.first { $0.description == type }
}

If you're not sure, then you'll have to keep an eye on the enum declaration, and add the new cases to the allMediaTypes array when that happens.

Ofcourse, if the enum would've been CaseIterable, then your problem would be less easier to solve, and would not require maintaining the code:

private func getMediaTypeStringToEnum(type: String) -> MediaDeviceType? {
    MediaDeviceType.allCases.first { $0.description == type }
}

As a side note, you could also implement a failable initializer over the enum, this should make the solution easier to use, and more cohesive with the enum:

extension MediaDeviceType {
    init?(string: String) {
        // do your thing
    }
}

swiftly code is:

@objc public enum MediaDeviceType: Int, CustomStringConvertible {
case audioBluetooth
case audioWiredHeadset
case audioBuiltInSpeaker
case audioHandset
case videoFrontCamera
case videoBackCamera
case other

init(from string: String) {
    switch (string) {
        case "audioBluetooth":
            self = .audioBluetooth
        case "audioWiredHeadset":
            self = .audioWiredHeadset
        case "audioBuiltInSpeaker":
            self = .audioBuiltInSpeaker
        case "audioHandset":
            self = .audioHandset
        case "videoFrontCamera":
            self = .videoFrontCamera
        case "videoBackCamera":
            self = .videoBackCamera
        default:
            self = .other
        }
}

public var description: String {
    switch self {
    case .audioBluetooth:
        return "audioBluetooth"
    case .audioWiredHeadset:
        return "audioWiredHeadset"
    case .audioBuiltInSpeaker:
        return "audioBuiltInSpeaker"
    case .audioHandset:
        return "audioHandset"
    case .videoFrontCamera:
        return "videoFrontCamera"
    case .videoBackCamera:
        return "videoBackCamera"
    case .other:
        return "other"
    }
}
}
Related