Background
I am trying to encode a String-style enum using the NSCoding protocol, but I am running into errors converting to and back from String.
I get the following errors while decoding and encoding:
String is not convertible to Stage
Extra argument ForKey: in call
Code
enum Stage : String { case DisplayAll = "Display All" case HideQuarter = "Hide Quarter" case HideHalf = "Hide Half" case HideTwoThirds = "Hide Two Thirds" case HideAll = "Hide All" } class AppState : NSCoding, NSObject { var idx = 0 var stage = Stage.DisplayAll override init() {} required init(coder aDecoder: NSCoder) { self.idx = aDecoder.decodeIntegerForKey( "idx" ) self.stage = aDecoder.decodeObjectForKey( "stage" ) as String // ERROR } func encodeWithCoder(aCoder: NSCoder) { aCoder.encodeInteger( self.idx, forKey:"idx" ) aCoder.encodeObject( self.stage as String, forKey:"stage" ) // ERROR } // ... }
In Swift, an enum (short for enumeration) is a user-defined data type that has a fixed set of related values. We use the enum keyword to create an enum. For example, Note: Enum values are also called enum cases. And, we use the case keyword to declare values inside the enum. Since enum is a data-type, we can create variables of enum type.
You use the case keyword to introduce new enumeration cases. Swift enumeration cases don’t have an integer value set by default, unlike languages like C and Objective-C. In the CompassPoint example above, north, south, east and west don’t implicitly equal 0, 1, 2 and 3.
This additional information is called an associated value, and it varies each time you use that case as a value in your code. You can define Swift enumerations to store associated values of any given type, and the value types can be different for each case of the enumeration if needed.
Apple’s Barcode implementation is simple: This enum however contains more information than can be represented by a simple raw value type like a String or an Int and so it cannot conform to Codable without additional instructions as to how each case is to be coded.
You need to convert the enum to and from the raw value. In Swift 1.2 (Xcode 6.3), this would look like this:
class AppState : NSObject, NSCoding { var idx = 0 var stage = Stage.DisplayAll override init() {} required init(coder aDecoder: NSCoder) { self.idx = aDecoder.decodeIntegerForKey( "idx" ) self.stage = Stage(rawValue: (aDecoder.decodeObjectForKey( "stage" ) as! String)) ?? .DisplayAll } func encodeWithCoder(aCoder: NSCoder) { aCoder.encodeInteger( self.idx, forKey:"idx" ) aCoder.encodeObject( self.stage.rawValue, forKey:"stage" ) } // ... }
Swift 1.1 (Xcode 6.1), uses as
instead of as!
:
self.stage = Stage(rawValue: (aDecoder.decodeObjectForKey( "stage" ) as String)) ?? .DisplayAll
Swift 1.0 (Xcode 6.0) uses toRaw()
and fromRaw()
like this:
self.stage = Stage.fromRaw(aDecoder.decodeObjectForKey( "stage" ) as String) ?? .DisplayAll aCoder.encodeObject( self.stage.toRaw(), forKey:"stage" )
Here is a solution for Swift 4.2. As stated in the other answers, the problem is that you try to directly assign the stage
variable with a decoded string, and you try to cast the stage
variable to a string in the encodeWithCoder
method. You need to use raw values instead.
enum Stage: String { case DisplayAll = "Display All" case HideQuarter = "Hide Quarter" case HideHalf = "Hide Half" case HideTwoThirds = "Hide Two Thirds" case HideAll = "Hide All" } class AppState: NSCoding, NSObject { var idx = 0 var stage = Stage.DisplayAll override init() {} required init(coder aDecoder: NSCoder) { self.idx = aDecoder.decodeInteger(forKey: "idx") self.stage = Stage(rawValue: aDecoder.decodeObject(forKey: "stage") as String) } func encodeWithCoder(aCoder: NSCoder) { aCoder.encode(self.idx, forKey:"idx") aCoder.encode(self.stage.rawValue, forKey:"stage") } // ... }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With