Enum Not Working in Custom Initializer

Swift enum with custom initializer loses rawValue initializer

This bug is solved in Xcode 7 and Swift 2

Enum not working in custom initializer

This is a pretty weird bug

It occurs when you attempt to pass an enum into an argument of an initialiser, autocomplete will fail and instead of suggesting the enum cases after typing Enum., it will list the instance members of the class you’re calling the initialiser on. If you try and use the single dot syntax (.Case), autocomplete will also fail, but instead of displaying the list of instance members, it simply won't display anything.

I initially thought it may have had something to do with the naming of your enum and class (BuildingType & Building), but this is not the case.

This bug only appears to be present in initialisers with multiple arguments (one of which being an enum). I couldn’t reproduce this issue with single argument initialisers.

The reproducibility appears to depend on whether the initialiser is 'complete'. I'm considering an initialiser to be 'complete' if it has all argument names and values (except the enum) defined. For example:

// Incomplete (foo is one argument of many)
let baz = Baz(foo: Foo.

// Semi-Complete (before you assign the second parameter a value)
let baz = Baz(foo: Foo., string: <String Placeholder>)

// Complete
let baz = Baz(foo: Foo., string: "")

// Complete (note the lack of the last bracket)
let baz = Baz(param: 0, foo: Foo.

Here is my test setup (Xcode 7.3, Swift 2.2):

enum Foo {
case Bar
}

class Baz {
var iReallyShouldntBeDisplayedHere = 0
init(foo:Foo, string:String) {}
init(foo: Foo) {}
}

And here is a list of cases where I've found the bug does & doesn't occur:

// Enum is the only argument

// CORRECT: accepting the initialiser's autocomplete (so it's 'complete'), then typing "Foo." brings up autocomplete options for enum cases
let baz = Baz(foo: Foo.)

// CORRECT: typing the initialiser yourself (so it's 'incomplete'), then typing "Foo." in the first parameter brings up autocomplete options for enum cases
let baz2 = Baz(foo: Foo.

// Enum is one argument of many

// INCORRECT: accepting the initialiser's autocomplete (so it's 'semi-complete'), then typing "Foo." in the first parameter brings up Baz's instance members ("iReallyShouldntBeDisplayedHere")
let baz3 = Baz(foo: Foo., string: <String Placeholder>)

// CORRECT: typing the initialiser yourself (so it's 'incomplete'), and typing "Foo." in the first parameter brings up enum cases
let baz4 = Baz(foo: Foo.

// Single dot syntax (where enum is one argument of many)

// CORRECT: typing the initialiser yourself (so it's 'incomplete'), and typing "." in the first parameter brings up enum cases
let baz5 = Baz(foo:.

// CORRECT: accepting the initialiser's autocomplete (so it's 'semi-complete'), then typing "." in the first parameter brings up enum cases
let baz6 = Baz(foo:., string: <String Placeholder>)

// INCORRECT: modifying the foo: argument once the initialiser is 'complete' by typing "." in the first parameter doesn't generate the autocomplete list
let baz7 = Baz(foo:., string: "")

I also tried this where foo: is the last argument, but the initialiser always has to be complete in that case, so it always fails. I did try with initialisers that take 3 arguments, but it appears to have the same behaviour as an initialiser with 2 arguments.

If anyone knows of any more cases where this bug can be reproduced, I'd love to know!

How to simplify Swift Enum custom init

You can get rid of the switch statements by defining two dictionaries for a bidirectional mapping between the Int values and the enum cases.

enum Roman: String {
case I, V, X, L, C, D, M

private static let intValues:[Roman:Int] = [.I:1,.V:5,.X:10,.L:50,.C:100,.D:500,.M:1000]
private static let mappingDict:[Int:Roman] = Dictionary(uniqueKeysWithValues: Roman.intValues.map({ ($1, $0) }))

var intValue:Int {
return Roman.intValues[self]!
}

init?(intValue:Int){
guard let roman = Roman.mappingDict[intValue] else { return nil }
self = roman
}
}

Can you initialize an Enum value from the name of its case (*not* its RawValue?)

In Swift 4.2, this is quite easy to do now using CaseIterable.

enum MyOtherEnum: CaseIterable {
case xxx
case yyy
case zzz

init?(caseName: String) {
for value in MyOtherEnum.allCases where "\(value)" == caseName {
self = value
return
}

return nil
}
}

enum MyTestEnum: Int, CaseIterable{
case one = 1
case eight = 8
case unknown = -1

init?(caseName: String) {
for value in MyTestEnum.allCases where "\(value)" == caseName {
self = value
return
}

return nil
}
}

What I am doing here is creating a failable initializer which iterates through all potential cases, testing to see if "\(value)" (which returns the name for that potential case) matches the caseName argument passed in to the initializer.

When a match is found, self is set and the loop ends. Otherwise, nil is returned for the call.

Below, are two working and two failing examples:

let myOtherEnum = MyOtherEnum(caseName:"xxx")
print(myOtherEnum) // MyOtherEnum.xxx

let myTestEnum = MyTestEnum(caseName:"eight")
print(myTestEnum?.rawValue) // 8

let myOtherEnumFail = MyOtherEnum(caseName:"aaa")
print(myOtherEnumFail) // nil

let myTestEnumFail = MyTestEnum(caseName:"ten")
print(myTestEnumFail) // nil

Overriding Enum init?(rawValue: String) to not be optional

The default initializer is failable. It means that if the received parameter does not match a valid enum case it does return nil.

Now you want to do 2 incompatibles things:

  1. You want to redefine the default initializer making it not failable. In fact you want a default enum value created when the received param is not valid.
  2. Inside your redefined initializer you want to call a failable initializer (which no longer exists) using the same name of the new one.

This is not possible, I the 3 possible solutions as follows:

  1. Creating a different init

You define a new not failable initializer with a default value, a different parameter name and inside it you call the default failable initializer.

enum Language: String {

case english = "English", italian = "Italian", french = "French"

init(fromRawValue: String) {
self = Language(rawValue: fromRawValue) ?? .english
}
}

  1. Redefining the default init

You redefine the default initializer, you make it not failable and you write the full logic inside it.

enum Language: String {

case english = "English", italian = "Italian", french = "French"

init(rawValue: String) {
switch rawValue {
case "Italian": self = .italian
case "French": self = .french
default: self = .english
}
}
}

  1. Creating a static func
enum Language: String {

case english = "English", italian = "Italian", french = "French"

static func build(rawValue: String) -> Language {
return Language(rawValue: rawValue) ?? .english
}
}

Now you can build a Language value writing:

let italian = Language.build(rawValue: "Italian") // Italian
let defaultValue = Language.build(rawValue: "Wrong input") // English

Swift enum with a custom constructor

You don't need to return anything. You just call initializer:

enum MyEnum: String {
case hello = "hello"
case world = "world"

init?(caseInsensitive string: String) {
self.init(rawValue: string.lowercased())
}
}

print(MyEnum(caseInsensitive: "HELLO") as Any) // => Optional(Untitled.MyEnum.hello)
print(MyEnum(caseInsensitive: "Goodbye") as Any) // => nil

Initialize enum from associated value

Yes you can

enum class DirectionSwiped(val raw: Int){
LEFT(4),
RIGHT(8);
}

val left = DirectionSwiped.LEFT
val right = DirectionSwiped.RIGHT

val leftRaw = DirectionSwiped.LEFT.raw
val rightRaw = DirectionSwiped.LEFT.raw

val fromRaw = DirectionSwiped.values().firstOrNull { it.raw == 5 }

This would be the correct way to access the instances of the enum class

What you are trying to do is create a new instance outside the definition site, which is not possible for enum or sealed classes, that's why the error says the constructor is private



Related Topics



Leave a reply



Submit