Using Swift Enums for Hard-Coded Strings

Swift has made quite an improvement to the enum type compared to what you could do in C. In this post, I will show you how you can use the new features of enum to manage hard-coded strings in your application.

Using Enums for Segue Identifiers

In Swift, you can change the raw value type of an enum to be a string. This will allow us to define an enum that has segue identifiers as its raw value. Let’s look at how we can transform our prepareForSegue function from the following example using hard-coded strings:


override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
    if let identifier = segue.identifier {
        if identifier == "Login" {
            ...
        } else if identifier == "Main" {
            ...
        } else if identifier == "Options" {
            ...
        }
    }
}

Using the string type as the raw value of the enum, we can define our segue identifiers in an enum like this:


enum SegueIdentifier: String {
    case Login = "Login"
    case Main = "Main"
    case Options = "Options"
}

Now we can change our prepareForSegue to take advantage of the enum we just created.


override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
    if let identifier = segue.identifier, segueIdentifier = SegueIdentifier(rawValue: identifier) {
        switch segueIdentifier {
        case .Login:
            ...
        case .Main:
            ...
        case .Options:
            ...
        }
    }
}

The hard-coded segue identifiers may appear in several of your view controllers, so it is much better to centralize them in an enum that can be shared throughout your application.

Parameterized Messages

Another place where hard-coded strings may be repeated in your application is in error messages. Good error messages will be parameterized, so that you have more context to understand what is wrong. Unfortunately, we cannot use the same trick as we did above (changing the raw value type of the enum to a string). The raw value in an enum has to be constant.


// This will not compile
enum Alert: String {
    case DuplicateUserNameMessage = "The username \(userName) already exists"
}

One option is to use Objective-C format specifiers and the string initializer. This solution is not bad, but we can do better and make the code more readable.


enum Alert: String {
    case DuplicateUserNameMessage = "The username %@ already exists"
}

let message = String(format: Alert.DuplicateUserNameMessage.rawValue, "Mike")
print(message)

Protocols, Methods, and Properties

For a better solution, let me introduce you to a couple new features. Swift enums can implement functions, computed properties, and protocols. The CustomStringConvertible protocol is a good one to implement for our parameterized messages, allowing us to pass the enum to a print function so it will know how to print it. All we have to do is implement the description property.


public protocol CustomStringConvertible {
    /// A textual representation of `self`.
    var description: String { get }
}

You implement a protocol on an enum just as you would implement a struct or class. The description property will print out the message using the enum’s associated value parameters.


enum Alert: CustomStringConvertible {
    case DuplicateUserNameMessage(userName: String)
    case NetworkFailureMessage(url: String)
    case OverheatingMessage(degrees: Float)
    var description: String {
        switch self {
        case DuplicateUserNameMessage(let userName): return "The username \(userName) already exists"
        case NetworkFailureMessage(let url): return "Unable to connect to \(url)"
        case OverheatingMessage(let degrees): return String(format: "Warning: The water is %.1f degrees", degrees)
        }
    }
}

let userName = "Mike"
let url = "www.google.com"
let degrees: Float = 103.4587254

print(Alert.DuplicateUserNameMessage(userName: userName))
print(Alert.NetworkFailureMessage(url: url))
print(Alert.OverheatingMessage(degrees: degrees))

This code will print out the following:

The username Mike already exists
Unable to connect to www.google.com
Warning: The water is 103.5 degrees

This solution is much more readable and Swift-like. Now all of your error messages are defined in one enum instead of being duplicated throughout your code.

Hope this is helpful for you.

Conversation
  • Phoney says:

    Error strings need to be localized, typically using NSLocalizedString(). I don’t see how that works with your enum errors.

    Converting string constants to enums for the segue.identifier, although it seems swifty, doesn’t seem like it gains much over private let string constants. I generally don’t like being forced to use rawValue. I guess I would prefer it was named something else, like value. Better yet would be if the enum could automatically be typecast to its value, er, rawValue.

    Do others also think that rawValue implies a code smell?

    • Severin says:

      Was about to comment with the same though. How does this fit in with Localization. Seems like using enums is giving you more work.
      In regards to your segue question, for the segue part I usually create an extension on UIStoryboard:

      extension UIStoryboard {

      class func menu() -> MenuViewController? {
      return mainStoryboard().instantiateViewController(withIdentifier: “Menu”) as? MenuViewController
      }

      }

      And can can used like so:

      UIStoryboard.Menu

  • @stuffmc says:

    Thanks for the post. You don’t need to define the string if they are the same. You can just do

    enum SegueIdentifier: String {
    case Login
    case Main
    case Options
    }

    and .Login.rawValue will give “Login”

  • Comments are closed.