How should I implement Default Associated Values with Swift Enums? - swift

Swift question is there a way of having an enum type with one case that can have an associated value.
I have an API that gives me available filters, it's unlikely but possible that the API will add additional filter types. So if the API sends an unknown filter type I want to keep that information associated with the enum.
Below are the different ideas I had about doing this.
My first two tries didn't compile. My third try just feels a bit clunky.
Does anyone have a better way of doing this? Do you think I just shouldn't use an enum for this problem?
typealias APIFilterIdentifier = String
/* Does Not Compile */
enum EnumTestAssociatedValeu: APIFilterIdentifier {
case Unknown(APIFilterIdentifier)
case Everyone = "everyone"
case Team = "myteam"
}
/* Does not compile */
enum EnumTestDefaultAssociatedValues: APIFilterIdentifier {
case Unknown(APIFilterIdentifier)
case Everyone(APIFilterIdentifier = "everyone")
case Team(APIFilterIdentifier = "myteam")
}
/* Compiles but is there a better way? */
enum EnumTestWithCustomInit {
case Unknown(APIFilterIdentifier)
case Everyone
case Team
init(filterIdentifier: APIFilterIdentifier) {
let everyone: APIFilterIdentifier = EnumTestWithCustomInit.everyoneFilterIdentifier
let team: APIFilterIdentifier = EnumTestWithCustomInit.teamFilterIdentifier
switch filterIdentifier {
case everyone:
self = .Everyone
case team:
self = .Team
default:
self = .Unknown(filterIdentifier)
}
}
func asIdentifer() -> APIFilterIdentifier {
switch self {
case .Everyone:
return EnumTestWithCustomInit.everyoneFilterIdentifier
case .Team:
return EnumTestWithCustomInit.teamFilterIdentifier
case .Unknown(let filterIdentifier):
return filterIdentifier
}
}
private static var everyoneFilterIdentifier: APIFilterIdentifier {
return "everyone"
}
private static var teamFilterIdentifier: APIFilterIdentifier {
return "myteam"
}
}

I know this is a bit old, but would this work for what you want?
typealias FilterIdentifier = String
enum DefaultAPIFilters: FilterIdentifier {
case Everyone = "everyone"
case Team = "team"
}
enum APIFilters {
case Default(DefaultAPIFilters)
case Custom(FilterIdentifier)
}
let everyoneFilter = APIFilters.Default(.Everyone)
let teamFilter = APIFilters.Default(.Team)
let clownFilter = APIFilters.Custom("clowns_only")

Extending Nathan Perry's response:
You can add a
var underlyingString: String {
return getUnderlyingString(self)
}
to the enum. Then define
func getUnderlyingString(apiFilter: APIFilters) -> String {
switch apiFilter {
case .Default(let defaultAPIFilter):
return defaultAPIFilter.rawValue
case .Custom(let custom):
return custom
}
}

According to this answer: Default value for Enum in Swift
I recommend using such an approach
public enum Result {
case passed(hint: String)
case failed(message: String)
static let passed: Self = .passed(hint: "")
}
let res: Result = Result.passed

Related

SOLVED - Swift Enum - Casting Nested Enums to String Enum to allow .rawValue

SOLVED
Thank you #New Dev and #Joakim Danielson for your help. I used #Joakim Danielson's answer to improve my code.
I have an extension method to assign accessibilityIdentifiers to views based on a given String Enum. I updated the method to directly accept String Enum Cases as a parameter, thus COMPLETELY eliminating the need for the AccessibilityId enum class as shown below, awesome!
Changes
Before:
.accessibility(identifier: .home(.clickButton))
// Simplified for StackOverflow.
// Imagine 20 more cases..
enum AccessibilityId {
case home(HomeId)
var rawValue: String {
switch self {
case .home(let id):
return id.rawValue
}
}
}
extension View {
func accessibility(identifier: AccessibilityId) -> ModifiedContent<Self, AccessibilityAttachmentModifier> {
self.accessibility(identifier: identifier.rawValue)
}
}
After:
.accessibility(identifier: HomeId.clickButton)
extension View {
func accessibility<T: RawRepresentable>(identifier: T) -> ModifiedContent<Self, AccessibilityAttachmentModifier> where T.RawValue == String {
self.accessibility(identifier: identifier.rawValue)
}
}
---------------------------------------------------------------
Original Question
What I have
enum Item {
case animal(AnimalId)
case vehicle(VehicleId)
case food(FoodId)
var rawValue: String {
switch self {
case .animal(let id):
return id.rawValue
case .vehicle(let id):
return id.rawValue
case .food(let id):
return id.rawValue
}
}
}
enum AnimalId: String {
case cat
case dog
}
// etc.
// Imagine more cases and more enums.
What I want
enum Item {
case animal(AnimalId)
case vehicle(VehicleId)
case food(FoodId)
var rawValue: String {
switch self {
case self as StringEnum:
return id.rawValue
default:
return ""
}
}
}
Usage
func test() {
foo(.animal(.cat))
foo(.vehicle(.plane))
foo(.food(.tacos))
}
func foo(_ item: Item) {
print(item.rawValue)
}
I am happy with the usage, but I'd like to reduce the amount of duplicate cases in the given switch statement. Notice how they all have return id.rawValue. The above is just an example, in reality I have around 30 cases.
My Question
Is there a way for me to catch all Nested String Enums in a single switch or let case to reduce the duplicate code I have to write without losing the intended usage?
Thank you for your efforts, I hope to find an improvement for my code!
Here is a solution that is not based on Item being an enum but instead a generic struct
struct Item<T: RawRepresentable> where T.RawValue == String {
let thing: T
var rawValue: String {
thing.rawValue
}
}
With this solution you don't need to change your other enums.
Example
let item1 = Item(thing: AnimalId.cat)
let item2 = Item(thing: VehicleId.car)
print(item1.rawValue, item2.rawValue)
outputs
cat car
You need something common between all these associated values, like a conformance to a shared protocol, e.g. protocol RawStringValue:
protocol RawStringValue {
var rawValue: String { get }
}
// String enums already conform without any extra implementation
extension AnimalId: RawStringValue {}
extension VehicleId: RawStringValue {}
extension FoodId: RawStringValue {}
Then you could create a switch self inside like so:
var rawValue: String {
switch self {
case .animal (let id as RawStringValue),
.vehicle (let id as RawStringValue),
.food (let id as RawStringValue):
return id.rawValue
}
}
That being said, enum with associated values isn't the most convenient type to work with, so be sure that it's the right choice.

How to create Enum for hardcoded string in swift?

I have some hardcoded string, based on that i have created switch case. But instead of string in switch case i am trying to create Enum for same. but i am not sure how to do that. I dont want to use default case.
Do i need to access with raw value or any other better way to do ?
enum screens: String {
case faq = "faq", contactus = "contactus", termncondi = "termncondi", dashoboard = "dashoboard"
}
func deepLink(text: String) -> String {
switch text {
case "faq":
return (FAQ.localized())
case "contactus":
return (Contactus.localized())
case "termncondi":
return (Term.localized())
case "dashoboard":
return (Dashboard.localized())
default:
return ""
}
}
You can simply define the enum and override the rawValue property like so:
enum Link {
case faq
case contact
...
var rawValue: String {
switch self {
case .faq:
return FAQ.localized()
case .contact:
return Contact.localized()
default:
return "Unknown case"
}
}
}
And get its rawValue.
let faqLink = Link.faq.rawValue
You can also perform a switch on an enum instance just like you do with a string.
In order to get hardcoded values do something like:
public extension String {
static func getScreens(name: DeepLink) -> String {
return name.rawValue
}
public enum DeepLink: String {
case faq = "faq"
case contactus = "contactus"
// and so on
}
}

Can I use delegate in enum? (Swift)

I'm trying to use a value from a ViewController in enum. I'm not sure but the only thing I can think of is using a protocol/delegate, which seems like a bad idea...
Right now, the value I want is declared globally, so it works just fine in the code below... but I don't want to declare it globally and would like to declare it in a ViewController.
import Foundation
enum FIRCollectionReference: String {
case users
case chatList
case chatData
case templateReply
func goToLayer() -> String {
switch self {
case .chatData:
return "users/\(myDocId)/chatData"
case .templateReply:
return "users/\(myDocId)/chatData"
default:
return self.rawValue
}
}
}
Any suggestions?
Create a method in enum FIRCollectionReference that accepts an Int (myDocId), i.e.
enum FIRCollectionReference: String {
case users
case chatList
case chatData
case templateReply
func goToLayer(_ myDocId: Int) -> String {
switch self {
case .chatData:
return "users/\(myDocId)/chatData"
case .templateReply:
return "users/\(myDocId)/chatData"
default:
return self.rawValue
}
}
}
Usage:
let reference = FIRCollectionReference.chatData
print(reference.goToLayer(1234))

Generic class using some class returned from a function cause an error

I am confused using generics.
I want to implement something like this:
let assume WantedClass as
class WantedClass<T>: NSObject where T: SomeBasicProtocol {
....
}
and an enum
enum Provider: Int {
case one = 1
case two
case three
var providerClass: SomeBasicProtocol.Type {
switch self {
case .one:
return SomeClass1.self
case .two:
return SomeClass2.self
case .three:
return SomeClass3.self
}
}
}
when I try to define an instance
let provider: Provider = .one
let GenericClassIWantToInject = provider.providerClass
let wantedInstance = WantedClass<GenericClassIWantToInject>()
it yields this error:
Use of undeclared type 'GenericClassIWantToInject'
Why? What have I misunderstood? Is it possible to code this way?
class SomeClass1: SomeBasicProtocol { }
class SomeClass2: SomeBasicProtocol { }
class SomeClass3: SomeBasicProtocol { }
To make things clearer, you have done everything correct except for the last part where you did this let wantedInstance = WantedClass<GenericClassIWantToInject>(). Lets break down and see what you are trying to do with this line of code. You are trying to tell the compiler at compile time, that WantedClass has a generic parameter that has to be GenericClassIWantToInject which is computed at run time. Doesn't that sound wrong to you? Can the compiler infer the Type of a generic parameter at compile time with something that is computed at run time? So to answer one of your questions, no you can not use the code this way.
What you can do is something like this:
protocol SomeBasicProtocol {}
class WantedClass<T>: NSObject where T: SomeBasicProtocol {
let generic: T
init(generic: T) {
self.generic = generic
super.init()
}
}
class SomeClass1: SomeBasicProtocol {}
class SomeClass2: SomeBasicProtocol {}
class SomeClass3: SomeBasicProtocol {}
enum Provider: Int {
case one = 1
case two
case three
var providerClass: SomeBasicProtocol {
switch self {
case .one:
return SomeClass1()
case .two:
return SomeClass2()
case .three:
return SomeClass3()
}
}
}
let provider: Provider = .one
let classToInject = provider.providerClass
switch classToInject {
case let class1 as SomeClass1:
let wantedClass = WantedClass(generic: class1)
case let class2 as SomeClass2:
let wantedClass = WantedClass(generic: class2)
case let class3 as SomeClass3:
let wantedClass = WantedClass(generic: class3)
default:
return
}
Now I'm not sure what you want to achieve, but following your example I came up with the code above and that's what I'm guessing you want to do.
Moreover do these changes if you don't need a variable of your generic Parameter in WantedClass:
class WantedClass<T>: NSObject where T: SomeBasicProtocol {
override init() {
super.init()
}
}
switch classToInject {
case let class1 as SomeClass1:
let wantedClass = WantedClass<SomeClass1>()
case let class2 as SomeClass2:
let wantedClass = WantedClass<SomeClass2>()
case let class3 as SomeClass3:
let wantedClass = WantedClass<SomeClass3>()
default:
return
}
Thanks to #Serj
I've changed my code to this one:
let providerClass = provider.providerClass
switch providerClass {
case is SomeClass1.Type:
let wantedInstance = WantedClass<SomeClass1>()
case is SomeClass2.Type:
let wantedInstance = WantedClass<SomeClass2>()
case is SomeClass3.Type:
let wantedInstance = WantedClass<SomeClass3>()
default: break
}
Now it works.

In Swift, is it possible to convert a string to an enum?

If I have an enum with the cases a,b,c,d is it possible for me to cast the string "a" as the enum?
Sure. Enums can have a raw value. To quote the docs:
Raw values can be strings, characters, or any of the integer or
floating-point number types
β€” Excerpt From: Apple Inc. β€œThe Swift Programming Language.” iBooks. https://itun.es/us/jEUH0.l,
So you can use code like this:
enum StringEnum: String
{
case one = "value one"
case two = "value two"
case three = "value three"
}
let anEnum = StringEnum(rawValue: "value one")!
print("anEnum = \"\(anEnum.rawValue)\"")
Note: You don't need to write = "one" etc. after each case. The default string values are the same as the case names so calling .rawValue will just return a string
EDIT
If you need the string value to contain things like spaces that are not valid as part of a case value then you need to explicitly set the string. So,
enum StringEnum: String
{
case one
case two
case three
}
let anEnum = StringEnum.one
print("anEnum = \"\(anEnum)\"")
gives
anEnum = "one"
But if you want case one to display "value one" you will need to provide the string values:
enum StringEnum: String
{
case one = "value one"
case two = "value two"
case three = "value three"
}
All you need is:
enum Foo: String {
case a, b, c, d
}
let a = Foo(rawValue: "a")
assert(a == Foo.a)
let πŸ’© = Foo(rawValue: "πŸ’©")
assert(πŸ’© == nil)
In Swift 4.2, the CaseIterable protocol can be used for an enum with rawValues, but the string should match against the enum case labels:
enum MyCode : String, CaseIterable {
case one = "uno"
case two = "dos"
case three = "tres"
static func withLabel(_ label: String) -> MyCode? {
return self.allCases.first{ "\($0)" == label }
}
}
usage:
print(MyCode.withLabel("one")) // Optional(MyCode.one)
print(MyCode(rawValue: "uno")) // Optional(MyCode.one)
In case with an enum with Int type you can do it so:
enum MenuItem: Int {
case One = 0, Two, Three, Four, Five //... as much as needs
static func enumFromString(string:String) -> MenuItem? {
var i = 0
while let item = MenuItem(rawValue: i) {
if String(item) == string { return item }
i += 1
}
return nil
}
}
And use:
let string = "Two"
if let item = MenuItem.enumFromString(string) {
//in this case item = 1
//your code
}
Riffing on djruss70's answer to create highly generalized solution:
extension CaseIterable {
static func from(string: String) -> Self? {
return Self.allCases.first { string == "\($0)" }
}
func toString() -> String { "\(self)" }
}
Usage:
enum Chassis: CaseIterable {
case pieridae, oovidae
}
let chassis: Chassis = Chassis.from(string: "oovidae")!
let string: String = chassis.toString()
Note: this will unfortunately not work if the enum is declared #objc. As far as I know as of Swift 5.3 there is no way to get this to work with #objc enum's except brute force solutions (a switch statement).
If someone happens to know of a way to make this work for #objc enums, I'd be very interested in the answer.
Swift 4.2:
public enum PaymentPlatform: String, CaseIterable {
case visa = "Visa card"
case masterCard = "Master card"
case cod = "Cod"
var nameEnum: String {
return Mirror(reflecting: self).children.first?.label ?? String(describing: self)
}
func byName(name: String) -> PaymentPlatform {
return PaymentPlatform.allCases.first(where: {$0.nameEnum.elementsEqual(name)}) ?? .cod
}
}
Extending Duncan C's answer
extension StringEnum: StringLiteralConvertible {
init(stringLiteral value: String){
self.init(rawValue: value)!
}
init(extendedGraphemeClusterLiteral value: String) {
self.init(stringLiteral: value)
}
init(unicodeScalarLiteral value: String) {
self.init(stringLiteral: value)
}
}
For Int enum and their String representation, I declare enum as follows:
enum OrderState: Int16, CustomStringConvertible {
case waiting = 1
case inKitchen = 2
case ready = 3
var description: String {
switch self {
case .waiting:
return "Waiting"
case .inKitchen:
return "InKitchen"
case .ready:
return "Ready"
}
}
static func initialize(stringValue: String)-> OrderState? {
switch stringValue {
case OrderState.waiting.description:
return OrderState.waiting
case OrderState.inKitchen.description:
return OrderState.inKitchen
case OrderState.ready.description:
return OrderState.ready
default:
return nil
}
}
}
Usage:
order.orderState = OrderState.waiting.rawValue
let orderState = OrderState.init(rawValue: order.orderState)
let orderStateStr = orderState?.description ?? ""
print("orderStateStr = \(orderStateStr)")
I used this:
public enum Currency: CaseIterable, Codable {
case AFN = 971 // Afghani (minor=2)
case DZD = 012 // Algerian Dinar (minor=2)
...
private static var cachedLookup: [String: Currency] = [:]
init?(string: String) {
if Self.cachedLookup.isEmpty {
Self.cachedLookup = Dictionary(uniqueKeysWithValues: Self.allCases.map { ("\($0)", $0) })
}
if let currency = Self.cachedLookup[string] {
self = currency
return
} else {
return nil
}
}
}
I found the other answers make this way more complicated then it needs to be. Here is a quick and concise example.
enum Gender: String {
case male, female, unspecified
}
Simple enum, note that I added ": String" to the enum itself to declare the type as string.
Now all you have to do is:
let example: Gender = Gender(rawValue: "male")
And thats it, 'example' is now an enum of type Gender with the value of .male
There is literally nothing else you need to do in Swift 4+.