Is there a clean way of making an enum with associated value conform to rawRepresentable? - swift

I have this in my code and it works, however if I have other enums (not necessarily color) with a long list it gets tiresome. Is there a better way of having an enum with an associated value that also conforms to RawRepresentable?
public enum IconColor {
case regular
case error
case warning
case success
case custom(String)
public var value: Color {
return loadColor(self.rawValue)
}
}
extension IconColor: RawRepresentable {
public var rawValue: String {
switch self {
case .regular: return "icon_regular"
case .error: return "icon_error"
case .warning: return "icon_warning"
case .success: return "icon_success"
case .custom(let value): return value
}
}
public init(rawValue: String) {
switch rawValue {
case "icon_regular": self = .regular
case "icon_error": self = .error
case "icon_warning": self = .warning
case "icon_success": self = .success
default: self = .custom(rawValue)
}
}
}

There's not a general solution.
public var rawValue: String {
switch self {
case .custom(let value): return value
default: return "icon_\(self)"
}
}
public init(rawValue: String) {
self =
[.regular, .error, .warning, .success]
.first { rawValue == $0.rawValue }
?? .custom(rawValue)
}

Related

Updating value of Enum

I have an enum that I use to populate a collection View and data changes based on API call and when this call is made, I want to change the subTitle value here is what I have currently
enum BalanceType: String, CaseIterable {
case ledger
case available
var backgroundImage: UIImage {
switch self {
case .ledger:
return R.image.walletBgNonWithdrawable()!
case .available:
return R.image.walletBgWithdrawable()!
}
}
var title: String {
switch self {
case .ledger:
return "Contribution Ledger Balance"
case .available:
return "My Available Balance"
}
}
var subTitle: String {
switch self {
case .ledger:
return Util.df2so(LedgerBalanceModel.currentWallet()?.totalLedgerBalance ?? 0)
case .available:
return Util.df2so((LedgerBalanceModel.currentWallet()?.totalLedgerBalance ?? 0) - (LedgerBalanceModel.currentWallet()?.totalMinBalance ?? 0))
}
}
}
my call that should update the value is
func showLedgerBal(_ data: LedgerBalanceModel) {
collectionView.reloadData()
}

Swift Codable how to use Any type?

When I try to access the value of "value" for example to use it in a label.text, I get an error
Cannot assign value of type 'MyValue?' to type 'String?'
When I print the value to the terminal, it says ...
unknown context at 0x109d06188).MyValue.string...
How can solve this problem?
struct Root: Codable {
let description,id: String
let group,groupDescription: String?
let name: String
let value: MyValue
enum CodingKeys: String, CodingKey {
case description = "Description"
case group = "Group"
case groupDescription = "GroupDescription"
case id = "Id"
case name = "Name"
case value = "Value"
}
}
enum MyValue: Codable {
case string(String)
case innerItem(InnerItem)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
if let x = try? container.decode(InnerItem.self) {
self = .innerItem(x)
return
}
throw DecodingError.typeMismatch(MyValue.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for MyValue"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .string(let x):
try container.encode(x)
case .innerItem(let x):
try container.encode(x)
}
}
}
You can get string values for your label by conforming to rawRepresentable protocol:
enum MyValue: Codable, RawRepresentable {
var rawValue: String {
switch self {
case .string(let stringVal):
return stringVal
case .innerItem(let myVal):
return String(describing: myVal)
}
}
typealias RawValue = String
init?(rawValue: String) {
return nil
}
case string(String)
case innerItem(InnerItem)
}
let myVal = MyValue.string("testString")
var strVal: String = myVal.rawValue // testString
To get the associated values in the enum you could add two computed properties in MyValue
var stringValue : String? {
guard case .string(let string) = self else { return nil }
return string
}
var innerItemValue : InnerItem? {
guard case .innerItem(let innerItem) = self else { return nil }
return innerItem
}
Or switch on value like in the encode method
switch root.value {
case .string(let string): // do something with `string`
case .innerItem(let innerItem): // do something with `innerItem`
}
Or simply use if case
if case .string(let string) = root.value { someLabel.text = string }

how to use Codable with Class enum

I feel like I'm going way too far for what should really be a simple thing. With the below code, the error that I get is: Cannot invoke 'decode' with an argument list of type '(GameOfLife.Cell, forKey: GameOfLife.Cell.CodingKeys)'
extension GameOfLife {
enum Cell: Equatable, Codable {
case alive
case born
case dying
case dead
var isAlive: Bool {
switch self {
case .alive, .born: return true
case .dying, .dead: return false
}
}
var isDead: Bool {
switch self {
case .alive, .born: return false
case .dying, .dead: return true
}
}
func equalTo(_ rhs: Cell) -> Bool {
switch (self) {
case .alive, .born:
return rhs.isAlive
case .dead, .dying:
return rhs.isDead
}
}
init(_ living: Bool) {
self = living ? .alive : .dead
}
enum CodingKeys: CodingKey {
case alive
case born
case dying
case dead
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
let leftValue = try container.decode(GameOfLife.Cell.alive, forKey: CodingKeys.alive)
self = GameOfLife.Cell.alive
} catch {
let leftValue = try container.decode(GameOfLife.Cell.born, forKey: CodingKeys.born)
self = GameOfLife.Cell.born
} catch {
let leftValue = try container.decode(GameOfLife.Cell.dying, forKey: CodingKeys.dying)
self = GameOfLife.Cell.dying
} catch {
let leftValue = try container.decode(GameOfLife.Cell.dead, forKey: CodingKeys.dead)
self = GameOfLife.Cell.dead
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .alive:
try container.encode("alive", forKey: .alive)
case .born:
try container.encode("born", forKey: .born)
case .dying:
try container.encode("dying", forKey: .dying)
case .dead:
try container.encode("dead", forKey: .dead)
}
}
}
}
The first parameter of decode( is the expected type of the decoded object. As you are encoding the enum cases a String, it's supposed to be String
let leftValue = try container.decode(String.self, forKey: .alive) // The compiler can infer the type `CodingKeys`
However the entire init(from method makes no sense. If an error occurs inside a catch expression it is not going to be caught in the subsequent catch expression.
To encode an enum case as String just declare the raw type of the enum as String and delete the CodingKeys and the init(from and encode(to methods. And if you want to adopt Equatable you have to implement ==
extension GameOfLife : Codable {
enum Cell: String, Equatable, Codable {
case alive, born, dying, dead
var isAlive: Bool {
switch self {
case .alive, .born: return true
case .dying, .dead: return false
}
}
// isDead can be simplified
var isDead: Bool {
return !isAlive
}
static func == (lhs: Cell, rhs: Cell) -> Bool {
switch (lhs.isAlive, rhs.isAlive) {
case (true, true), (false, false): return true
default: return false
}
}
init(_ living: Bool) {
self = living ? .alive : .dead
}
}
}

Dictionary of Optional Keys

Is there any workaround to create a dictionary with Optional keys? I know a proper solution for this would require Conditional Conformances to be implemented, but do I have any options before then?
let dict: [Int?: Int] = [nil: 1]
// ERROR: type 'Int?' does not conform to protocol 'Hashable'
Update: Swift 4.2
Conditional conformances have been implemented in Swift 4.2, woohoo! 🎉
This allowed for Optional to be made conditionally Hashable, when the wrapped element is Hashable. So you can use any Optional<T: Hashable> as a dictionary key, directly!
let d: [Int?: String] = [
nil: "nil",
1: "a",
2: "b",
3: "c",
]
for (key, value) in d {
let stringKey = key.map(String.init(describing:)) ?? "nil"
print("\(stringKey): \(value)")
}
Before Swift 4.2
Here's one workaround I found: Create a new HashableOptional enum:
enum HashableOptional<Wrapped: Hashable> {
case none
case some(Wrapped)
public init(_ some: Wrapped) {
self = .some(some)
}
public init(_ optional: Wrapped?) {
self = optional.map{ .some($0) } ?? .none
}
public var value: Wrapped? {
switch self {
case .none: return nil
case .some(let wrapped): return wrapped
}
}
}
extension HashableOptional: Equatable {
static func ==(lhs: HashableOptional, rhs: HashableOptional) -> Bool {
switch (lhs, rhs) {
case (.none, .none): return true
case (.some(let a), .some(let b)): return a == b
default: return false
}
}
}
extension HashableOptional: Hashable {
var hashValue: Int {
switch self {
case .none: return 0
case .some(let wrapped): return wrapped.hashValue
}
}
}
extension HashableOptional: ExpressibleByNilLiteral {
public init(nilLiteral: ()) {
self = .none
}
}
And then you can use it like so:
let dict: [HashableOptional<Int>: Int] = [nil: 1]

How to archive enum with an associated value?

I'm trying to encode an object and i have some troubles. It work's fine with strings, booleans and else, but i don't know how to use it for enum.
I need to encode this:
enum Creature: Equatable {
enum UnicornColor {
case yellow, pink, white
}
case unicorn(UnicornColor)
case crusty
case shark
case dragon
I'm using this code for encode:
func saveFavCreature(creature: Dream.Creature) {
let filename = NSHomeDirectory().appending("/Documents/favCreature.bin")
NSKeyedArchiver.archiveRootObject(creature, toFile: filename)
}
func loadFavCreature() -> Dream.Creature {
let filename = NSHomeDirectory().appending("/Documents/favCreature.bin")
let unarchived = NSKeyedUnarchiver.unarchiveObject(withFile: filename)
return unarchived! as! Dream.Creature
}
Here is required functions (model.favoriteCreature == Dream.Creature)
override func encode(with aCoder: NSCoder) {
aCoder.encode(model.favoriteCreature, forKey: "FavoriteCreature")
aCoder.encode(String(), forKey: "CreatureName")
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let favoriteCreature = aDecoder.decodeObject(forKey: "FavoriteCreature")
let name = aDecoder.decodeObject(forKey: "CreatureName")
}
It works fine with "name", i think the problem is in aCoder.encode() coz i don't know what type to write there. I get next error when run:
-[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance
-[NSKeyedArchiver dealloc]: warning: NSKeyedArchiver deallocated without having had -finishEncoding called on it.
I read some advices in comments and can assume that i have no rawValues in enum Creature, i made raw type "String" in that enum:
enum Creature: String, Equatable {
enum UnicornColor {
case yellow, pink, white
}
case unicorn(UnicornColor)
case crusty
case shark
case dragon
Now i have this error:
Enum with raw type cannot have cases with arguments.
Also i read that associated values and raw values can't coexist. Maybe there is some other way to archive enum without raw values?
Hope someone can help me, thank's
You are dealing with a problem that arises because Swift native features don't always play well with Objective-C. NSCoding has its roots in the Objective-C world, and Objective-C doesn't know anything about Swift Enums, so you can't simply archive an Enum.
Normally, you could just encode/decode the enumeration using raw values, but as you found, you can't combine associated types and raw values in a Swift enumeration.
Unfortunately this means that you will need to build your own 'raw' value methods and handle the cases explicitly in the Creature enumeration:
enum Creature {
enum UnicornColor: Int {
case yellow = 0, pink, white
}
case unicorn(UnicornColor)
case crusty
case shark
case dragon
init?(_ creatureType: Int, color: Int? = nil) {
switch creatureType {
case 0:
guard let rawColor = color,
let unicornColor = Creature.UnicornColor(rawValue:rawColor) else {
return nil
}
self = .unicorn(unicornColor)
case 1:
self = .crusty
case 2:
self = .shark
case 3:
self = .dragon
default:
return nil
}
}
func toRawValues() -> (creatureType:Int, unicornColor:Int?) {
switch self {
case .unicorn(let color):
let rawColor = color.rawValue
return(0,rawColor)
case .crusty:
return (1,nil)
case .shark:
return (2,nil)
case .dragon:
return (3,nil)
}
}
}
You can then encode/decode like this:
class SomeClass: NSObject, NSCoding {
var creature: Creature
init(_ creature: Creature) {
self.creature = creature
}
required init?(coder aDecoder: NSCoder) {
let creatureType = aDecoder.decodeInteger(forKey: "creatureType")
let unicornColor = aDecoder.decodeInteger(forKey: "unicornColor")
guard let creature = Creature(creatureType, color: unicornColor) else {
return nil
}
self.creature = creature
super.init()
}
func encode(with aCoder: NSCoder) {
let creatureValues = self.creature.toRawValues()
aCoder.encode(creatureValues.creatureType, forKey: "creatureType")
if let unicornColor = creatureValues.unicornColor {
aCoder.encode(unicornColor, forKey: "unicornColor")
}
}
}
Testing gives:
let a = SomeClass(.unicorn(.pink))
var data = NSMutableData()
let coder = NSKeyedArchiver(forWritingWith: data)
a.encode(with: coder)
coder.finishEncoding()
let decoder = NSKeyedUnarchiver(forReadingWith: data as Data)
if let b = SomeClass(coder: decoder) {
print(b.creature)
}
unicorn(Creature.UnicornColor.pink)
Personally, I would make Creature a class and use inheritance to deal with the variation between unicorns and other types
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.
To simplify the coding/decoding you could provide an initializer for Creature requiring a Data and a computed property named data. As Creature changes or as new associated values are added, the interface to NSCoding does not change.
class Foo: NSObject, NSCoding {
let creature: Creature
init(with creature: Creature = Creature.crusty) {
self.creature = creature
super.init()
}
required init?(coder aDecoder: NSCoder) {
guard let data = aDecoder.decodeObject(forKey: "creature") as? Data else { return nil }
guard let _creature = Creature(with: data) else { return nil }
self.creature = _creature
super.init()
}
func encode(with aCoder: NSCoder) {
aCoder.encode(creature.data, forKey: "creature")
}
}
A serialization of Creature into and out of Data could be accomplished like this.
enum Creature {
enum UnicornColor {
case yellow, pink, white
}
case unicorn(UnicornColor)
case crusty
case shark
case dragon
enum Index {
static fileprivate let ofEnum = 0 // data[0] holds enum value
static fileprivate let ofUnicornColor = 1 // data[1] holds unicorn color
}
init?(with data: Data) {
switch data[Index.ofEnum] {
case 1:
switch data[Index.ofUnicornColor] {
case 1: self = .unicorn(.yellow)
case 2: self = .unicorn(.pink)
case 3: self = .unicorn(.white)
default:
return nil
}
case 2: self = .crusty
case 3: self = .shark
case 4: self = .dragon
default:
return nil
}
}
var data: Data {
var data = Data(count: 2)
// the initializer above zero fills data, therefore serialize values starting at 1
switch self {
case .unicorn(let color):
data[Index.ofEnum] = 1
switch color {
case .yellow: data[Index.ofUnicornColor] = 1
case .pink: data[Index.ofUnicornColor] = 2
case .white: data[Index.ofUnicornColor] = 3
}
case .crusty: data[Index.ofEnum] = 2
case .shark: data[Index.ofEnum] = 3
case .dragon: data[Index.ofEnum] = 4
}
return data
}
}