Refactoring two similar swift switch statements - swift

I have a couple of switch statements where I initially convert a some JSON values to a user-friendly name (convertCategoryValueToName).
Later on, in a different part of the app, I need to undertake some checks based on user-friendly name, and I have written a corresponding switch to convert the name back to it's original value (convertCategoryNameToValue).
The switch statements are long, and I'm not happy with the repetition. Is there a way to refactor this work into one Switch?
Shortened switch examples...
func convertCategoryValueToName(category: String) -> String? {
var categoryName: String?
switch category {
case "dessert":
categoryName = "Desserts"
case "drink":
categoryName = "Drinks"
default:
break
}
return categoryName
}
func convertCategoryNameToValue(category: String) -> String? {
var categoryValue: String?
switch category {
case "Desserts":
categoryValue = "dessert"
case "Drinks":
categoryValue = "drink"
default:
break
}
return categoryValue
}

You should simply use an enum.
enum Category: String {
case dessert = "Desserts"
case drink = "Drinks"
}
Then use Category.dessert.rawValue to display the "user-friendly name" on the UI and use the enum cases for everything else in your code.

This will do:
let dict = ["dessert": "Desserts",
"drink" : "Drinks"]
func convertCategoryValueToName(category: String) -> String? {
return dict[category]
}
func convertCategoryNameToValue(category: String) -> String? {
return dict.keys.first(where: { $0 == category})
}

Related

Return templet string from the selected country code

I would like to get the pre-define temple name based on the country selection. Here I'm trying some code, but unable to get that from code. How do I get back messages based on the country code input?
enum Descriptor: String, CaseIterable, CustomStringConvertible {
case fr = "FR"
case jp = "JP"
var description: String {
get {
return self.rawValue
}
}
var mesage : String {
let templet = "Welcome to "
switch self {
case .fr:
return templet + "France"
case .jp:
return templet + "Japan"
}
}
}
extension Descriptor {
static func hasCountry(code: String) -> String? {
return Descriptor.allCases
.map({$0.rawValue})
.first(where: {$0.description == code})
}
}
let x = Descriptor.hasCountry(code: "JP")
print(x)
// Expected output is like
// Welcome to Japan
// or
// Welcome to France
extension Descriptor {
static func hasCountry(code: String) -> String? {
return Descriptor.allCases
.first(where: {$0.description == code})?
.mesage
}
}
You have a tiny bit of mistake in your extension.
So when you do .map({$0.rawValue}), you actually transform all your enum cases to a strings array ["FR", "JP"].
What you actually want to be doing is find your first enum case, and call .mesage on that one.

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

Kotlin enum classes in Swift

I would like to use this Kotiln code in Swift, but I don't know how to get the best and clean solution:
enum class ProType(val gCode: String, val cCode: String) {
FUND("FN", "PP"),
STOCK("VA", "")
}
Technically #esemusa answer is right. But if you have more than ~5 values in enum, you end up with difficult to maintain giant switch statements for every property.
So for cases like that I prefer to do this:
struct ProTypeItem {
var gCode: String
var cCode: String
}
struct ProType {
static let fund = ProTypeItem(gCode: "FN", cCode: "PP")
static let stock = ProTypeItem(gCode: "VA", cCode: "")
}
And you use it simply as ProType.stock, ProType.fund.gCode etc
You can also make ProTypeItem Comparable, Equatable etc.
should be like this:
enum ProType {
case fund
case stock
var gCode: String {
switch self {
case .fund:
return "FN"
case .stock:
return "VA"
}
}
var cCode: String {
switch self {
case .fund:
return "PP"
case .stock:
return ""
}
}
}

Swift: assign to variable in switch case-let-where

Is it possible to make an assignment to the artist variable before it is used in the where subclause?
var artist
switch fullline {
case let path where path.hasPrefix("Monet"):
artist = "Monet"
case let path where path.hasPrefix("Cezanne"):
artist = "Cezanne"
default: ()
}
Closure:
case let path where { () -> Bool in let artist = "Monet"; return path.hasPrefix(artist) }:
Error:
() -> Bool' is not convertible to 'Bool'
Context:
I have lines of freeform text with artist name as the prefix that requires
massaging to output consistent humanly readable text. e.g.
Monet : Snow at Argenteuil 02, 1874
Monet - Snow at Argenteuil, 1874, 3rd Floor Collections
Monet, Claude - 1875, Snow in Argenteuil
Cezzane - Vase of Flowers, 1880-81, print
Cezzane, Paul 1900-1903 Vase of Flowers
Cezzane - Vase with Flowers, 1895-1896
There will be a code fragments that performs detailed processing/categorizing
for each artist. Hence the processing logic is artist dependent.
I would like to define similar to the following construct
switch fullline
hasPrefix(artist = "Monet")
-> code logic 1
get_birthday(artist)
hasPrefix(artist = "Cezzane")
-> code logic 2
get_birthday(artist)
With a little modification to the Alexander's struct, you can write something like this:
struct PrefixMatcherWithHandler {
var handler: (String)->Void
var string: String
init(_ string: String, handler: #escaping (String)->Void) {
self.string = string
self.handler = handler
}
static func ~= (prefix: String, matcher: PrefixMatcherWithHandler) -> Bool {
if matcher.string.hasPrefix(prefix) {
matcher.handler(prefix)
return true
} else {
return false
}
}
}
var fullline: String = "Monet, Claude"
var artist: String? = nil
let matcher = PrefixMatcherWithHandler(fullline) {str in
artist = str
}
switch matcher {
case "Monet":
break
case "Cezanne":
break
default: break
}
print(artist ?? "") //->Monet
But having some side-effect in boolean operators like ~= makes your code less readable and can easily make unexpected result.
If you just want to reduce some redundant reference to a same thing, switch-statement may not be a good tool for it.
For example, you can get the same result without defining specific matcher types:
var fullline: String = "Monet, Claude"
var artist: String? = nil
if let match = ["Monet", "Cezanne"].first(where: {fullline.hasPrefix($0)}) {
artist = match
}
print(artist ?? "") //->Monet
ADDED for updated parts of the question
The following code behaves slightly different than prefix-matching, but I believe you do not want to match "Mon" to the line Monet, Claude - 1875, Snow in Argenteuil.
extension String {
var firstWord: String? {
var result: String? = nil
enumerateSubstrings(in: startIndex..<endIndex, options: .byWords) {str, _, _, stop in
result = str
stop = true
}
return result
}
}
func get_birthday(_ artist: String) {
//What do you want to do?
print(artist)
}
var fullline: String = "Monet, Claude - 1875, Snow in Argenteuil"
switch fullline.firstWord {
case let artist? where artist == "Monet":
//code dedicated for "Monet"
get_birthday(artist)
case let artist? where artist == "Cezanne":
//code dedicated for "Cezanne"
get_birthday(artist)
default:
break
}
When you can retrieve data suitable for switch-statement, the code would be far more intuitive and readable.
You're giving that closure where a boolean is expected. Not sure why you would want to do this, but you could make it work by using () to invoke the closure.
var artist
switch fullline {
case let path where { () -> Bool in let artist = "Monet"; return path.hasPrefix(artist) }():
artist = "Monet"
case let path where path.hasPrefix("Cezanne"):
artist = "Cezanne"
default: ()
}
Here is how I would do this:
import Foundation
struct PrefixMatcher {
let string: String
init(_ string: String) { self.string = string }
static func ~= (prefix: String, matcher: PrefixMatcher) -> Bool {
return matcher.string.hasPrefix(prefix)
}
}
extension String {
var prefix: PrefixMatcher { return PrefixMatcher(self) }
}
let fullline = "Monet 123456789"
let artist: String?
switch fullline.prefix {
case "Monet": artist = "Monet"
case "Cezanne": artist = "Cezanne"
default: artist = nil
}
print(artist as Any)
More general solution:
import Foundation
struct PredicateMatcher<Pattern> {
typealias Predicate = (Pattern) -> Bool
let predicate: Predicate
static func ~=(pattern: Pattern,
matcher: PredicateMatcher<Pattern>) -> Bool {
return matcher.predicate(pattern)
}
}
extension String {
var prefix: PredicateMatcher<String> {
return PredicateMatcher(predicate: self.hasPrefix)
}
}
You can achieve this by switching over a tuple of your enum and your optional.
Optional is an enum too, so you can switch both of them
enum SomeSnum {
case a, b, c
}
let someString: String? = "something"
let esomeEnum = SomeSnum.b
switch(esomeEnum, someString) {
case (.b, .some(let unwrappedSomething)) where unwrappedSomething.hasPrefix("so"):
print("case .b, \(unwrappedSomething) is unwrapped, and it has `so` prefix")
case (.a, .none):
print("case .a, and optional is nil")
default:
print("Something else")
}
You can also do an if statement
if case let (.b, .some(unwrappedSomething)) = (esomeEnum, someString), unwrappedSomething.hasPrefix("so") {
} else if case (.a, .none) = (esomeEnum, someString) {
} else {
}

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