Reputation: 440
In my project I have a couple of enums which I use everywhere, these enums are created based on external json, so input is always optional. If an enum can't be created from the input, I'll define a default value.
Example of how I do things now:
enum TextAlign:String {
case left, center, right
}
let rawData = [String:Any]()
func getTextAlign() -> TextAlign {
if let rawTextAlignString = rawData["textAlign"] as? String, let align = TextAlign(rawValue: rawTextAlignString) {
return align
}
return TextAlign.left
}
let textAlign = self.getTextAlign()
This works obviously, but I would like to make my constructor a bit more swifty, generic and applicable to more of these enums. My goal is to instantiate these enums like this:
let textAlign = TextAlign(rawOptionalValue: rawData["textAlign"] as? String) ?? TextAlign.left
So I basically want a failable initializer, which I can just write for the TextAlign enum, but there has to be a way to declare this in a more 'generic' way so that I can use the initializer on all of my enum:String instances. I'm struggeling a bit with the syntax and options of generics in swift.
Any idea?
I see a lot of answers which are all not wrong, but probably I wasn't clear enough in what I'm looking for.
I have more enums like this:
enum TextAlign:String {
case left, center, right
}
enum CornerRadius:String {
case none, medium, large
}
enum Spacing:String {
case tight, medium, loose
}
I would like to define just 1 function that can initialize all these enums. (not because I'm lazy, but because I want to understand how to use Generics for this)
Probably what I need is some static func in an extension that applies to all these 'String/RawRepresentable' enums. I don't want to write all these failable initializers for each enum. (I believe it should be possible but I can't figure out the syntax)
After playing a bit with Joakim's answer I came up with the following solution:
extension RawRepresentable {
static func create<T:Any>(_ value: Any?, defaultValue: Self) -> Self where Self.RawValue == T {
guard let rawValue = value as? T, let instance = Self.init(rawValue: rawValue) else {
return defaultValue
}
return instance
}
}
This allows me to instantiate enums of type String
and Int
(and more) with this function. Like this:
enum TextAlign:String {
case left, center, right
}
enum CornerRadius:Int {
case none, medium, large
}
let json:[String:Any] = [
"textAlign":"left",
"cornerRadius":0
]
let cornerRadius = CornerRadius.create(json["cornerRadius"], defaultValue: .medium)
let align = TextAlign.create(json["textAlign"], defaultValue: .center)
I like that I can just pas Any?
as an argument and that it takes care of the casting on its own by doing let rawValue = value as? T
.
Okay, the complexcity of it all still bothered me a bit, so I tried the init route, which imo, looks way cleaner. The whole thing now looks like this:
extension RawRepresentable {
init(from value: Any?, or defaultValue: Self) {
self = Self.init(from: value) ?? defaultValue
}
init?(from value: Any?) {
guard let rawValue = value as? Self.RawValue, let instance = Self.init(rawValue: rawValue) else {
return nil
}
self = instance
}
}
I created a failable and a non-failable init with a default value for convenience purposes.
Now I can just instantiate any enum like this:
let cornerRadius = CornerRadius(json["cornerRadius"], or: .medium)
// or an optional one
let align = TextAlign(json["textAlign"])
Now I'm done with the updates...
Upvotes: 2
Views: 802
Reputation: 7663
You can declare your enums like this
enum TextAlign: String {
case left, center, right
init(rawOptionalValue: String?) {
self = TextAlign(rawValue: rawOptionalValue ?? TextAlign.left.rawValue) ?? .left
}
}
And then instantiate it like this:
let textAlign = TextAlign(rawOptionalValue: rawData["textAlign"] as? String)
Here's one example with a default value as an optional parameter as well:
enum TextAlign: String {
case left, center, right
init(rawOptionalValue: String?, defaultValue: TextAlign = TextAlign.left) {
self = TextAlign(rawValue: rawOptionalValue ?? defaultValue.rawValue) ?? defaultValue
}
}
let textAlign1 = TextAlign(rawOptionalValue: "left") // .left
let textAlign2 = TextAlign(rawOptionalValue: "right") // .right
let textAlign3 = TextAlign(rawOptionalValue: "center") // .center
let textAlign4 = TextAlign(rawOptionalValue: "notAnAlignment") // .left
let textAlign5 = TextAlign(rawOptionalValue: nil, defaultValue: .center) // .center
Ok, now I understand. Well then based on your latest update you can also do this I guess:
extension RawRepresentable {
init(rawOptionalValue: Any?, defaultValue: Self) {
guard let value = rawOptionalValue as? Self.RawValue else {
self = defaultValue
return
}
self = Self.init(rawValue: value) ?? defaultValue
}
}
The only thing here compared to my previous try is that you cannot provide a default defaultValue
. So you would use it like this:
let textAlign1 = TextAlign(rawOptionalValue: "left", defaultValue: .left) // .left
let textAlign2 = TextAlign(rawOptionalValue: "right", defaultValue: .left) // .right
let textAlign3 = TextAlign(rawOptionalValue: "center", defaultValue: .left) // .center
let textAlign4 = TextAlign(rawOptionalValue: "notAnAlignment", defaultValue: .left) // .left
let textAlign5 = TextAlign(rawOptionalValue: nil, defaultValue: .center) // .center
Upvotes: 2
Reputation: 63167
I don't think you need a new function for this. Just use Optional.map(_:)
:
let alignment = rawData["textAlign"].map(TextAlign.init(rawValue:)) ?? .left
Upvotes: 1
Reputation: 51892
Here is a function you can use to create enum items from a string
func createEnumItem<T: RawRepresentable>(_ value: String) -> T? where T.RawValue == String {
return T.init(rawValue: value)
}
and then use it like
let textAlign: TextAlign = createEnumItem("right")!
let radius: CornerRadius = createEnumItem("medium")!
Note that you always include the enum type in the variable declaration.
Of course since the return value is optional you need to handle that in a better way than my example here.
Update
In case you always know what your default is here is a modified version
func createEnumItem<T: RawRepresentable>(_ value: String, withDefault defaultItem: T) -> T where T.RawValue == String {
guard let item = T.init(rawValue: value) else {
return defaultItem
}
return item
}
Upvotes: 3
Reputation: 1457
You can define a static func like so and use it everywhere you need in your code
enum TextAlign: String {
case left, center, right
static func getTextAlign(rawData: [String:Any]) -> TextAlign {
if let rawTextAlignString = rawData["textAlign"] as? String, let align = TextAlign(rawValue: rawTextAlignString) {
return align
}
return TextAlign.left
}
}
// Test
var myRawData = [String:Any]()
let textAlign1 = TextAlign.getTextAlign(rawData: myRawData) // left
myRawData["textAlign"] = "center"
let textAlign2 = TextAlign.getTextAlign(rawData: myRawData) // center
Upvotes: 0
Reputation: 3494
You can try this:
let textAlign = TextAlign(rawValue: rawData["textAlign"] as? String ?? TextAlign.left.rawValue)
Upvotes: 0