Swift Enum and Nscoding

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



Leave a reply



Submit