How to Encode Enum Using Nscoder in Swift

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

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?

How to use NSCoding protocol with an enum?

Generally speaking the representation of enums can vary. When working with Objective-C, you should use the NS_ENUM macro to be sure of which type is used to represent the enumeration. There's more background in this article.

How do I encode [Character : Int] property using NSCoder in Swift 3?

Reason

That is because the Character-typed keys in cx will be boxed as _SwiftValue objects which will be sent encodeWithCoder: which leads to the unrecognized selector exception.

See the comment at the top of SwiftValue.h:

This implements the Objective-C class that is used to carry Swift
values that have been bridged to Objective-C objects without special
handling. The class is opaque to user code, but is NSObject- and
NSCopying- conforming and is understood by the Swift runtime for
dynamic casting back to the contained type.

Solution

If you can change the type of cx to [String : Int], everything will work out of the box (no pun intended).

Otherwise you will have to convert cx in Foo.encode(with:) to something that can be encoded (like [String : Int], for instance) and vice versa in the decoding initializer.

See How do I encode Character using NSCoder in swift? and How do I encode enum using NSCoder in swift? for some code.

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.



Related Topics



Leave a reply



Submit