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 enum
s 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 isNSObject
- 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
The "++" and "--" Operators Have Been Deprecated Xcode 7.3
Swift Dictionary Get Key For Value
How to Compare Two Dictionaries in Swift
How to Convert a View (Not Uiview) to an Image
How to Check If a Firebase Database Value Exists
Swift Generics Not Preserving Type
Swift 3.0: Convert Server Utc Time to Local Time and Vice-Versa
Difference Between Flatmap and Compactmap in Swift
Why Convenience Keyword Is Even Needed in Swift
How to Print Double Quotes Inside ""
Why Can't I Pass a Protocol.Type to a Generic T.Type Parameter
Swift 3 For Loop With Increment
Swiftui Iterating Through Dictionary With Foreach
Extend Array Types Using Where Clause in Swift
Changing Navigation Title Programmatically
Fix Warning "C-Style For Statement Is Deprecated" in Swift 3