Swift - Associated value or extension for an Enum - swift

General question regarding swift enum.
I want to create an enum of "icon" and "associate" a value to the enum case
enum Icon {
case plane
case arrow
case logo
case flag
}
I want to create an associated image to the enum's value.
And also an associated color to the enum value
So for instance if it was possible to do something like:
extension Icon.plane {
var image = {
get {
return UIImage("plane.png")
}
}
var color = {
get {
return UIColor.greenColor()
}
}
}
var image = Icon.arrow.image // the image associated to the enum
var color = Icon.arrow.color // the color associated to the enum
Is this type of thing possible?

Unfortunately you cannot define static properties based on enum cases, but you can use computed properties and switch to return values for each case:
enum Icon {
case plane
case arrow
case logo
case flag
var image: UIImage {
switch self {
case .plane: return UIImage(named: "plane.png")!
case .arrow: return UIImage(named: "arrow.png")!
case .logo: return UIImage(named: "logo.png")!
case .flag: return UIImage(named: "flag.png")!
}
}
var color: UIColor {
switch self {
case .plane: return UIColor.greenColor()
case .arrow: return UIColor.greenColor()
case .logo: return UIColor.greenColor()
case .flag: return UIColor.greenColor()
}
}
}
// usage
Icon.plane.color

Using enums with associated values combined with switch statements you can be very flexible. A first example:
enum Icon {
case plane(img:UIImage, col:UIColor)
case arrow(img:UIImage, col:UIColor)
case logo(img:UIImage, col:UIColor)
case flag(img:UIImage, col:UIColor)
var values:(img:UIImage,col:UIColor) {
switch self {
case let .plane(image, color):
return (image,color)
case let .arrow(image, color):
return (image,color)
case let .logo(image, color):
return (image,color)
case let .flag(image, color):
return (image,color)
}
}
}
var a = Icon.plane(img: UIImage(named: "image.png")!, col: UIColor.blueColor())
a.values.col
a.values.img
and a second example:
enum Icon {
case plane(img:UIImage, col:UIColor)
case arrow(img:UIImage, col:UIColor)
case logo(img:UIImage, col:UIColor)
case flag(img:UIImage, col:UIColor)
var img:UIImage {
switch self {
case let .plane(image, color):
return image
case let .arrow(image, color):
return image
case let .logo(image, color):
return image
case let .flag(image, color):
return image
}
}
var col:UIColor {
switch self {
case let .plane(image, color):
return color
case let .arrow(image, color):
return color
case let .logo(image, color):
return color
case let .flag(image, color):
return color
}
}
}
var a = Icon.plane(img: UIImage(named: "image.png")!, col: UIColor.blueColor())
a.col
a.img
no need for extensions. And if you really do want static values, you could do this:
struct MyIcon {
static let plane = Icon.plane(img: UIImage(named: "image.png")!, col: UIColor.blueColor())
static let arrow = Icon.arrow(img: UIImage(named: "image.png")!, col: UIColor.blueColor())
static let logo = Icon.logo(img: UIImage(named: "image.png")!, col: UIColor.blueColor())
static let flag = Icon.flag(img: UIImage(named: "image.png")!, col: UIColor.blueColor())
}
MyIcon.arrow.col
which might be tidier than placing the fixed literal values inside a switch statement.

Shorter and safer code:
import UIKit
enum Icon: String {
case plane, arrow, logo, flag
var image: UIImage {
// If the image is not available, provide a default value so we dont force optional unwrapping
return UIImage(named: "\(self.rawValue).png") ?? UIImage()
}
var color: UIColor {
switch self {
case .plane: return UIColor.green
case .arrow: return UIColor.green
case .logo: return UIColor.green
case .flag: return UIColor.green
}
}
}
// Usage
Icon.plane.color
Icon.arrow.image

More cleaner and readable
enum Icon {
case plane
case arrow
case logo
case flag
var image: UIImage {
return value.image
}
var color: UIColor {
return value.color
}
private var value: (image: UIImage, color: UIColor) {
switch self {
case .plane: return (UIImage(named: "plane.png")!, UIColor.green)
case .arrow: return (UIImage(named: "arrow.png")!, UIColor.green)
case .logo: return (UIImage(named: "logo.png")!, UIColor.green)
case .flag: return (UIImage(named: "flag.png")!, UIColor.green)
}
}
}
// Use
Icon.plane.image
Icon.plane.color

Related

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

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)
}

Is there a better way to do this switch?

What I'm trying to do is: that based on the notification type, I'll change the image of the tableViewCell, and I know that maybe there is a better way of achieving this. I thought that maybe using enums would be a good way, but there is a lot of code in here, and it will do nothing but grow with time.
Is there a better way of achieving this?
enum NotificationIcons {
case newPayment
case newPaymentMethod
case newOffers
case userInfoUpdate
case supportChat
case newAnnouncement
case cardVerification
var strings: String {
switch self {
case .newPayment:
return "new_payment"
case .newPaymentMethod:
return "new_payment_method"
case .newOffers:
return "new_offers"
case .userInfoUpdate:
return "user_info_update"
case .supportChat:
return "support_chat"
case .newAnnouncement:
return "new_announcement"
case .cardVerification:
return "card_verification"
}
}
var image: UIImage {
switch self {
case .newPayment: return UIImage(named: "Icon-notification-service-pay")!
case .newPaymentMethod: return UIImage(named: "Icon-notification-add-paycard")!
case .newOffers: return UIImage(named: "Icon-notification-promos")!
case .userInfoUpdate: return UIImage(named: "Icon-notification-update-data")!
case .supportChat: return UIImage(named: "Icon-notification-support")!
case .newAnnouncement: return UIImage(named: "Icon-notification-advice")!
case .cardVerification: return UIImage(named: "Icon-notification-add-paycard")!
}
}
var detailImage: UIImage {
switch self {
case .newPayment: return UIImage(named: "Icon-notification-detail-service-pay")!
case .newPaymentMethod: return UIImage(named: "Icon-notification-detail-add-paycard")!
case .newOffers: return UIImage(named: "Icon-notification-detail-promos")!
case .userInfoUpdate: return UIImage(named: "Icon-notification-detail-update-data")!
case .supportChat: return UIImage(named: "Icon-notification-detail-support")!
case .newAnnouncement: return UIImage(named: "Icon-notification-detail-advice")!
case .cardVerification: return UIImage(named: "Icon-notification-detail-add-paycard")!
}
}
}
Inside my tableViewCell I have this variable notification which starts to set all values on didSet
switch notification.type {
case NotificationIcons.newPayment.strings:
notificationImageView.image = NotificationIcons.newPayment.image
break
case NotificationIcons.newPaymentMethod.strings:
notificationImageView.image = NotificationIcons.newPaymentMethod.image
break
case NotificationIcons.newOffers.strings:
notificationImageView.image = NotificationIcons.newPaymentMethod.image
break
case NotificationIcons.userInfoUpdate.strings:
notificationImageView.image = NotificationIcons.newPaymentMethod.image
break
case NotificationIcons.supportChat.strings:
notificationImageView.image = NotificationIcons.newPaymentMethod.image
break
case NotificationIcons.newAnnouncement.strings:
notificationImageView.image = NotificationIcons.newPaymentMethod.image
break
case NotificationIcons.cardVerification.strings:
notificationImageView.image = NotificationIcons.newPaymentMethod.image
break
default:
break
}
Firstly, assign rawValue of type String to the NotificationIcons enum, like this:
enum NotificationIcons: String {
case newPayment = "new_payment"
//...
}
Then, modify the switch statement with an initializer:
guard let type = NotificationIcons(rawValue: notification.type) else { return }
notinotificationImageView.image = type.image

Generic optional enum func

In my project I have a couple of enums which I use everywhere, these enums are created based on external json, so input is always optional. If an enum can't be created from the input, I'll define a default value.
Example of how I do things now:
enum TextAlign:String {
case left, center, right
}
let rawData = [String:Any]()
func getTextAlign() -> TextAlign {
if let rawTextAlignString = rawData["textAlign"] as? String, let align = TextAlign(rawValue: rawTextAlignString) {
return align
}
return TextAlign.left
}
let textAlign = self.getTextAlign()
This works obviously, but I would like to make my constructor a bit more swifty, generic and applicable to more of these enums. My goal is to instantiate these enums like this:
let textAlign = TextAlign(rawOptionalValue: rawData["textAlign"] as? String) ?? TextAlign.left
So I basically want a failable initializer, which I can just write for the TextAlign enum, but there has to be a way to declare this in a more 'generic' way so that I can use the initializer on all of my enum:String instances.
I'm struggeling a bit with the syntax and options of generics in swift.
Any idea?
Update #1
I see a lot of answers which are all not wrong, but probably I wasn't clear enough in what I'm looking for.
I have more enums like this:
enum TextAlign:String {
case left, center, right
}
enum CornerRadius:String {
case none, medium, large
}
enum Spacing:String {
case tight, medium, loose
}
I would like to define just 1 function that can initialize all these enums. (not because I'm lazy, but because I want to understand how to use Generics for this)
Probably what I need is some static func in an extension that applies to all these 'String/RawRepresentable' enums. I don't want to write all these failable initializers for each enum. (I believe it should be possible but I can't figure out the syntax)
Update #2
After playing a bit with Joakim's answer I came up with the following solution:
extension RawRepresentable {
static func create<T:Any>(_ value: Any?, defaultValue: Self) -> Self where Self.RawValue == T {
guard let rawValue = value as? T, let instance = Self.init(rawValue: rawValue) else {
return defaultValue
}
return instance
}
}
This allows me to instantiate enums of type String and Int (and more) with this function. Like this:
enum TextAlign:String {
case left, center, right
}
enum CornerRadius:Int {
case none, medium, large
}
let json:[String:Any] = [
"textAlign":"left",
"cornerRadius":0
]
let cornerRadius = CornerRadius.create(json["cornerRadius"], defaultValue: .medium)
let align = TextAlign.create(json["textAlign"], defaultValue: .center)
I like that I can just pas Any? as an argument and that it takes care of the casting on its own by doing let rawValue = value as? T.
Update #3 (solution)
Okay, the complexcity of it all still bothered me a bit, so I tried the init route, which imo, looks way cleaner. The whole thing now looks like this:
extension RawRepresentable {
init(from value: Any?, or defaultValue: Self) {
self = Self.init(from: value) ?? defaultValue
}
init?(from value: Any?) {
guard let rawValue = value as? Self.RawValue, let instance = Self.init(rawValue: rawValue) else {
return nil
}
self = instance
}
}
I created a failable and a non-failable init with a default value for convenience purposes.
Now I can just instantiate any enum like this:
let cornerRadius = CornerRadius(json["cornerRadius"], or: .medium)
// or an optional one
let align = TextAlign(json["textAlign"])
Now I'm done with the updates...
Here is a function you can use to create enum items from a string
func createEnumItem<T: RawRepresentable>(_ value: String) -> T? where T.RawValue == String {
return T.init(rawValue: value)
}
and then use it like
let textAlign: TextAlign = createEnumItem("right")!
let radius: CornerRadius = createEnumItem("medium")!
Note that you always include the enum type in the variable declaration.
Of course since the return value is optional you need to handle that in a better way than my example here.
Update
In case you always know what your default is here is a modified version
func createEnumItem<T: RawRepresentable>(_ value: String, withDefault defaultItem: T) -> T where T.RawValue == String {
guard let item = T.init(rawValue: value) else {
return defaultItem
}
return item
}
I don't think you need a new function for this. Just use Optional.map(_:):
let alignment = rawData["textAlign"].map(TextAlign.init(rawValue:)) ?? .left
You can declare your enums like this
enum TextAlign: String {
case left, center, right
init(rawOptionalValue: String?) {
self = TextAlign(rawValue: rawOptionalValue ?? TextAlign.left.rawValue) ?? .left
}
}
And then instantiate it like this:
let textAlign = TextAlign(rawOptionalValue: rawData["textAlign"] as? String)
Update
Here's one example with a default value as an optional parameter as well:
enum TextAlign: String {
case left, center, right
init(rawOptionalValue: String?, defaultValue: TextAlign = TextAlign.left) {
self = TextAlign(rawValue: rawOptionalValue ?? defaultValue.rawValue) ?? defaultValue
}
}
let textAlign1 = TextAlign(rawOptionalValue: "left") // .left
let textAlign2 = TextAlign(rawOptionalValue: "right") // .right
let textAlign3 = TextAlign(rawOptionalValue: "center") // .center
let textAlign4 = TextAlign(rawOptionalValue: "notAnAlignment") // .left
let textAlign5 = TextAlign(rawOptionalValue: nil, defaultValue: .center) // .center
Update 2
Ok, now I understand. Well then based on your latest update you can also do this I guess:
extension RawRepresentable {
init(rawOptionalValue: Any?, defaultValue: Self) {
guard let value = rawOptionalValue as? Self.RawValue else {
self = defaultValue
return
}
self = Self.init(rawValue: value) ?? defaultValue
}
}
The only thing here compared to my previous try is that you cannot provide a default defaultValue. So you would use it like this:
let textAlign1 = TextAlign(rawOptionalValue: "left", defaultValue: .left) // .left
let textAlign2 = TextAlign(rawOptionalValue: "right", defaultValue: .left) // .right
let textAlign3 = TextAlign(rawOptionalValue: "center", defaultValue: .left) // .center
let textAlign4 = TextAlign(rawOptionalValue: "notAnAlignment", defaultValue: .left) // .left
let textAlign5 = TextAlign(rawOptionalValue: nil, defaultValue: .center) // .center
You can try this:
let textAlign = TextAlign(rawValue: rawData["textAlign"] as? String ?? TextAlign.left.rawValue)
You can define a static func like so and use it everywhere you need in your code
enum TextAlign: String {
case left, center, right
static func getTextAlign(rawData: [String:Any]) -> TextAlign {
if let rawTextAlignString = rawData["textAlign"] as? String, let align = TextAlign(rawValue: rawTextAlignString) {
return align
}
return TextAlign.left
}
}
// Test
var myRawData = [String:Any]()
let textAlign1 = TextAlign.getTextAlign(rawData: myRawData) // left
myRawData["textAlign"] = "center"
let textAlign2 = TextAlign.getTextAlign(rawData: myRawData) // center

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
}
}

swift set value with switch will got error: expected initial value after '='

I would wonder if there's any short cut way to set value of colorPredicate
enum Color {
case black
case white
}
func predicateForColor(color: Color, compoundWith compoundPredicate: NSPredicate?) -> NSPredicate {
// NOTE: if I use the code bellow to set the value of colorPredicate, will got error: expected initial value after '='.
// let colorPredicate =
// switch color {
// case .black: return predicateForBlack()
// case .white: return predicateForWhite()
// }
func getPredicateByColor(color: Color) -> NSPredicate {
switch color {
case .black: return predicateForBlack()
case .white: return predicateForWhite()
}
}
let colorPredicate = getPredicateByColor(color: color)
if let predicate = compoundPredicate {
return NSCompoundPredicate(andPredicateWithSubpredicates: [predicate, colorPredicate])
} else {
return colorPredicate
}
}
func predicateForBlack() -> NSPredicate {
print("get black predicate")
return NSPredicate(format: "color = black")
}
func predicateForWhite() -> NSPredicate {
print("get white predicate")
return NSPredicate(format: "color = white & someother condition")
}
print(predicateForColor(color: .black, compoundWith: nil))
let colorPredicate: NSPredicate = { (color: Color) -> NSPredicate in
switch color {
case .black: return predicateForBlack()
case .white: return predicateForWhite()
}
}(color)
update
Your code produces an error because you need to write:
let variable = { switch { ... } }()
instead of
let variable = switch { ... }
so that you define a block and call it, you cannot assign from a switch statement.
dictionary approach
setup:
var lookup: [Color: NSPredicate] = [:]
lookup[.black] = NSPredicate(format: "color = black")
lookup[.white] = NSPredicate(format: "color = white")
use:
let colorPredicate = lookup[color]