I am new on swift. I have 2+ enums and i would like to create enum to object generator function. I couldn't find what type I should use for the Enum parameter in my function.
My enums;
public enum Animal: String, CaseIterable {
case DOG = "dog"
case CAT = "cat"
case BIRD = "bird"
}
public enum Car: String, CaseIterable {
case BMW = "bmw"
case AUDI = "audi"
}
My Function;
func enumToObj(Enum:TYPE?) -> Dictionary <String, String> {
var enumsObject: [String:String] = [:];
for enumData in Enum.allCases {
let value = enumData.rawValue;
enumsObject[value] = value;
}
return enumsObject;
}
//my expectation
enumToObj(Animal) && enumToObj(Car)
You can define a generic function which take as argument a type that is CaseIterable and RawRepresentable:
func enumToObj<T>(_ Enum:T.Type) -> [String: T.RawValue]
where T: CaseIterable & RawRepresentable
{
var enumsObject: [String: T.RawValue] = [:];
for enumData in Enum.allCases {
let value = enumData.rawValue;
enumsObject["\(enumData)"] = value;
}
return enumsObject;
}
print(enumToObj(Animal.self))
// ["CAT": "cat", "DOG": "dog", "BIRD": "bird"]
print(enumToObj(Car.self))
// ["AUDI": "audi", "BMW": "bmw"]
Using reduce(into:_:) this can also be written as
func enumToObj<T>(_ Enum:T.Type) -> [String: T.RawValue]
where T: CaseIterable & RawRepresentable
{
return Enum.allCases.reduce(into: [:]) { (enumsObject, enumData) in
let value = enumData.rawValue;
enumsObject["\(enumData)"] = value;
}
}
Related
I am trying to write a generic function in the the body of the function, it reads one of the property of the Type.
My function:
func doSomething<Key>(
_ key: Key
) -> Int {
let a = key.aProperty
return doSomethingElse(a)
}
and I have put different struct to doSomething as long as they have the 'aProperty' as one of its properties
struct A {
let a : String
let aProperty: String
}
struct B {
let b : String
let aProperty: String
}
let a = A()
let b = B()
doSomething<A>(a)
doSomething<B>(b)
But I get compiler error in my doSomething function saying 'Value of type 'Key' has no member 'aProperty' .
So can you please tell me what am I missing in defining my doSomething function?
You can use protocol.
func doSomethingElse(_ a: String) -> Int { 0 }
protocol HasAProperty {
var aProperty: String { get }
}
func doSomething<Key: HasAProperty>(
_ key: Key
) -> Int {
let a = key.aProperty
return doSomethingElse(a)
}
struct A: HasAProperty {
let a : String
let aProperty: String
}
struct B: HasAProperty {
let b : String
let aProperty: String
}
let a = A(a: "a", aProperty: "aProperty")
let b = B(b: "b", aProperty: "aProperty")
doSomething(a)
doSomething(b)
Given the following codable struct:
struct MyBanana: Codable {
var b: String?
var z: String?
enum CodingKeys: String, CodingKey {
case b = "B"
case z = "ZOOM"
}
}
How can i get an array of strings like so: ["B", "ZOOM"] without an instance of MyBanana?
Additionally, is there a way to get this from auto-synthesised CodingKeys, e.g.
struct MyBanana: Codable {
var b: String?
var z: String?
}
would return ["b","z"]
I tried the following for the first question:
func whatever<T, Key>(keyedBy: Key.Type) -> [T] where T: Codable, Key: CodingKey
{
let allKeys: [Key] = [Key]()
for k in keyedBy {
allKeys.append(k)
}
return [T]()
}
But i get
Type 'Key.Type' does not conform to protocol 'Sequence'
Update
For declared CodingKeys #Cameron Porter's answer works fine (add CaseIterable to the enum)
For synthesized ones this works, albeit pretty manual still:
extension Decodable {
func getAllCodingKeys(_ activator: (String) -> String) -> [String] {
let mirror = Mirror(reflecting: self)
return mirror.children.filter({ $0.label != nil }).map({ activator($0.label!) })
}
}
Then in your Codable, add
func getAllKeys() -> [String] {
return self.getAllCodingKeys { CodingKeys(stringValue: $0)!.stringValue }
}
struct MyBanana: Codable {
var b: String?
var z: String?
enum CodingKeys: String, CodingKey, CaseIterable {
case b = "B"
case z = "ZOOM"
}
static var allCases: [String] {
return CodingKeys.allCases.map { $0.rawValue }
}
}
And then you can get your array of coding keys as
MyBanana.allCases
Doesn't work for auto-synthesised coding keys however
Is there any way to copy all the values of an enumeration into a dictionary without polling them in any FOR loop?
For example, from this enumeration:
enum FruitPriority: String {
case PEARS
case ORANGES
case APPLES
}
to call some single function from within the ENUM, something like this:
var fruitPriorityArray: [String : Int] = FruitPriority.someFunction()
and get this result (sorted according to hash value):
["PEARS": 0, "ORANGES": 1, "APPLES": 2]
The preference would be to make only a single call to the ENUM.
Thank you.
enum FruitPriority: String, CaseIterable {
case PEARS
case ORANGES
case APPLES
}
let result = FruitPriority.allCases.enumerated().reduce([String: Int]()) { dict, fruit in
var dict = dict
dict[fruit.element.rawValue] = fruit.offset
return dict
}
print(result)
The result is:
["PEARS": 0, "ORANGES": 1, "APPLES": 2]
For version Swift 4.1 and earlier the implementation of CaseIterable:
#if swift(>=4.2)
#else
public protocol CaseIterable {
associatedtype AllCases: Collection where AllCases.Element == Self
static var allCases: AllCases { get }
}
extension CaseIterable where Self: Hashable {
static var allCases: [Self] {
return [Self](AnySequence { () -> AnyIterator<Self> in
var raw = 0
return AnyIterator {
let current = withUnsafeBytes(of: &raw) { $0.load(as: Self.self) }
guard current.hashValue == raw else {
return nil
}
raw += 1
return current
}
})
}
}
#endif
Original post of CaseIterable implementation.
If you need just single call to the enum:
Add CaseIterable to your enum and then just create function getDictionary in enum which returns you dictionary (for each enum case will be rawValue assigned as key and hashValue as value )
enum FruitPriority: String, CaseIterable {
case PEARS
case ORANGES
case APPLES
func getDictionary() -> [String: Int] {
var dictionary = [String: Int]()
FruitPriority.allCases.forEach {
dictionary[$0.rawValue] = $0.hashValue
}
return dictionary
}
}
then you can just call
var fruitPriorityArray: [String : Int] = FruitPriority.getDictionary()
Note: if you're using earlier versions of Swift you can see this to create CaseIterable protocol
If you make your enum CaseIterable, you can construct a dictionary using reduce(into:). Since the hashValue can now change from run to run, I would recommend using enumerated() to number the cases in order:
enum FruitPriority: String, CaseIterable {
case PEARS
case ORANGES
case APPLES
}
let result = FruitPriority.allCases.enumerated().reduce(into: [:]) { $0[$1.element.rawValue] = $1.offset }
print(result)
// ["ORANGES": 1, "APPLES": 2, "PEARS": 0]
I am trying to create a class and use that class as the type for my new enum like shown below.
class Abc{
var age = 25
var name = "Abhi"
}
enum TestEnum : Abc {
case firstCase
case secondCase
}
I am getting following error in playground .
error: raw type 'Abc' is not expressible by any literal
So i tried conforming to RawRepresentable protocol like this.
extension TestEnum : RawRepresentable{
typealias RawValue = Abc
init?(rawValue:RawValue ) {
switch rawValue {
case Abc.age :
self = .firstCase
case Abc.name :
self = .secondCase
}
}
var rawValue : RawValue {
switch self{
case .firstCase :
return Abc.age
case .secondCase :
return Abc.name
}
}
}
I am getting following errors after this :
error: raw type 'Abc' is not expressible by any literal
error: instance member 'age' cannot be used on type 'Abc'
error: instance member 'name' cannot be used on type 'Abc'
What is the proper way to declare enums of a certain class type, not getting clear idea on this. Anyone help?
I'm not really sure what do you want to achieve, but take a look at my implementation, approach that I use in my projects:
class Abc {
var age: Int
var name: String
init(age: Int, name: String) {
self.age = age
self.name = name
}
}
enum TestEnum {
case firstCase
case secondCase
var instance: Abc {
switch self {
case .firstCase: return Abc(age: 25, name: "John")
case .secondCase: return Abc(age: 20, name: "Marry")
}
}
}
//Usage:
let abc = TestEnum.secondCase.instance
print(abc.age) //prints '20'
From Docs
In particular, the raw-value type must conform to the Equatable
protocol and one of the following protocols:
ExpressibleByIntegerLiteral for integer literals,
ExpressibleByFloatLiteral for floating-point literals,
ExpressibleByStringLiteral for string literals that contain any number
of characters, and ExpressibleByUnicodeScalarLiteral or
ExpressibleByExtendedGraphemeClusterLiteral for string literals that
contain only a single character.
So make your class Abc to conform to Equatable and one of the above mentioned protocols. Here is an example
public class Abc : Equatable,ExpressibleByStringLiteral{
var age = 25
var name = "Abhi"
public static func == (lhs: Abc, rhs: Abc) -> Bool {
return (lhs.age == rhs.age && lhs.name == rhs.name)
}
public required init(stringLiteral value: String) {
let components = value.components(separatedBy: ",")
if components.count == 2 {
self.name = components[0]
if let age = Int(components[1]) {
self.age = age
}
}
}
public required convenience init(unicodeScalarLiteral value: String) {
self.init(stringLiteral: value)
}
public required convenience init(extendedGraphemeClusterLiteral value: String) {
self.init(stringLiteral: value)
}
}
enum TestEnum : Abc {
case firstCase = "Jack,29"
case secondCase = "Jill,26"
}
Now you can initialize your enum like
let exEnum = TestEnum.firstCase
print(exEnum.rawValue.name) // prints Jack
For detailed discussion and example you can refer
https://swiftwithsadiq.wordpress.com/2017/08/21/custom-types-as-raw-value-for-enum-in-swift/
Have a look at Associated Values.
For your example:
class Abc {
var age = 25
var name = "Abhi"
}
enum TestEnum {
case age(Int)
case name(String)
}
Then you can use it like this:
var person = Abc()
...
var value = TestEnum.age(person.age)
switch value {
case .age(let age):
print("Age: \(age).")
case .name(let name):
print("Name: \(name).")
}
And for convenience you can write extension for enum, that will take your Abc object and convert it to enum value:
static func fromAbc(_ object: Abc) -> TestEnum? {
if object.age {
return TestEnum.age(object.age)
}
if object.name {
return TestEnum.name(object.name)
}
return nil
}
Note: in func fromAbc(object: Abc) -> TestEnum? you should replace conditions in if's to something that can be expressed as Bool (age > 0, etc).
As for the row values - in the doc it is stated that
Raw values can be strings, characters, or any of the integer or floating- point number types. Each raw value must be unique within its enumeration declaration.
And I'm not sure you can fit class there.
Is this possible to create a enum of Tuples in Swift?
I'd like to build something like:
enum ErrorCode: (Int, String) {
case Generic_Error = (0, "Unknown")
case DB_Error = (909, "Database")
}
But it doesn't compile... Is there a way to obtain a similar result?
Swift enumerations cannot have Tuples as a raw value type.
Alternative approaches include storing the code and deriving a description from that:
enum ErrorCode: Int, CustomStringConvertible {
case Generic = 0
case DB = 909
var description: String {
switch self {
case .Generic:
return "Unknown"
case .DB:
return "Database"
}
}
}
...or storing associated values for code and description in the enumeration cases themselves:
enum Error {
case Generic(Int, String)
case DB(Int, String)
}
If you're just looking for constant values, #matt's suggestion of organizing them within a struct would work, too.
It depends what you mean by "similar". What I do is use a Struct with static constant properties:
struct Trouble {
static let Generic_Error = (0, "Unknown")
static let DB_Error = (909, "Database")
}
Now things like Trouble.Generic_Error are usable throughout your code.
You can conform to RawRepresentable and use (Int, String) as the Self.RawValue associated type.
enum ErrorCode: RawRepresentable {
case Generic_Error
case DB_Error
var rawValue: (Int, String) {
switch self {
case .Generic_Error: return (0, "Unknown")
case .DB_Error: return (909, "Database")
}
}
init?(rawValue: (Int, String)) {
switch rawValue {
case (0, "Unknown"): self = .Generic_Error
case (909, "Database"): self = .DB_Error
default: return nil
}
}
}
you can do such thing, maybe:
enum ErrorCode {
case Generic_Error
case DB_Error
func values() -> (code: Int!, description: String?)! {
switch self {
case .Generic_Error:
return (0, "Unknown")
case .DB_Error:
return (909, "Database")
}
}
}
and you can do such thing later:
let errorCode: ErrorCode = ErrorCode.Generic_Error;
if (errorCode.values().code == 0) {
// do the business here...
}
Create your enum and add a var of tuple type (String, String).
enum SomeType {
case type1
case type2
case type3
var typeNameAndDescription: (name: String, description: String) {
switch self {
case .type1:
return ("type1 name", "type1 description")
case .type2:
return ("type2 name", "type2 description")
case .type3:
return ("type3 name", "type3 description")
}
}
}
and later:
let myType = SomeType.type1
let typeName = myType.typeNameAndDescription.name
let typeDescription = myType.typeNameAndDescription.description
My solution to keep the enum, was to create the get method for the rawValue var:
enum LoadingType {
case poster
case movie
case refresh
var rawValue: (file: String, anim: String) {
get {
switch self {
case .movie:
return ("poster_loading", "LoadingView")
case .poster:
return ("loading", "LoadingView")
case .refresh:
return ("loading", "RefreshView")
}
}
}
}
With this code, you can even call each of your Tuple elements by a name:
self.type.rawValue.file
I think I would change your code to something like this:
enum ErrorCode {
case generic, db
var message: String {
switch self {
case .generic:
return "Unknown"
case .db:
return "Database"
}
}
var code: Int {
switch self {
case .generic:
return 0
case .db:
return 909
}
}
}
I feel this would make it much more easier to use like so:
let error = ErrorCode.generic
print("Error Code: \(error.code), Message: \(error.message)")
enum ErrorCode {
case Generic_Error
case DB_Error
public var code:Int{
switch self {
case .Generic_Error: return 0
case .DB_Error: return 909
}
}
public var name:String{
switch self {
case .Generic_Error: return "Unknown"
case .DB_Error: return "Database"
}
}
}
using:
let err:ErrorCode = .Generic_Error
print(err.code) //0
print(err.name) //Unknown
This is what I did in a similar situation. Use an associated data enum, with default values (you can even name the values in the tuple if you like). Then add a simple function that returns the tuple based on self.
enum ErrorCode {
case Generic_Error( code: Int = 0, desc: String = "Unknown")
case DB_Error( code: Int = 909, desc: String = "Database")
func getCodeDescription() -> (Int, String) {
switch self {
case let .Generic_Error(code, desc): return (code, desc)
case let .DB_Error(code, desc): return(code, desc)
}
}
}
Later
var errorCode: Int
var errorDesc: String
let myError: ErrorCode = .DB_Error() // let it take the default values
(errorCode, errorDesc) = myError.getCodeDescription()
Create an Enumeration as Int.
For example:
https://github.com/rhodgkins/SwiftHTTPStatusCodes/blob/master/Sources/HTTPStatusCodes.swift