Default value for Enum in Swift
Asked Answered
B

11

21

I have an enum :

public enum PersonType:String {

 case Cool                       = "cool"
 case Nice                       = "rude"
 case SoLazy                     = "so-lazy"

 public var description: String {
    switch self {
    case .Cool:
        return "Cool person"
    case .Nice:
        return "Nice person"
    case .SoLazy:
        return "its so lazy person"
    }
}


 public var typeImage: String {
    switch self {
    case .Cool:
        return "cool.png"
    case .Nice:
        return "img_nice.png"
    case .Solazy:
        return "lazy.png"
    }
   }  

}

The problem I don't know all the person type keys so I need to handle a default case of type person and to give it the description will be it's key like "so-lazy" and a default image.

let's say I get this result from the web service:

[
    {
        name: "john",
        key: "cool"
    },
    {
        name: "paul",
        key: "funny"
    }
]

I need to have a a default case to handle the key "funny"

here is how I init my enum while parsing and creating person object:

if let personType = PersonType(rawValue:personTypeKey ?? "") {
   self.personType = personType
}

I want an else or a better approach to handle the case of unknown keys in my enum, and give them the key as description and a default image.

Burgle answered 9/6, 2016 at 16:52 Comment(0)
A
23

Another approach that works in Swift 3 (maybe 2, don't know):

enum PersonType: String {
    case cool = "cool"
    case nice = "nice"
    case soLazy = "so-lazy"
    case other
}

let person = PersonType(rawValue: "funny") ?? .other

The person variable is of type PersonType.other in this case.

The downside to this is that you don't know the raw string value of the .other case.

Albuminous answered 24/1, 2017 at 1:43 Comment(0)
H
16

Drop the raw type, and use enum with associated value:

public enum PersonType {
    case Cool
    case Nice
    case SoLazy
    case Unknown(String)
    static func parse(s:String) -> PersonType {
        switch s {
            case "Cool" : return .Cool
            case "Nice" : return .Nice
            case "SoLazy" : return .SoLazy
            default: return Unknown(s)
        }
    }
}

The downside to dropping the raw type is that you must provide some logic for parsing the known enum values. The upside, however, is that you can fit anything else into a single Unknown case, while keeping the actual "unknown" value available for later use.

Heliochrome answered 9/6, 2016 at 17:2 Comment(10)
it seems that it can be a good approach, but how to create new enums while parsing and having different keys, known and unknownBurgle
@Burgle Call PersonType.parse(key), it will produce the proper enum value. Note the last edit.Heliochrome
I will give all cases of enum an associated value (String, UIImage) is there any way to declare that globally and give them tags, for example (typeName:String, imageType:UIImage), to make code prettier ?Burgle
and how to access an associated value after parsin without a switch. let's say I have let myPersonType = PersonType.parse(key) how to get the associated values from myPersonType variable?Burgle
@Burgle You can use case without a switch. See this answer for an example: if case let .Unknown(key) = parsedValue {...}Heliochrome
but I will store the PersonType in an enum in the person object and want to get the associated value no matter case it is like person.personType.myFirstAcossiatedValue I don't want each time to make a long switch to just get the associated value because I have a lot of casesBurgle
@Burgle Other enum values don't have the associated value, only .Unknown does. You can store enum in a personType and then use the test above to retrieve the associated value as an optional String? or return nil for enums that have no associated value. You can even make a property that gives you the associated value or nil, but using the enum in a switch is still a better approach.Heliochrome
I choose to give all cases an associated value so what to so in this case to get the associated values ?Burgle
@Burgle An odd thing about associated values is that when you give them to all enum cases, and the type of the values is the same, the associated value becomes a lot less useful. In situations like that it's better to go back to an enum without an associated value, and wrap it in a class that holds an enum and a string (which replaces the associated value). You can still use your enum in a switch if you want, as described here in the "Directory Traversion" section.Heliochrome
I end up using a classic approach class :( your approach still elegant, but I think in my case enum is not the best choiceBurgle
B
15

This goes pretty close but I would like to be able to store the value that can be associated with it, kind of like you can with C.

enum Errors: Int {
    case transactionNotFound = 500
    case timeout = -1001
    case invalidState = 409
    case notFound = 404
    case unknown

    init(value: Int) {
        if let error = Errors(rawValue: value) {
            self = error
        } else {
            self = .unknown
        }
    }
}

Errors(value: 40) // .unknown
Errors(value: 409) // .invalidState
Errors(value: 500) // .transactionNotFound

Had to create a custom initializer, otherwise it is recursive. And it is still possible to create using the rawValue initializer by accident.

This however feels more Swifty, I removed the : Int type specifier which allows you to use associated values, now the exceptional case that we don't do anything special is handled in the other:

enum Errors2 {
    case transactionNotFound
    case timeout
    case invalidState
    case notFound
    case other(Int)

    init(rawValue: Int) {
        switch rawValue {
        case 500:
            self = .transactionNotFound
        case -1001:
            self = .timeout
        case 409:
            self = .invalidState
        case 404:
            self = .notFound
        default:
            self = .other(rawValue)
        }
    }
}

Errors2(rawValue: 40) // .other(40)
Errors2(rawValue: 409) // .invalidState
Errors2(rawValue: 500) // .transactionNotFound
Errors2(rawValue: -1001) // .timeout

With this I could get the actual value for an "other" error, and I can use the rawValue so it acts a lot like an Int based enum. There is the single case statement to map the names but from then on you can use the names and never need to refer to the numbers.

Boatyard answered 8/11, 2017 at 1:25 Comment(1)
self = Errors(rawValue: value) ?? .unknownTransposition
M
10

In Swift 5.1 it's now possible to set default values. Your code would look like this:

enum PersonType {
  case cool(String = "cool")
  case nice(String = "rude")
  case soLazy(String = "so-lazy")
}
Mange answered 9/7, 2019 at 22:22 Comment(0)
E
8

like so:

init() {
    self = .Cool
}
Eden answered 8/5, 2017 at 7:48 Comment(1)
This is cleanest way. self is mutable for value types.Dinkins
S
4

This question is pretty old now and a lot has moved on in the Swift world. With Swift 5 I would recommend the approach below which involves creating a new initializer for the enum:

public enum PersonType:String, ExpressibleByNilLiteral {

    case Cool = "cool"
    case Nice = "rude"
    case SoLazy = "so-lazy"

    public init(nilLiteral:()) {
        self = .SoLazy
    }

    public init!(_ optionalValue:RawValue?) {
        guard let rawValue = optionalValue,
              let validValue = PersonType(rawValue:rawValue) else {
            self = .SoLazy
            return
        }
        self = validValue
    }

    public var description: String {
        switch self {
        case .Cool return "Cool Person"
        //... etc
        }
    }

    public var typeImage: String {
        switch self {
        case .Cool return "cool.png"
        //... etc
        }
    }
}

Use it like this:

self.personType = PersonType(personTypeKey)

Or like this:

self.personType = nil

In either case, even if the value isn't valid for the PersonType enum or it's just nil you will get a valid enum that's set to the default value of .SoLazy

This is similar to several other approaches in this thread but instead of listing out all the possible valid values (which can be unwieldy if there are a lot of them) it uses a multiple guard let = statement to guarantee it works.

Stinnett answered 13/3, 2020 at 19:51 Comment(0)
E
2

Try this approach.

public enum PersonType:String {

    case Cool                       = "cool"
    case Nice                       = "rude"
    case SoLazy                     = "so-lazy"

    static let allKeys = [Cool.rawValue, Nice.rawValue, SoLazy.rawValue]
}

extension PersonType
{
    func description(personTypeKey : String) -> String {

        if PersonType.allKeys.contains(personTypeKey)
        {
            switch self {
            case .Cool:
                return "Cool person"
            case .Nice:
                return "Nice person"
            case .SoLazy:
                return "its so lazy person"
            }
        }
        else
        {
            return "YourTextHere"
        }
    }

    func typeImage(personTypeKey : String) -> String {

        if PersonType.allKeys.contains(personTypeKey)
        {
            switch self {
            case .Cool:
                return "cool.png"
            case .Nice:
                return "img_nice.png"
            case .SoLazy:
                return "lazy.png"
            }
        }
        else
        {
            return "YourImageHere"
        }
    }
}
Enceinte answered 9/6, 2016 at 17:1 Comment(2)
With your approach How to create an enum with Unknown keyBurgle
@Burgle Updated answer, Please check.Enceinte
R
1

I wonder if dictionary is not a better fit than enum here:

let dict = [
    "Cool": "cool",
    "Nice": "rude",
    "SoLazy": "so-lazy"
]

let personType = "unknown"
let personDescription = dict[personType] ?? "Unknown"

Less typing, faster processing, more natural handling of the default case, easier to expand.

Ratite answered 9/6, 2016 at 18:13 Comment(2)
what about each type has an image, maybe later another var, so dictionary is not suitable for my caseBurgle
In that case, you would not use [String: String] dictionary, but [String: (String, String)], [String: [String]] or [String: Custom Struct]Ratite
L
1

For you case:

Default Value of Enum: I just add an default computed property, Or include an customize init.

public enum PersonType:String {

    case Cool                       = "cool"
    case Nice                       = "rude"
    case SoLazy                     = "so-lazy"

    /// add a `default` computer property
    public static var `default`: PersonType {
        return .SoLazy
    }

    /// add an customize init function 
    public init(person: String? = nil) {
        if let person = person {
            switch person {
            case "cool": self = .Cool
            case "rude": self = .Nice
            case "so-lazy": self = .SoLazy
            default: self = .SoLazy
            }
        } else {
            self = .SoLazy
        }
    }

    public var description: String {
        switch self {
        case .Cool:
            return "Cool person"
        case .Nice:
            return "Nice person"
        case .SoLazy:
            return "its so lazy person"
        }
    }

    public var typeImage: String {
        switch self {
        case .Cool:
            return "cool.png"
        case .Nice:
            return "img_nice.png"
        case .SoLazy:
            return "lazy.png"
        }
    }

}

To use:

if let personType = PersonType(rawValue:personTypeKey ?? "") {
    self.personType = personType
} else {
    self.personType = PersonType.default
}

Or

if let personType = PersonType(rawValue:personTypeKey ?? "") {
    self.personType = personType
} else {
    self.personType = PersonType()
}

Default Value of Enum With Associated Value:

public enum Gender {
    case man
    case woman
}

public enum PersonType {

    case cool(Gender)
    case nice(Gender)
    case soLazy(Gender)

    public static var `default`: PersonType {
        return PersonType.make.soLazy()
    }

    public enum Builder {
        public static func cool() -> PersonType {
            return PersonType.cool(.woman)
        }
        public static func nice() -> PersonType {
            return PersonType.nice(.woman)
        }
        public static func soLazy() -> PersonType {
            return PersonType.soLazy(.woman)
        }
    }

    public static var make: PersonType.Builder.Type {
        return PersonType.Builder.self
    }


    public var description: String {
        switch self {
        case .cool(let gender):
            switch gender {
            case .man: return "Cool boy"
            case .woman: return "Cool girl"
            }
        case .nice(let gender):
            switch gender {
            case .man: return "Nice boy"
            case .woman: return "Nice girl"
            }
        case .soLazy(let gender):
            switch gender {
            case .man: return "its so lazy boy"
            case .woman: return "its so lazy girl"
            }
        }
    }

    public var typeImage: String {
        switch self {
        case .cool(_):
            return "cool.png"
        case .nice(_):
            return "img_nice.png"
        case .soLazy(_):
            return "lazy.png"
        }
    }

}

To use:

let onePersonType = PersonType.default
let anotherPersonType = PersonType.make.soLazy()

The second case solution I was found on Ilya Puchka' blog. And also it's mentioned in swift's proposal.

Limicolous answered 16/5, 2017 at 3:36 Comment(0)
O
1

To answer your question:

public enum PersonType:String {

    case Cool                       = "cool"
    case Nice                       = "rude"
    case SoLazy                     = "so-lazy"
    static var `default`: PersonType { return .SoLazy }

    public init(rawValue: RawValue) {
        switch rawValue {
        case PersonType.Cool.rawValue: self = .Cool
        case PersonType.Nice.rawValue: self = .Nice
        case PersonType.SoLazy.rawValue: self = .SoLazy
        default: self = .default
        }
    }

    public var description: String {
        switch self {
        case .Cool:
            return "Cool person"
        case .Nice:
            return "Nice person"
        case .SoLazy:
            return "its so lazy person"
        }
    }

    public var typeImage: String {
        switch self {
        case .Cool:
            return "cool.png"
        case .Nice:
            return "img_nice.png"
        case .SoLazy:
            return "lazy.png"
        }
    }

}

Now since having no failable initializer with default value replace your:

if let personType = PersonType(rawValue:personTypeKey ?? "") {
   self.personType = personType
}

With:

personType = PersonType(rawValue: personTypeKey)
Overissue answered 26/7, 2019 at 21:0 Comment(0)
R
1

I recommend using such an approach

public enum Result {
    case passed(hint: String)
    case failed(message: String)

    static let passed: Self = .passed(hint: "")
}


let res: Result = Result.passed
Rickrack answered 6/5, 2020 at 14:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.