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 case
s.
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:
- 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.
- 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:
- 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
}
}
- 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
}
}
}
- 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
How to Get Title from Wkinterfacebutton
How to Call a Method on a Uiview from Outside the Uiviewrepresentable in Swiftui
Disable Bounce Scrolling for Wkwebview in MACos
Create Complicated Nscompoundpredicate in Swift 3
How to Declare Exponent/Power Operator with New Precedencegroup in Swift 3
iOS Firebase: Firauthuidelegate.Authui Not Being Called
Sort Dictionary Keys by Value, Then by Key
Parse.Com Pfgeopoint.Geopointforcurrentlocationinbackground Not Doing Anything
Swift Package Manager Unable to Compile Ncurses Installed Through Homebrew
Recursion Over a Swift Sliceable
How Does Appdelegate.Swift Replace Appdelegate.H and Appdelegate.M in Xcode 6.3
Update Nstouchbar on the Fly to Add/Remove Items Programmatically
Error: Extraneous Argument Label 'No1:' in Call
Generics and Functional Programming in Swift
Swift: Google Maps Draw Waypoint Polyline
Type a Requires That Type B Be a Class Type Swift 4
"Message from Debugger: Unable to Attach" When Running Tests on Osx App