Enum Initialized with a Non-Existent Rawvalue Does Not Fail and Return Nil

Enum initialized with a non-existent rawValue does not fail and return nil

I filed a bug with Apple and this is the reply I received:

"Engineering has determined that this issue behaves as intended based on the following information:

Because C enums may have values added in future releases, or even have "private case" values used by the framework that are not included in the headers, there's no way to check whether a value provided in Swift is actually valid or invalid. Therefore, init(rawValue:) is obliged to produce a value just as a C cast would. There are discussions in the Swift Open Source project on how to improve this situation in later versions of Swift, but the initializer for MKMapType still won't return nil."

Thanks to Apple Engineering for this explanation.

Enum defined with NS_ENUM's rawValue initializer doesn't fail

This is intended behavior. For any NS_ENUMs bridged to Swift the constructor will never return nil.

Try it with some other enums in the iOS SDK bridged to Swift with unexpected values. They will all return non-nil, even for a rawValue that is not defined by the enum:

UITableViewCellStyle(rawValue: 7) // "Optional(__C.UITableViewCellStyle)"
UITableViewCellAccessoryType(rawValue: 9999) // "Optional(__C.UITableViewCellAccessoryType)"

or, with unsafeBitCast:

unsafeBitCast(42, UITableViewCellEditingStyle.self) // "Optional(__C.UITableViewCellStyle)"

Martin R pointed out that this is documented in
the Xcode 6.3 release notes:

Imported NS_ENUM types with undocumented values, such as
UIViewAnimationCurve, can now be converted from their raw integer
values using the init(rawValue:) initializer without being reset to
nil. Code that used unsafeBitCast as a workaround for this issue can
be written to use the raw value initializer. For example:

let animationCurve =  
unsafeBitCast(userInfo[UIKeyboardAnimationCurveUserInfoKey].integerValue,
UIViewAnimationCurve.self)

can now be written instead as:

let animationCurve = UIViewAnimationCurve(rawValue:  
userInfo[UIKeyboardAnimationCurveUserInfoKey].integerValue)!

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

Check if a value is present in enum or not

You can simply try to initialize a new enumeration case from your string or check if all cases contains a rawValue equal to your string:

let string = "categories"

if let enumCase = HomeDataType(rawValue: string) {
print(enumCase)
}

if HomeDataType.allCases.contains(where: { $0.rawValue == string }) {
print(true)
}

Codable enum with default case in Swift 4

You can extend your Codable Type and assign a default value in case of failure:

enum Type: String {
case text,
image,
document,
profile,
sign,
inputDate = "input_date",
inputText = "input_text" ,
inputNumber = "input_number",
inputOption = "input_option",
unknown
}
extension Type: Codable {
public init(from decoder: Decoder) throws {
self = try Type(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? .unknown
}
}

edit/update:

Xcode 11.2 • Swift 5.1 or later

Create a protocol that defaults to last case of a CaseIterable & Decodable enumeration:

protocol CaseIterableDefaultsLast: Decodable & CaseIterable & RawRepresentable
where RawValue: Decodable, AllCases: BidirectionalCollection { }

extension CaseIterableDefaultsLast {
init(from decoder: Decoder) throws {
self = try Self(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? Self.allCases.last!
}
}

Playground testing:

enum Type: String, CaseIterableDefaultsLast {
case text, image, document, profile, sign, inputDate = "input_date", inputText = "input_text" , inputNumber = "input_number", inputOption = "input_option", unknown
}

let types = try! JSONDecoder().decode([Type].self , from: Data(#"["text","image","sound"]"#.utf8))  // [text, image, unknown]

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

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

How to get enum from raw value in Swift?

Too complicated, just assign the raw values directly to the cases

enum TestEnum: String {
case Name = "Name"
case Gender = "Gender"
case Birth = "Birth Day"
}

let name = TestEnum(rawValue: "Name")! //Name
let gender = TestEnum(rawValue: "Gender")! //Gender
let birth = TestEnum(rawValue: "Birth Day")! //Birth

If the case name matches the raw value you can even omit it

enum TestEnum: String {
case Name, Gender, Birth = "Birth Day"
}

In Swift 3+ all enum cases are lowercased

How can a return a blank value for my Enum

Why not use nil?

First declare your property as optional:

var type: SupportType?

And then you can pass nil:

NewType(name: data.name, supportType: nil, supportName: "")

Enum class in swift

Why don't you simply add a static property to your enum Type?

enum Type: Int {
case A = 0, B, C
static let all = [A, B, C]
}


Related Topics



Leave a reply



Submit