Swift: Unable to switch on enum associated value - ios

I'm converting an old Objective C app into SWIFT for learning purposes, and I've stumbled upon a strange problem when trying to provide a switch statement on an enum.
The code looks like this:
switch entry.mood {
case let Mood.THDiaryMoodGood:
self.moodImageView.image = UIImage(named: "icn_happy")
case let Mood.THDiaryMoodAverage:
self.moodImageView.image = UIImage(named: "icn_average")
case let Mood.THDiaryMoodBad:
self.moodImageView.image = UIImage(named: "icn_bad")
default:
self.moodImageView.image = UIImage(named: "icn_happy")
}
Where Mood is:
enum Mood: Int16 {
case THDiaryMoodGood = 0
case THDiaryMoodAverage = 1
case THDiaryMoodBad = 2
}
And the representation in mood is stored in a CoreData entity named mood with the type Integer 16.
My casts directly matched each-other, however when I try and use the switch statement provided above I get the error: Enum case pattern cannot match values of the non-enum type Int16.
I'm rather confused as to why I'm receiving this error, from my understanding the process should be evaluated like this:
entry.mood = 1
switch(1) {
// Int16: 0 returned from enum - would evaluate false and fall through to case B
case Mood.THDiaryMoodGood:
self.mood.image = ...
// Int16: 1 returned from enum - would evaluate true and set the icon
case Mood.THDiaryMoodAverage:
self.mood.image = ...
// Other cases not evaluated as we returned true...
}
Is my thought process or logic flawed here? I'm very confused...any help would be greatly appreciated, thanks!

The problem is that you’re passing an Int16 value to the switch. You’re setting entry.mood, an Int16, to the raw value 1, but the switch wants your Mood type. So you have a type mismatch.
You can solve it by turning the value into a Mood:
switch Mood(rawValue: entry.mood)! {

Swift enums are not the same as Objective-C enums. In Objective-c, the enum is the backing value, referred to by a different name. In swift, the enum is it's own type, and the value is an associated value (you don't actually need a backing value if you don't want one, in your case it makes sense though).
Have a read of this, it explains things nicely. https://developer.apple.com/library/ios/documentation/swift/conceptual/Swift_Programming_Language/Enumerations.html

Related

How to I cast a type to a variable in an enum within a switch statement?

switch error {
case Project.Client.LoadError.operationFailed(let error2):
switch error2 {
case Project.Client.APIClientError.invalidStatusCode(let message, _):
guard message != nil, message == "error" else {
return refresh()
}
delegate?.error(invalidScript)
default:
refresh()
}
default:
refresh()
}
Here is my current code. This works but I feel there is a better way to do this.
How can I make error2 NOT go through another switch statement and just cast it as an invalidStatusCode that I can use?
Or, what terms can I search up to better understand what is happening here/understand what to look up myself for the answer?
As I might not be fully understanding switch statements as casting type.
Thank you in advance.
Pattern matching of enums with associated values is composable, so you can do this:
switch error {
case Project.Client.LoadError.operationFailed(Project.Client.APIClientError.invalidStatusCode("error" , _)):
delegate?.error(invalidScript)
default:
refresh()
}
You might notice that this is a bit hard to read because of how long these fully-qualified type-names are.
I suspect the type of error is the general Error, but in other cases, if the type of error is already-known to be Project.Client.LoadError, then you can just use .operationFailed (leaving the type to be inferred from context). And if .operationFailed's associated type is known to have type Project.Client.APIClientError, you can just use .invalidStatusCode.
If not, you can use some local type-aliases can help with that:
typealias LoadError = Project.Client.LoadError
typealias APIClientError = Project.Client.APIClientError
switch error {
case LoadError.operationFailed(APIClientError.invalidStatusCode("error", _)):
delegate?.error(invalidScript)
default:
refresh()
}
Since you are just drilling down to one case for both switches, you don't need any switches at all. You can use if case instead. To generalize, suppose we have this:
enum Outer {
case inner(Inner)
case other
}
enum Inner {
case message(String)
case other
}
Now suppose subject might be an Outer. Then to determine that this is an Outer's .inner case whose Inner value is .message and that message's value is "howdy", we can say:
if case Outer.inner(let inner) = subject,
case Inner.message(let message) = inner,
message == "howdy" {

Enum: Count and list all cases (from outside!)

I've got this enum (Xcode 10/Swift 5) in its own file ("MyEnum.swift"):
enum MyEnum: Int, CaseIterable {
case A = 0
case B = 1
case C = 2
case D = 3
case E = 4
//Each case needs its own number and description!
var description: String {
switch self {
case .A:
return "abc"
case .B:
return "bcd"
case .C:
return "cde"
case .D:
return "def"
case .E:
return "efg"
}
}
}
... and want to add the descriptions to a PickerView. I know how to set up the functions for the view but I'm now stuck on counting the enum cases and adding the descriptions.
According to the documentation and different questions, adding CaseIterable is supposed to make it possible to call
MyEnum.allCases.count
... but I can only access allCases from within the enum file. From outside (so from my ViewController class) I can only call MyEnum.AllCases, which hasn't got count and, to be honest, I'm not even sure what AllCases returns exactly (it's not a normal array you can use count on).
Adding this code (source) to the enum file at least makes it possible to count the cases:
static let count: Int = {
var max: Int = 0
while MyEnum(rawValue: max) != .none { max += 1 }
return max
}()
...but isn't there supposed to be an easier way to do this with Swift 4.2+?
How do I get a list of the case descriptions to fill my PickerView with (so "abc", "bcd",... - preferably without hardcoding it)?
I tried in 1 file:
enum TestEnum: CaseIterable {
case test1
case test2
}
in another file of another class I wrote:
let count = TestEnum.allCases.count
And it works, but I noticed that when I was typing "allCases" wasn't shown
I manually needed to write it
allCases is declared public, so you should be able to access it from another file:
/// A collection of all values of this type.
public static var allCases: Self.AllCases { get }
like this:
let descriptions = MyEnum.allCases.map { $0.description }
Try cleaning the project and rebuild.
Also, if something is not in Xcode's autocomplete list, it doesn't mean that you can't access that thing. Xcode's complete list has a ton of bugs according to my own experience.
For the sake of completeness and in case allCases doesn't work at all (even after a restart) add this to the enum file:
static let allValues = [MyEnum.A, MyEnum.B, MyEnum.C, MyEnum.D, MyEnum.E]
It's hardcoded but at least it gives you access to everything:
count: MyEnum.allValues.count
description (e.g. "abc"): MyEnum.allValues[0].description
rawValue (e.g. 0):MyEnum.allValues[0].rawValue
The same issue happened to me. What ended up working is restarting xcode.

Enum Types: get raw value from key

How to get the raw value from a enum passing the key value? Must work for any enum types working like an extension of enum types.
Any mirror reflection, mappable or RawRepresentable solutions are welcome.
I would like something like this:
enum Test : String {
case One = "1"
}
Test.rawValueFromKey(keyValue: "One") // Must return "1"
// I don't want the solution below, I must get the rawValue through the key name as String.
Test.One.rawValue
I do need to get the rawValue passing the name of the key as a String. Otherwise, I will need to make a switch or many if/else conditions. I have a big Enum and I don't want to check the string passed in a switch for example and pass Test.One.rawValue. I would like to get the rawValue directly through the key as String, just like in a dictionary.
I also don't want the code below, because I have a Enum of 40 items and I think it is ugly to make a switch of 40 items.
switch keyValue {
case "One":
return Test.One.rawValue
break
}
I want something like this:
func rawValueFromKey (keyValue: String) -> Test {
// code here to get the rawValue through the keyValue as String
// return the proper Test Enum
}
I tried some possible solutions using Mirror reflection and enum iterations to find the rawValue through the keyValue but didn't work.
Please give the solution in both Swift 2 and 3.
Thanks
As far as I know, you can't reflect on types so I think you will be able to achieve this only with CustomReflectable or maybe using localized strings for mapping the values/keys. Invariably you'll have to map somehow.
Why not something like this? Sure you are just comparing Strings so it's potentially unsafe (if your enum would conform to CustomStringConvertible, etc), but you can't really avoid that.
I think CaseIterable is available only from Swift 4 though...
protocol KeyRepresentable: CaseIterable {
}
extension KeyRepresentable {
static func fromKey(key: String) -> Self? {
return Self
.allCases
.first { "\($0)" == key }
}
}
Usage:
enum Option: Int, KeyRepresentable {
case a = 1, b = 2
}
print(Option.fromKey(key: "a")?.rawValue)

Update / change the rawValue of a enum in Swift

Taking the below enum for instance
enum Name : String {
case Me = "Prakash"
case You = "Raman"
}
Can I do the following
Change the raw value of one "case" to something else.
Name.Me = "Prak"
Add a new case to the ENUM
Name.Last = "Benjamin"
Thanks!
Short answer: No, you can't.
Enumeration types are evaluated at compile time.
It's not possible to change raw values nor to add cases at runtime.
The only dynamic behavior is using associated values.
Reference: Swift Language Guide: Enumerations
No you cannot. Instead You can redefine your enum to contain associated values instead of raw values.
enum Name {
case Me(String)
case You(String)
case Last(String)
}
var me = Name.Me("Prakash")
print(me)
me = .You("Raman")
print(me)
me = .Last("Singh")
print(me)

Apple Swift: Type Casting Generics

I'm writing some Swift code where I have an array containing a generic type:
let _data: Array<T> = T[]()
Later in my code I need to determine the type stored in the array. I tried using the type casting technique described in the documentation (although it was not used for generics).
switch self._data {
case let doubleData as Array<Double>:
// Do something with doubleData
case let floatData as Array<Float>:
// Do something with floatData
default:
return nil // If the data type is unknown return nil
}
The above switch statement results in the following error upon compilation:
While emitting IR SIL function #_TFC19Adder_Example___Mac6Matrix9transposeUS_7Element__fGS0_Q__FT_GSqGS0_Q___
for 'transpose' at /code.viperscience/Adder/src/Adder
Library/Matrix.swift:45:3 :0: error: unable to execute
command: Segmentation fault: 11 :0: error: swift frontend
command failed due to signal (use -v to see invocation) Command
/Applications/Xcode6-Beta2.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift
failed with exit code 254
Anybody know how I can cast my generic data to its actual type in order to take specific action?
In swift, as operator is something like dynamic_cast in C++, which can be used to down cast an object.
Say you have an object a of type A, and you can write let a as B only when type B is identical to type A, or B is a sub-class of A.
In your case, apparently Array<T> cannot always be down cast to Array<Double> or Array<Float>, so compiler reports errors.
A simple fix is to convert to AnyObject first, and then downcast to Array<Double> or Array<Float>:
let anyData: AnyObject = self._data;
switch anyData {
case let doubleData as? Array<Double>: // use as? operator, instead of as,
// to avoid runtime exception
// Do something with doubleData
case let floatData as? Array<Float>:
// Do something with floatData
default:
return nil // If the data type is unknown return nil
Suppose you have an array of buttons:
let views: [NSView] = [NSButton(), NSButton(), NSButton()]
You can use these casts:
let viewsAreButtons = views is [NSButton] // returns true
let buttonsForSure = views as! [NSButton] // crashes if you are wrong
let buttonsMaybe = views as? [NSButton] // optionally set
If you try to use as in a switch case like below, it will not work. The compiler (Swift 1.2 Xcode 6.3b1) says: "Downcast pattern of type [NSButton] cannot be used."
switch views {
case let buttons as [NSButton]:
println("Buttons")
default:
println("something else")
}
Call it a limitation. File a radar with your use case. The Swift team really seams to be listening for feedback. If you really want to get it to work, you can define your own pattern matching operator. In this case it would be something like this:
struct ButtonArray { }
let isButtonArray = ButtonArray()
func ~=(pattern: ButtonArray, value: [NSView]) -> Bool {
return value is [NSButton]
}
Then this works:
switch views {
case isButtonArray:
println("Buttons") // This gets printed.
default:
println("something else")
}
Try it in a Playground. Hope it helps!

Resources