How do I encode enum using NSCoder in swift?
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" )
How can I encode (with NSCoding) an enum that has no rawValue?
I think adding a raw value to the enum here is the solution with the least code and is the most maintainable. So if you can modify the enum, add a raw value.
Now let's assume you can't modify the enum. You still can do this in a few ways.
The first one, which I think is quite ugly, is to add an extension
of the enum and add a static method like this:
static func direction(from rawValue: String) -> Direction {
switch rawValue {
case: "north": return .north
case: "south": return .south
default: fatalError()
}
}
To convert Direction
to a codeable value, use String(describing:)
to convert the enum to a string. To convert a string back to an enum, just use the method above.
The second one, slightly better, but still not as good as just adding a raw value.
You use a dictionary:
let enumValueDict: [String: Direction] = [
"north": .north, "south": .south
]
To convert Direction
to a codeable value, use String(describing:)
to convert the enum to a string. To convert a string back to an enum, just access the dictionary.
Encode enum with NSCoding
This is similar to @Bennett's answer, but it you can use it with NSNumber
as shown:
enum ServerType: UInt {
case PC
case PE
}
let someType: ServerType = .PE
NSNumber(value: someType.rawValue) // 1
Save Array of dictionaries with Enum type, NSCoding
medals
is not an AnyObject
, so when you encode it as? AnyObject
you get nil
.
Just because the enum has raw string values doesn't mean you can automatically bridge it to [String:String]
. You have to do that yourself. For example (using Airspeed Velocity's version of mapValues
):
convenience required init?(coder decoder: NSCoder) {
self.init()
if let medalStrings = decoder.decodeObjectForKey(Key.medals) as? [[String: String]] {
medals = medalStrings.map { $0.mapValues { Medal(rawValue: $0) ?? .Unearned } }
}
}
func encodeWithCoder(encoder: NSCoder) {
let medalStrings = medals.map { $0.mapValues { $0.rawValue } }
encoder.encodeObject(medalStrings, forKey: Key.medals)
}
How to archive enum with an associated value?
The main problem for your issue is that you cannot pass Swift enums to encode(_:forKey:)
.
This article shown by Paulw11 will help you solve this part. If the enum can easily have rawValue
, it's not too difficult.
But, as you see, Enum with raw type cannot have cases with arguments.
Simple enums can easily have rawValue
like this:
enum UnicornColor: Int {
case yellow, pink, white
}
But enums with associate values, cannot have rawValue
in this way. You may need to manage by yourself.
For example, with having inner enum's rawValue
as Int
:
enum Creature: Equatable {
enum UnicornColor: Int {
case yellow, pink, white
}
case unicorn(UnicornColor)
case crusty
case shark
case dragon
static func == (lhs: Creature, rhs: Creature) -> Bool {
//...
}
}
You can write an extension for Dream.Creature
as:
extension Dream.Creature: RawRepresentable {
var rawValue: Int {
switch self {
case .unicorn(let color):
return 0x0001_0000 + color.rawValue
case .crusty:
return 0x0002_0000
case .shark:
return 0x0003_0000
case .dragon:
return 0x0004_0000
}
}
init?(rawValue: Int) {
switch rawValue {
case 0x0001_0000...0x0001_FFFF:
if let color = UnicornColor(rawValue: rawValue & 0xFFFF) {
self = .unicorn(color)
} else {
return nil
}
case 0x0002_0000:
self = .crusty
case 0x0003_0000:
self = .shark
case 0x0004_0000:
self = .dragon
default:
return nil
}
}
}
(In fact, it is not an actual rawValue
and you'd better rename it for a more appropriate name.)
With a definition like shown above, you can utilize the code shown in the link above.
iOS (Swift): Encoding/Decoding Enums
You can take advantage of the rawValue
support that String
based enums (and not only) have:
func encode(with aCoder: NSCoder) {
aCoder.encode(direction.rawValue, forKey: "direction")
}
public convenience required init?(coder aDecoder: NSCoder) {
guard let direction = Direction(rawValue: aDecoder.decodeObject(forKey: "direction") else {
// direction was not encoded, we assume an encoding error
return nil
}
self.init(direction: direction)
}
Encode nested enumeration swift 3
I tried to encode and decode the status since it's the only property in the class but you might need to do the same for the other properties if any was found
First i started with giving the State
enum a value that i can encode
enum Status {
enum StatusValue {
case isLogin(LoginStatus)
case isRegister(RegisterStatus)
case isGetUserInfo(RegisterStatus)
case unknow
}
}
extension Status.StatusValue {
var value: Int {
switch self {
case .isLogin(let value):
return value.rawValue
case .isRegister(let value):
return value.rawValue
case .isGetUserInfo(let value):
return value.rawValue
case .unknow:
return -1
}
}
}
enum LoginStatus: Int {
case a = 0
case b
case c
case d
}
enum RegisterStatus: Int {
case a = 4
case b
case c
case d
}
enum GetUserInfoStatus: Int {
case a = 8
case b
case c
case d
}
Second I configured the User
class to implement NSCoding
public class User: NSObject, NSCoding {
override init() {
status = .unknow
}
init(_ status: Status.StatusValue) {
self.status = status
}
var status: Status.StatusValue
public func encode(with aCoder: NSCoder) {
print(self.status.value)
aCoder.encode(self.status.value, forKey: "status")
}
public required convenience init(coder aDecoder: NSCoder) {
let status = aDecoder.decodeObject(forKey: "status") as? Status.StatusValue ?? .unknow
self.init(status)
}
func save() {
let savedData = NSKeyedArchiver.archivedData(withRootObject: self)
let defaults = UserDefaults.standard
defaults.set(savedData, forKey: "user")
defaults.synchronize()
}
}
Finally I tested the result through
let user1: User = User()
user1.status = .isLogin(LoginStatus.b)
user1.save()
let user2: User
let defaults = UserDefaults.standard
if let saveduser = defaults.object(forKey: "user") as? Data {
user2 = NSKeyedUnarchiver.unarchiveObject(with: saveduser) as! User
print(user2)
}
I would also suggest to read a little about it in here: NSCoding, Workaround for Swift Enum with raw type + case arguments?
Related Topics
Protocol Can Only Be Used as a Generic Constraint
Swift: Generics and Type Constraints, Strange Behavior
Dictionary of String:Any Does Not Conform to Protocol 'Decodable'
How to Get Walking and Running Distance Using Healthkit in Swift
Why Are Objects in the Same Sknode Layer Not Interacting with Each Other
iOS 10 Notification Content Extension Not Loading
Skshapenode Is Not Responding to Physicsbody
Use of Undeclared Type Autoreleasingunsafepointer Xcode 6 Beta 6
How to Stop/Cancel Playsoundfilenamed in Swift
Cast Cgfloat to Int in Extension Binaryfloatingpoint
Override Multiple Overloaded Init() Methods in Swift
Swiftui Classes That Conforms Observableobject Should Be Singleton
Use Background Image on Uisearchcontroller iOS 11
Firebase and Reading Nested Data Using Swift
Xcframework Issue, a Library with the Identifier "Ios-Armv7_Arm64" Already Exists