enum CardPosition: CGFloat {
case top = UIScreen.main.bounds.height //This line has the error "Raw value for enum case must be a literal"
case middle = 500
case bottom = 590
}
In the top case, I guess it doesn't return a CGFloat, but it also can't be typecast as a CGFloat for some reason using the "as" keyword and I don't know why. Any idea how to do this?
This is an enum that has a raw value that is a CGFloat.
What's wrong with the top case is that only a literal number is legal as a raw value. You cannot assign a variable like UIScreen.main.bounds.height. You must write out an actual number, there and then.
Taking a longer view, it looks like what you want here might not be an enum, or might not be an enum that takes a raw value. For example, you can have an enum that has an associated value:
enum CardPosition {
case top(CGFloat)
case middle(CGFloat)
case bottom(CGFloat)
}
Now you can attach the value at initialization time:
let myPosition = CardPosition.top(UIScreen.main.bounds.height)
let myOtherPosition = CardPosition.middle(500)
Note that you cannot mix and match; if we're going to use an associated value, then this enum can't have a fixed raw value.
The Problem
Can you define an enum to represent known values for a property in your model while still allowing for unknown values to be returned from the backend?
Short answer: Yes you can! The answer is below.
More Context
As part of our app, we have defined a set of feature flags that the app uses to enable/disable certain features depending on a set of criteria. These flags are sent back from the backend as an array of strings.
However, in our app, rather than dealing with the messiness of string constants, we want to define those values as an enum which we mark as Codable so the compiler handles the encoding/decoding to the actual enum cases for us automatically.
Here's a typical enum for such scenarios...
enum FeatureFlag : String, CaseIterable, Codable {
case allowsTrading
case allowsFundScreener
case allowsFundsTransfer
}
The wrinkle with this design is it doesn't handle values that may be defined, and returned from the backend in the future.
There are several ways to handle this scenario:
Abandon the enum and go to string constants. This is error prone and breaks containment/scoping since any string can participate in this logic.
Stick with an enum as-is and force an update to the app when the backend gets updated passing the buck to deployment.
Update the backend to handle versioning to only return values known to that version of the app complicating the logic on the backend to know about the various front-ends, which they shouldn't.
The most common, defensively program for unknowns by writing your own encoder/decoder methods for each class/struct that uses this enumeration, ignoring any flags not known by the current list of cases.
One through three are maintenance nightmares in their own right. Yes, four is better, but writing all those custom serializers/deserializers can be pretty time-consuming and error-prone, plus it defeats leveraging the benefits of the compiler being able to automatically do it for you!
But what if there's a number five? What if you can make the enumeration itself gracefully handle unknown values at runtime, while remaining lossless in the process, and without having to resort to optionals?
Well that's the exact solution I present below! Enjoy!
TL:DR - The Solution
For those who just want to see the solution, here it is in its entirety. This allows you to define an enum with known cases, but which can handle any raw value thrown at it at runtime, and do so in a lossless way for re-encoding purposes.
enum FeatureFlag : RawRepresentable, CaseIterable, Codable {
typealias RawValue = String
case allowsTrading
case allowsFundScreener
case allowsFundsTransfer
case unknown(RawValue)
static let allCases: AllCases = [
.allowsTrading,
.allowsFundScreener,
.allowsFundsTransfer
]
init(rawValue: RawValue) {
self = Self.allCases.first{ $0.rawValue == rawValue }
?? .unknown(rawValue)
}
var rawValue: RawValue {
switch self {
case .allowsTrading : return "ALLOWS_TRADING"
case .allowsFundScreener : return "ALLOWS_FUND_SCREENER"
case .allowsFundsTransfer : return "ALLOWS_FUNDS_TRANSFER"
case let .unknown(value) : return value
}
}
}
The Explanation
As mentioned above, our app has a certain set of known feature flags. At first, one may define them like so.
enum FeatureFlag : String, CaseIterable, Codable {
case allowsTrading
case allowsFundScreener
case allowsFundsTransfer
}
Simple enough. But again, now any value defined with the type FeatureFlag can only handle one of those specific known types.
Now say thanks to a new feature in the backend, a new flag allowsSavings is defined and pushed down to your app. Unless you have manually written the decoding logic (or resorted to optionals), the decoder will fail.
But what if the enum can handle unknown cases automatically for you, and do so in a completely transparent way?
It can! The trick is to define one additional case, unknown with an associated value of type RawValue. This new case handles all the unknown types handed to it when being decoded or even re-encoded.
Let's start by updating our enum with the new unknown case.
enum FeatureFlag : String, CaseIterable, Codable {
case allowsTrading
case allowsFundScreener
case allowsFundsTransfer
case unknown(RawValue)
}
This of course throws a ton of compiler errors thanks to that new case. Because of it, both RawRepresentable and CaseIterable can no longer be automatically synthesized by the compiler, so we have to manually implement them ourselves. Let's start with...
Manually Implementing the CaseIterable protocol
This is the easiest of the two steps. Since this 'version' of our app only knows about the first three cases, we can safely ignore all others. As such, to satisfy the protocol, we define a static allCases property that only specifies those cases we do care about.
Of Note: The property type AllCases here is an alias for [FeatureFlag] or more succinctly [Self], which we get for free when conforming to CaseIterable.
static let allCases: AllCases = [
.allowsTrading,
.allowsFundScreener,
.allowsFundsTransfer
]
With the above, this satisfies the CaseIterable protocol. Let's move on to...
Manually Implementing the RawRepresentable protocol
This is a little more complex/verbose, but this is where the 'magic' happens.
Specifying the RawValue type
Normally, to indicate your enum can be represented by a raw value, you specify a data type after an enum's name. In actuality, this is shorthand for telling the compiler you are conforming your enum to the RawRepresentable protocol and setting a RawValue typealias for that data type. However, again, because of our unknown type having an associated value, the compiler can't do that implicitly, so we must do so explicitly.
To do so, replace your raw type with RawRepresentable in the definition, then manually set the RawValue typealias inside, like so...
enum FeatureFlag : RawRepresentable, CaseIterable, Codable {
typealias RawValue = String
case allowsTrading
case allowsFundScreener
case allowsFundsTransfer
case unknown(RawValue)
}
implementing the rawValue: property (rawValues matching the case names)
Next up, we have to implement the rawValue property. For known cases where the raw value matches the case name, the implementation is simple as we can just return String(describing: self) and for the unknown cases, we return the associated value. Here's that implementation
var rawValue: RawValue {
switch self {
case let .unknown(value) : return value
default : return String(describing: self)
}
}
implementing the rawValue: property (rawValues unrelated to the case names)
But what if we wanted to express different values from the case names, or even a different data type entirely? In that situation, we have to manually expand out the switch statement and return the appropriate values like so....
var rawValue: RawValue {
switch self {
case .allowsTrading : return "ALLOWS_TRADING"
case .allowsFundScreener : return "ALLOWS_FUND_SCREENER"
case .allowsFundsTransfer : return "ALLOWS_FUNDS_TRANSFER"
case let .unknown(value) : return value
}
}
*Note: You must specify the raw values here and not up with the case definitions using the equals (=) as that's really syntactic sugar for the compiler to create what we're doing manually here, which again we have to since the compiler can't do it for us.
implementing init(rawValue:)... the 'Magic Sauce'
As mentioned, the entire purpose of this exercise is to allow your code to work with known types as usual, but to also gracefully handle unknown cases that are thrown at it. But how do we achieve that?
The trick is in the initializer, you first search for a known type within allCases and if a match is found, you use it. If however a match isn't found, rather than return nil like in the default implementation, you instead use the newly-defined unknown case, placing the unknown raw value inside.
This has the additional benefit of guaranteeing to always return an enum value from the initializer, so we can define it as a non-optional initializer as well (which is different from the implicitly created one from the compiler) making the call-site code easier to use as well.
Here's the implementation of the initializer:
init(rawValue: String) {
self = Self.allCases.first{ $0.rawValue == rawValue }
?? .unknown(rawValue)
}
In some situations, you may want to log when an unknown value is being passed for debugging purposes. That can be done with a simple guard statement (or an if if you prefer) like so...
init(rawValue: String) {
guard let knownCase = Self.allCases.first(where: { $0.rawValue == rawValue }) else {
print("Unrecognized \(FeatureFlag.self): \(rawValue)")
self = .unknown(rawValue)
return
}
self = knownCase
}
Interesting Behaviors on Equality and Hashability
One of the interesting things is that enums based on a raw value actually use that value for equality comparisons. Thanks to that piece of information, all three of these values are equal...
let a = FeatureFlag.allowsTrading // Explicitly setting a known case
let b = FeatureFlag(rawValue: "allowsTrading") // Using the initializer with a raw value from a known case
let c = FeatureFlag.unknown("allowsTrading") // Explicitly setting the 'unknown' case but with a raw value from a known case
print(a == b) // prints 'true'
print(a == c) // prints 'true'
print(b == c) // prints 'true'
Additionally, if your raw value conforms to Hashable, you can make the entire enum conform to Hashable by simply specifying its conformance to that protocol.
extension FeatureFlag : Hashable {}
With that conformance, now you can use it in sets or as keys in a dictionary as well, which again, thanks to the rules of equality above, provides for some interesting, but logically expected, behaviors.
Again, using 'a', 'b' and 'c' as defined above, you can use them like so...
var items = [FeatureFlag: Int]()
items[a] = 42 // Set using a known case
print(items[a] ?? 0) // prints 42 // Read using a known case
print(items[b] ?? 0) // prints 42 // Read using the case created from the initializer with a raw value from the known case
print(items[c] ?? 0) // prints 42 // Read using the 'unknown' case but with a raw value from the known case
Side-Benefit: Lossless Encoding/Decoding
One often-overlooked/underappreciated side-benefit of this approach is that serilization/deserialization is lossless and transparent, even for unknown values. In other words, when your app decodes data containing values you don't know about, the unknown case is still capturing and holding them.
This means if you were to then re-encode/re-serialize that data back out again, those unknown values would be re-written identically to how they would be if your app did know about them.
This is incredibly powerful!
This means for instance if an older version of your app reads in data from a server containing newer, unknown values, even if it has to re-encode that data to push it back out again, the re-encoded data looks exactly the same as if your app did know about those values without having to worry about versioning, etc. They're just silently and happily passed back.
Summary
With the above in place, you can now encode or decode any string into this enumeration type, but still have access to the known cases you care about, all without having to write any custom decoding logic in your model types. And when you do 'know' about the new type, simply add the new case as appropriate and you're good to go!
Enjoy!
Love this proposed solution! One small suggestion, add some logging in case the system encounters unknown types.
init?(rawValue: String) {
if let item = Self.allCases.first(where: { $0.rawValue == rawValue }) {
self = item
} else {
self = Self.other(rawValue)
if #available(iOS 12.0, *) {
os_log(.error, "Unknown FeatureFlag: %s", rawValue)
} else {
print("Error: Unknown FeatureFlag: \(rawValue)")
}
}
}
Let's say I have the following class:
class User: NSObject {
var name = "Fred"
var age = 24
var email = "fred#freddy.com"
var married = false
}
I want to be able to write a generic function that takes in a list of KeyPaths for a known class type, read the values and print to screen. The problem is, the I can't get the following code to compile as the type of the KeyPath's Value is not known, and will be different for each time. What do I have to do to make this work generically?
Consider the following:
struct KeyPathProperties<T> {
var name: String
var relatedKeyPaths: [KeyPath<T, Any>]
}
extension KeyPath where Root == User {
var properties: KeyPathProperties<Root> {
switch self {
case \Root.name:
return KeyPathProperties(name: "name", relatedKeyPaths: [\Root.age, \Root.email])
default:
fatalError("Unknown key path")
}
}
}
This line fails to compile:
return KeyPathProperties(name: "name", relatedKeyPaths: [\Root.age, \Root.email])
with this error:
Cannot convert value of type 'KeyPath<User, Int>' to expected element type 'KeyPath<User, Any>'
This is what I wish to be able to do, for instance:
let myUser = User()
var keyPathProps = KeyPathProperties(name: "name", relatedKeyPaths: [\User.age, \User.email])
for keyPath in props.relatedKeyPaths {
print("Value: \(myUser[keyPath: keyPath])")
}
The above won't compile of course. Essentially I want to store keyPaths in an array at runtime, so I can generically at some point in time get values out of the User. I need to know if I can re-write the above in some way where the compiler can safely and correctly determine the type of the keyPath's value at runtime.
This is a conceptual use case for a much more complex architectural issue I'm trying to solve with hopefully less code.
MORE INFORMATION:
At runtime I wish to keep track of the properties that get modified - these properties are held in a modifiedProps array in each object / instance. At some point at runtime, I wish to be able to enumerate over this array of KeyPaths and print their values like so:
for modifiedKeyPath in self.modifiedProps {
print ("\(self[keyPath: modifiedKeyPath])"
}
In short - I need to be able to capture the generic type of the KeyPath within KeyPathProperties. How do I achieve this?
SIDE NOTE: I can already easily achieve this by using Swift 3 style string based KeyPaths (by adding #objc to the class properties). I can store an array of keyPaths as strings and later do:
let someKeyPath = #keyPath(User.email)
...
myUser.value(forKeyPath: someKeyPath)
I just cannot do this with Swift 4 KeyPaths generically.
The error tells you what your misconception is:
Cannot convert value of type 'KeyPath<User, Int>'
to expected element type 'KeyPath<User, Any>'
You seem to think that you can use a KeyPath<User, Int> where a KeyPath<User, Any> is expected, ostensibly on the grounds that an Int is an Any. But that's not true. These are generic types, and generic types are not covariant — that is, there is no substitution principle for generics based on their parameterized types. The two types are effectively unrelated.
If you need an array of key paths regardless of their parameterized types, you would need an array of PartialKeyPath or AnyKeyPath. It seems that in your use case the root object is the same throughout, so presumably you want PartialKeyPath.
I'm trying to get a default value for the enum so I can use it as a param. This code isn't working, but I'd like to get something like:
print("Param: \(Params.RCLoss.description)")
and the output should be:
Param: RC_LOSS_MAN
Here is the code:
enum Params {
enum RCLoss: Int32, CustomStringConvertible {
case disable = 0
case enable = 1
var description: String {
return "RC_LOSS_MAN"
}
}
}
I want to be able to pass this:
set(parameterType: Params.RCLoss.description, parameterValue: Params.RCLoss.enable)
which should correspond to these values being set:
set(parameterType: "RC_LOSS_MAN", parameterValue: 0)
It seems you want just
enum rcLoss: Int32 {
case disable = 0
case enable = 1
static var description: String {
return "RC_LOSS_MAN"
}
}
rcLoss is a type, description has to be static for you to be able to call rcLoss.description. And that means you cannot use CustomStringConvertible. You would use CustomStringConvertible to convert enum values to a String.
From Swift Book - Enumerations:
You access the raw value of an enumeration case with its rawValue property.
set(parameterType: Params.rcLoss.description, parameterValue: Params.rcLoss.enable.rawValue)
If you can though I would use the enumeration as the type of the formal parameter so that someone can't pass an invalid value to that function. Also I'm assuming that there is a reason you have nested an enum inside of an otherwise empty enum...
How do I save the value of an enum case to UserDefaults? I have tried and had no luck. I checked multiple sites including this one, but with no luck, they all are in Swift 2 or in Objective-c which I can't translate at all.
Create the enum with a property list compliant raw value for example Int
enum ExampleEnum : Int {
case default1
case default2
case default3
}
Implicitly the first case is 0, the second is 1 and so on.
Now you can save the (raw) value in UserDefaults
UserDefaults.standard.set(currentDefaultType.rawValue, forKey:"Foo")
And read it back
currentDefaultType = ExampleEnum(rawValue: UserDefaults.standard.integer(forKey:"Foo"))!
-UPDATE-
figured It out for myself I had to add an integer extension to my enum case so the enum had a value to save
so I started with two global variables at the top of my file containing the switch method
var switchCurrentType = .default1
var currentDefaultType = UserDefaults().integer(forkey: "CurrentDefaultType")
then I declared the enum case in the file where I was switching the cases (like if you want to switch on the press of a button or in a didMoveToView method you put your case here)
enum ExampleEnum : Int {
case default1
case default2
}
then I use this when switching the case
switchCurrentType = .default1 //or whatever you are trying to switch to
and I use this to save it to UserDefaults
UserDefaults.standard.set(switchCurrentType.rawValue, forKey: "CurrentDefaultType")
here is reading the saved data for further usage
//in the didMoveToView method put this code in
switchCurrentType = ExampleEnum(rawValue: UserDefaults.standard.integer(forKey: "CurrentDefaultType"))! //make sure exclamation mark at the end is there or it won't read properly