Extension for String enum implementing CaseIterable Swift 4 - swift4

From swift 4 the protocol CaseIterable in enums has made my life happier but I would like to know if I can create an extension for an enum Type:String,CaseIterable.
So far I can create an enum String extension like that:
extension RawRepresentable where RawValue == String{
func toCorrectFormatSring()->String{
var returnedString = self.rawValue
returnedString = returnedString.uppercased()
return returnedString
}
}
But I have some enums that have a common function and I don't want to repeat it in all the enums. The function gives all the cases in a coma separated string and it looks like that:
enum Vehicle:String,CaseIterable{
case car
case truck
static func getStringList()->String{
let aArray = self.allCases
var returnedString = ""
for aItem in aArray{
returnedString += "\(aItem.toCorrectFormatSring())\(aItem == aArray.last ? "":",")"
}
return returnedString
}
}
The function I want to use wit the extension is getStringList. Is it possible?
OUPUT
[CAR,TRUCK]

You probably want something like this:
extension RawRepresentable where RawValue == String {
func toCorrectFormat() -> String {
let returnedString = // whatever
return returnedString
}
}
extension CaseIterable where Self : RawRepresentable, Self.RawValue == String {
static func getStringList() -> String {
let aArray = Array(self.allCases)
var returnedString = ""
if let last = aArray.last {
for aItem in aArray{
returnedString += "\(aItem.toCorrectFormat())\(aItem == last ? "" : ",")"
}
}
return returnedString
}
}
Now you're good to go, because the protocol extension injects the desired static function into the enum:
enum E : String, CaseIterable {
case howdy
case byebye
}
let s = E.getStringList()
Now that you know how to inject the desired functionality, you can rewrite getStringList in a much better way (the loop is silly, the comparison with last is wrong, and the string interpolation is unnecessary). I think what you're really after is something like this:
extension CaseIterable where Self : RawRepresentable, Self.RawValue == String {
static func getStringList() -> String {
return Array(self.allCases)
.map{$0.rawValue.uppercased()}
.joined(separator:",")
}
}

Related

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

Expression pattern of type 'Roll' cannot match values of type 'Requests.RawValue' (aka 'Roll')

I am trying to create an enum that returns a struct and I am not succeeding . I have checked a few questions similar to mine e.g here, and this here and they do not address the issue I have. This is my code:
import Foundation
struct Roll {
let times: String
init(with times: String) {
self.times = times
}
}
fileprivate enum Requests {
case poker
case cards
case slots
}
extension Requests: RawRepresentable {
typealias RawValue = Roll
init?(rawValue: RawValue) {
switch rawValue {
case Roll(with: "once"): self = .poker
case Roll(with: "twice"): self = .cards
case Roll(with: "a couple of times"): self = .slots
default: return nil
}
}
var rawValue: RawValue {
switch self {
case .poker: return Roll(with: "once")
case .cards: return Roll(with: "twice")
case .slots: return Roll(with: "a couple of times")
}
}
}
Then I would like to use it like : Requests.cards.rawValue
That's because of your structure Roll doesn't conform Equatable protocol and you're trying to make a comparison of it, just change it to
struct Roll: Equatable {
let times: String
init(with times: String) {
self.times = times
}
}

Can you extend only RawRepresentables which use Strings for the raw value type?

I'm trying to write an extension that extends enums based on strings. The way I know to extend all enumerations is to extend RawRepresentable, but I want it restricted to strings-only.
extension RawRepresentable where RawRepresentable.RawValue == String{
func foo(){
let myRawValue:String = self.rawValue
}
}
So how do you specify a 'where' clause to achieve this?
To extend just RawRepresentables based on Strings, the where clause is simply where RawValue == String:
extension RawRepresentable where RawValue == String {
func foo() {
let myRawValue:String = self.rawValue
print(myRawValue)
}
}
enum Flintstone: String {
case fred, wilma, pebbles
}
Flintstone.fred.foo() // fred

How to sort objects by its enum value?

I've got Workout class with Difficulty property
enum Difficulty: String {
case easy = "easy"
case moderate = "moderate"
case hard = "hard"
}
class Workout {
var name: String?
var difficulty: Difficulty?
.
.
.
}
I'd like to sort an array of workouts by the difficulty property. I know I can achieve that by assigning enum's raw value to Int value and compare these values as follows:
data.sort { $0.workout.difficulty!.rawValue < $1.workout.difficulty!.rawValue }
But I really want this enum to store string, since it's convenient to assign it to label text down the line without ugly switch-case hacks, and be comparable in some way.
How to achieve that?
Implement the Comparable protocol on your enum. It gives you a static func < (lhs: Difficulty, rhs: Difficulty) -> Bool method where you define the sort.
Here is a full sample using a property to simplify the ordering
enum Difficulty: String, Comparable {
case easy = "easy"
case moderate = "moderate"
case hard(String) = "hard"
private var sortOrder: Int {
switch self {
case .easy:
return 0
case .moderate:
return 1
case .hard(_):
return 2
}
}
static func ==(lhs: Difficulty, rhs: Difficulty) -> Bool {
return lhs.sortOrder == rhs.sortOrder
}
static func <(lhs: Difficulty, rhs: Difficulty) -> Bool {
return lhs.sortOrder < rhs.sortOrder
}
}
Making it possible to use
data.sort { $0.workout.difficulty! < $1.workout.difficulty! }
edit/update: Swift 5.1 or later
You can change your enumeration RawValue type to integer and use its rawValue to sort your Workouts. Btw you should use a structure instead of a class and similar to what was suggested by Igor you could make your struct comparable instead of the enumeration:
struct Workout {
let name: String
let difficulty: Difficulty
}
extension Workout {
enum Difficulty: Int { case easy, moderate, hard }
}
extension Workout: Comparable {
static func <(lhs: Workout, rhs: Workout) -> Bool { lhs.difficulty.rawValue < rhs.difficulty.rawValue }
}
let wk1 = Workout(name: "night", difficulty: .hard)
let wk2 = Workout(name: "morning", difficulty: .easy)
let wk3 = Workout(name: "afternoon", difficulty: .moderate)
let workouts = [wk1, wk2, wk3] // [{name "night", hard}, {name "morning", easy}, {name "afternoon", moderate}]
let sorted = workouts.sorted() // [{name "morning", easy}, {name "afternoon", moderate}, {name "night", hard}]

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