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