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

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.

Related

Passing all Enum values in Swift

I have to pass an array of an enum as a function parameter, most of the time I have to pass every single case of the enum. Is there a shorthand way to include all instead of having to pass [.NFL, .NBA, .NHL, .MLB, .NCAAM, .NCAAF] every time? Can I make and all property so that I can just pass .all that included all the cases?
enum LSLeague: String {
case NFL = "NFL"
case NBA = "NBA"
case NHL = "NHL"
case MLB = "MLB"
case NCAAM = "NCAAM"
case NCAAF = "NCAAF"
}
You can use .allCases on the enum type.
So, you might still have to do a bit of work depending on your use case, but you can iterate over each element in an enum if you use:
for league in LSLeague.allCases {
//code here
}
EDIT: #aheze had a great point that I just wanted to add into this answer. He says, "Make sure to also conform the enum to CaseIterable"
So, also make sure that you change the enum you have to:
enum LSLeague: String: CaseIterable {
case NFL = "NFL"
case NBA = "NBA"
case NHL = "NHL"
case MLB = "MLB"
case NCAAM = "NCAAM"
case NCAAF = "NCAAF"
}
if you're able to use this technique.
Swift 5
Use CaseIterable protocol to the enum:
enum CustomTypes: String, CaseIterable {
case location = "Location"
case organization = "Organization"
case dutyUse = "Duty Use"
}
Usage:
let values: [String] = CustomTypes.allCases.map { $0.rawValue }
// values = ["Location", "Organization", "Duty Use"]

Return a random case from an enum in Swift

I just wrote a function that generates a random integer in a range of 0...2. The goal is to return a random case from an enum based on the random integer.
I'd like to prefer to use the case statement but I don't like the default case which returns an arbitrary enum. Omitting the default case will not work since switch is not able to know how much cases will occur. Even though the default case would never happen I don't consider this to be a clean solution.
Which solution would you prefer respectively consider to be the cleanest solution - please elaborate. Is there a better/more elegant way to return a random case from an enum?
If-else solution:
func generateRandomSign() -> Sign {
let randomSelection = Int.random(in: 0...2)
if randomSelection == 0 {
return .rock
} else if randomSelection == 1 {
return .paper
}
return .scissor
}
Switch solution:
func generateRandomSign() -> Sign {
let randomSelection = Int.random(in: 0...2)
switch randomSelection {
case 0: return .rock
case 1: return .paper
case 2: return .scissor
default: return .rock
}
}
There's no need for messing around with Ints. You should rather declare your Sign enum to conform to CaseIterable and call randomElement() on its allCases property (which as its name suggests, contains all cases of the enum).
This way you don't sacrifice type safety and you're guaranteed to get back a case of Sign every time.
enum Sign: CaseIterable {
case rock
case paper
case scissor
static func random() -> Sign {
return allCases.randomElement()! // safe to force unwrap, since the enum has at least 1 property, so this can never be `nil`
}
}
You can call it like let randomSign = Sign.random()

When does the == operator depend on Equatable and when does it not?

I have an array of the following struct:
struct AtomCameraType
{
var APIIdentifier: Input.HDRCamera
var name: String
init(...
}
That APIIdentifier, of type Input.HDRCamera, is a generated Google Protocol Buffer enum in Swift:
enum HDRCamera: SwiftProtobuf.Enum {
typealias RawValue = Int
case standard // = 1
case sony // = 2
case canon // = 3
case panasonic // = 4
case arri // = 5
case jvc // = 6
case red // = 7
case rec2100 // = 8
case fujifilm // = 9
case nikon // = 10
case proResRaw // = 11
init() {
self = .standard
}
This may not conform to the Equatable protocol.
Attempts to match one of these enums against another with == works in a straight if statement, but fails when using == in firstIndex(where:.
So this works (cameraTypes is the aforementioned array of AtomCameraType):
for testType in AtomCamera.cameraTypes() // array of struct AtomCameraType
{
if testType.APIIdentifier == camType.APIIdentifier
{
debugPrint("Match found.")
}
}
but this doesn't:
if let camIndex = AtomCamera.cameraTypes().firstIndex(where: { $0.APIIdentifier == camType.APIIdentifier })
{
debugPrint("Match found.")
}
but finally, it works if I add rawValue:
if let camIndex = AtomCamera.cameraTypes().firstIndex(where: { $0.APIIdentifier.rawValue == camType.APIIdentifier.rawValue })
{
// This works.
...
}
So it seems to me that == doesn't always need conformance to Equatable, but sometimes it does. Or is there something else going on here?
UPDATE: So, with no changes to this code, it now works. I made extensive changes in the AtomCamera object, but not to these structs or cameraTypes(). And, as you can see from the working/non-working examples (which I had in the code at the same time), they're both using the same call to cameraTypes() to fetch the array.
Returning to this issue, I looked for other firstIndex(where:) calls with similar structs and ==, and found that they worked. So I removed the ".rawValue" from this troublesome one experimentally... to find that the firstIndex now works. Baffling.
What's the protocol here? Delete this or close it, or just leave it?
The == operator "doesn't need" Equatable. In fact, no function, subscript, property or operator requirement of any protocol "needs" its protocol. Being present is a requirement for the protocol, but even without the protocol, nothing prevents an == operator from being defined, no different from any other arbitrary protocol.
I don't know what the definition of cameraTypes() is, but there is no difference between the == operator used in your if statement and that used in the firtIndex(where:) call. I don't know why it's exhibiting different behaviour for you.
Your rawValue example works because the rawValue's type is Int (or some integral type, IDK the specifics of SwiftProtobuf.Enum), which is Equatable (and thus, necessarily has a == operator defined).

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)

Swift generic array to specific type

I am writing a function that needs to return an array of items. In the function there will be some logic that will determine the type of the items I want to return from the function. I started something like this:
func testFunc<T>(option:Int) -> [T] {
var result:[T] = []
switch option {
case 1:
let sampleString:String = "hello"
result.append(sampleString)
case 2:
let sampleInt:Int = 23
result.append(sampleInt)
default:
return result
}
return result
}
This gives me the following errors:
"cannot invoke append with an argument list of type '(String)',
and
"cannot invoke append with an argument list of type '(Int)'
It makes sense, but, I am trying to figure out how to solve my problem. When I call the function I don't know what type will be in the array returned, but the function will know how to determine the type before it start appending items to the array.
How can I accomplish this in Swift?
Swift does not support variables with multiple types. Using a generic T makes the function generic so the return type can be chosen by the caller.
You should rethink your design.
If you really want to return multiple result types based on a parameter, there are a few workarounds:
1: Use an Any array.
var result:[Any] = []
2: Use an enum with associated values
enum TestFuncResult {
case string(String)
case int(Int)
}
func testFunc(option: Int) -> [TestFuncResult] {
var result:[TestFuncResult] = []
switch option {
case 1:
let sampleString:String = "hello"
result.append(.string(sampleString))
case 2:
let sampleInt:Int = 23
result.append(.int(sampleInt))
default:
return result
}
return result
}

Resources