Swift - Initialize enum from string in switch statement - ios

Is it possible to test whether a specific enum type can be initialized by rawValue when switching on a string, instead of using if let?
static func getCurrency(from code: String) -> Currency? {
if let fiatCurrency = Fiat(rawValue: code) {
return fiatCurrency
} else if let cryptoCurrency = Blockchain(rawValue: code) {
return cryptoCurrency
} else {
return nil
}
}
This may be similar to type casting, where currency adheres to my Currency protocol:
switch currency {
case let fiatCurrency as Fiat:
return getFiatFormatting(for: value, fiatCurrency: fiatCurrency)
case let blockchain as Blockchain:
return getCryptoFormatting(for: value, blockchain: blockchain)
case let token as Token:
return getTokenFormatting(for: value, token: token)
default:
return nil
}
Thanks!

If I understand correctly what you want, you can use the nil coalescing operator instead of if let.
static func getCurrency(from code: String) -> Currency? {
return Fiat(rawValue: code) ?? Blockchain(rawValue: code)
}
You can add as many other possible enum initializations as you want. It'll evaluate in order and return the first that it's not nil. If all are nil, it returns nil. So, it has exactly the same behaviour as a series of if-let-else.

Related

what's the meaning of optional binding in swift

without optional binding,we use optional like this,it seems tedious
func doSomething(str: String?)
{
let v: String! = str
if v != nil
{
// use v to do something
}
}
with optional binding,it seems the if let doesn't do any thing to make it less tedious. we still have a if statement to test before use it.
func doSomething(str: String?)
{
if let v = str
{
// use v to do something
}
}
Is there any other examples can show some benefits to use optional bindings ?
Advantages of the Optional binding over If Statements and Forced Unwrapping:
local variable which is not an optional and a shortcut when a structure is deeper than one level
Context:
You have three techniques to work with an optionals:
Optional binding
If statements and forced unwrapping
Optional chaining
Optional binding
You use optional binding to find out whether an optional contains a
value, and if so, to make that value available as a temporary constant
or variable. Optional binding can be used with if and while statements
to check for a value inside an optional, and to extract that value
into a constant or variable, as part of a single action.
If Statements and Forced Unwrapping
You can use an if statement to find out whether an optional contains a
value by comparing the optional against nil. You perform this
comparison with the “equal to” operator (==) or the “not equal to”
operator (!=).
Optional chaining
Optional chaining is a process for querying and calling properties,
methods, and subscripts on an optional that might currently be nil. If
the optional contains a value, the property, method, or subscript call
succeeds; if the optional is nil, the property, method, or subscript
call returns nil. Multiple queries can be chained together, and the
entire chain fails gracefully if any link in the chain is nil.
source
struct Computer {
let keyboard: Keyboard?
}
struct Keyboard {
let battery: Battery?
}
struct Battery {
let price: Int?
}
let appleComputer: Computer? = Computer(keyboard: Keyboard(battery: Battery(price: 10)))
func getBatteryPriceWithOptionalBinding() -> Int {
if let computer = appleComputer {
if let keyboard = computer.keyboard {
if let battery = keyboard.battery {
if let batteryPrice = battery.price {
print(batteryPrice)
return batteryPrice
}
}
}
}
return 0
}
func getBatteryPriceWithIfStatementsAndForcedUnwrapping() -> Int {
if appleComputer != nil {
if appleComputer!.keyboard != nil {
if appleComputer!.keyboard!.battery != nil {
if appleComputer!.keyboard!.battery!.price != nil {
print(appleComputer!.keyboard!.battery!.price!)
return appleComputer!.keyboard!.battery!.price!
}
}
}
}
return 0
}
func getBatteryPriceWithOptionalChainingAndForcedUnwrapping() -> Int {
if appleComputer?.keyboard?.battery?.price != nil {
print(appleComputer!.keyboard!.battery!.price!)
return appleComputer!.keyboard!.battery!.price!
}
return 0
}
func getBatteryPriceWithOptionalChainingAndOptionalBinding() -> Int {
if let price = appleComputer?.keyboard?.battery?.price {
print(price)
return price
}
return 0
}
func getBatteryPriceWithOptionalChainingAndNilCoalescing() -> Int {
print(appleComputer?.keyboard?.battery?.price ?? 0)
return appleComputer?.keyboard?.battery?.price ?? 0
}

Enum of structs in Swift 3.0

I am trying to create an enum of a struct that I would like to initialize:
struct CustomStruct {
var variable1: String
var variable2: AnyClass
var variable3: Int
init (variable1: String, variable2: AnyClass, variable3: Int) {
self.variable1 = variable1
self.variable2 = variable2
self.variable3 = variable3
}
}
enum AllStructs: CustomStruct {
case getData
case addNewData
func getAPI() -> CustomStruct {
switch self {
case getData:
return CustomStruct(variable1:"data1", variable2: SomeObject.class, variable3: POST)
case addNewData:
// Same to same
default:
return nil
}
}
}
I get the following errors:
Type AllStructs does not conform to protocol 'RawRepresentable'
I am assuming that enums cannot be used this way. We must use primitives.
It should be:
struct CustomStruct {
var apiUrl: String
var responseType: AnyObject
var httpType: Int
init (variable1: String, variable2: AnyObject, variable3: Int) {
self.apiUrl = variable1
self.responseType = variable2
self.httpType = variable3
}
}
enum MyEnum {
case getData
case addNewData
func getAPI() -> CustomStruct {
switch self {
case .getData:
return CustomStruct(variable1: "URL_TO_GET_DATA", variable2: 11 as AnyObject, variable3: 101)
case .addNewData:
return CustomStruct(variable1: "URL_TO_ADD_NEW_DATA", variable2: 12 as AnyObject, variable3: 102)
}
}
}
Usage:
let data = MyEnum.getData
let myObject = data.getAPI()
// this should logs: "URL_TO_GET_DATA 11 101"
print(myObject.apiUrl, myObject.responseType, myObject.httpType)
Note that upon Naming Conventions, struct should named as CustomStruct and enum named as MyEnum.
In fact, I'm not pretty sure of the need of letting CustomStruct to be the parent of MyEnum to achieve what are you trying to; As mentioned above in the snippets, you can return an instance of the struct based on what is the value of the referred enum.
I'm not commenting on the choice to use an enum here, but just explaining why you got that error and how to declare an enum that has a custom object as parent.
The error shows you the problem, CustomStruct must implement RawRepresentable to be used as base class of that enum.
Here is a simplified example that shows you what you need to do:
struct CustomStruct : ExpressibleByIntegerLiteral, Equatable {
var rawValue: Int = 0
init(integerLiteral value: Int){
self.rawValue = value
}
static func == (lhs: CustomStruct, rhs: CustomStruct) -> Bool {
return
lhs.rawValue == rhs.rawValue
}
}
enum AllStructs: CustomStruct {
case ONE = 1
case TWO = 2
}
A few important things that we can see in this snippet:
The cases like ONE and TWO must be representable with a Swift literal, check this Swift 2 post for a list of available literals (int,string,array,dictionary,etc...). But please note that in Swift 3, the LiteralConvertible protocols are now called ExpressibleByXLiteral after the Big Swift Rename.
The requirement to implement RawRepresentable is covered implementing one of the Expressible protocols (init?(rawValue:) will leverage the initializer we wrote to support literals).
Enums must also be Equatable , so you'll have to implement the equality operator for your CustomStruct base type.
Did you try conforming to RawRepresentable like the error is asking?
Using JSON representation should work for variable1 and variable3. Some extra work may be required for variable2.
struct CustomStruct: RawRepresentable {
var variable1: String
var variable2: AnyClass
var variable3: Int
init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8) else {
return nil
}
guard let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
return nil
}
self.variable1 = (json["variable1"] as? String) ?? ""
self.variable2 = (json["variable2"] as? AnyClass) ?? AnyClass()
self.variable3 = (json["variable3"] as? Int) ?? 0
}
var rawValue: String {
let json = ["variable1": self.variable1,
"variable2": self.variable2,
"variable3": self.variable3
]
guard let data = try? JSONSerialization.data(withJSONObject: json, options: []) else {
return ""
}
return String(data: data, encoding: .utf8) ?? ""
}
}
According to the documentation:
If a value (known as a “raw” value) is provided for each enumeration case, the value can be a string, a character, or a value of any integer or floating-point type.
So yes, you cannot set a struct type to be enum's raw value.
In your case I would suggest using string as the enum raw value and some dictionary mapping these strings to CUSTOM_STRUCT type.
A bit late on the party but maybe useful for someone else. I would consider to simply use computed variables instead of a struct.
enum MyEnum {
case getData
case addNewData
var variable1: String {
switch self {
case .getData: return "data1"
case .addNewData: return "data2"
}
}
var variable2: Int {
switch self {
case .getData: return 1
case .addNewData: return 2
}
}
// ....
}
Usage:
let data = MyEnum.getData
print (data.variable1) // "data1"

execute function inside enum

I'm trying to execute a function inside an enum, but when execute this code ContentType.SaveContent("News") i keep getting following error: Use of instance member on type 'ContentType'; did you mean to use a value of type 'ContentType' instead?. Why wont it run when i've set the type to String?
enum ContentType: String {
case News = "News"
case Card = "CardStack"
func SaveContent(type: String) {
switch type {
case ContentType.News.rawValue:
print("news")
case ContentType.Card.rawValue:
print("card")
default:
break
}
}
}
I would probably do this instead of what you are trying to do :
in ContentType Enum a func :
func saveContent() {
switch self {
case .News:
print("news")
case .Card:
print("cards")
}
}
in the other part of code that will use your enum :
func saveContentInClass(type: String) {
guard let contentType = ContentType(rawValue: type) else {
return
}
contentType.saveContent()
}
It's not a static func, therefore you can only apply it to an instance of the type, not to the type itself, which is what you are trying to do. Add static before func.
...and, just for good style, don't give funcs capital letters...

How to extend the Swift Dictionary type to return a non-empty String or nil

I'm writing an extension to Dictionary so that when I give it a String key, it'll return me a String only if the value associated with the key is non-nil and not empty.
extension Dictionary {
subscript(key: String) -> String? {
if let string = super.subscript(key) {
if string.isEmpty == false {
return string
}
}
return nil
}
}
However, at the if let string = super.subscript(key) { line, I get the following compile error and I don't know what it means--neither is there a Google result that explains it:
Expected -> for subscript element type
I am doing this because I'm working with an API that returns a JSON where a key's value may be an empty string--which is an invalid value to the app by our requirements, and hence, as good as nil.
Of course the longer way works, but I'm looking for a way to make this shorter.
if let value = dict["key"] as? String {
if value.isEmpty == false {
// The value is non-nil and non-empty.
}
}
You're going to think this is very silly, but my suggestion would be: do more or less exactly what you're doing, but encapsulate it as a separate function rather than trying to deal with the implications of defining a new subscript:
extension Dictionary {
func nes(key:Key) -> String? {
var result : String? = nil
if let s = self[key] as? String {
if !s.isEmpty {
result = s
}
}
return result
}
}
(nes stands for "non-empty string".)
Now call it like d.nes("foo").

How to get a swift enum's associated value regardless of the enum case

I have an object FormField which has two properties: a String name, and a value which can accept any type--hence I've made it Any!. However, I've been told in a separate question to use an enum with associated values instead of Any!.
enum Value {
case Text(String!)
case CoreDataObject(NSManagedObject!)
}
class FormField {
var name: String
var value: Value?
// initializers...
}
This approach makes it awfully verbose to check for nullity however. If I wanted to display an alert view for all the missing fields in the form, I'll have to repeat a nil check for every case in a switch statement:
for field in self.fields {
if let value = field.value {
switch value {
case .Text(let text):
if text == nil {
missingFields.append(field.name)
}
case .CoreDataObject(let object):
if object == nil {
missingFields.append(field.name)
}
}
}
}
Is there a shorter way of accessing the enum's associated value, regardless of the type? If I make FormField.value an Any! the above code would be as easy as:
for field in self.fields {
if field.value == nil {
missingFields.append(field.name)
}
}
Define a method isMissing() inside the enum - write it once and only once. Then you get nearly exactly what you prefer:
for field in self.fields {
if field.value.isMissing() {
missingFields.append(field.name)
}
}
It would look something like this (from the Swift Interpreter):
1> class Foo {}
>
2> enum Value {
3. case One(Foo!)
4. case Two(Foo!)
5.
6. func isMissing () -> Bool {
7. switch self {
8. case let .One(foo): return foo == nil
9. case let .Two(foo): return foo == nil
10. }
11. }
12. }
13> let aVal = Value.One(nil)
aVal: Value = One {
One = nil
}
14> aVal.isMissing()
$R0: Bool = true
With Swift 2 it's possible to get the associated value using reflection.
To make that easier just add the code below to your project and extend your enum with the EVAssociated protocol.
public protocol EVAssociated {
}
public extension EVAssociated {
public var associated: (label:String, value: Any?) {
get {
let mirror = Mirror(reflecting: self)
if let associated = mirror.children.first {
return (associated.label!, associated.value)
}
print("WARNING: Enum option of \(self) does not have an associated value")
return ("\(self)", nil)
}
}
}
Then you can access the .asociated value with code like this:
class EVReflectionTests: XCTestCase {
func testEnumAssociatedValues() {
let parameters:[EVAssociated] = [usersParameters.number(19),
usersParameters.authors_only(false)]
let y = WordPressRequestConvertible.MeLikes("XX", Dictionary(associated: parameters))
// Now just extract the label and associated values from this enum
let label = y.associated.label
let (token, param) = y.associated.value as! (String, [String:Any]?)
XCTAssertEqual("MeLikes", label, "The label of the enum should be MeLikes")
XCTAssertEqual("XX", token, "The token associated value of the enum should be XX")
XCTAssertEqual(19, param?["number"] as? Int, "The number param associated value of the enum should be 19")
XCTAssertEqual(false, param?["authors_only"] as? Bool, "The authors_only param associated value of the enum should be false")
print("\(label) = {token = \(token), params = \(param)")
}
}
// See http://github.com/evermeer/EVWordPressAPI for a full functional usage of associated values
enum WordPressRequestConvertible: EVAssociated {
case Users(String, Dictionary<String, Any>?)
case Suggest(String, Dictionary<String, Any>?)
case Me(String, Dictionary<String, Any>?)
case MeLikes(String, Dictionary<String, Any>?)
case Shortcodes(String, Dictionary<String, Any>?)
}
public enum usersParameters: EVAssociated {
case context(String)
case http_envelope(Bool)
case pretty(Bool)
case meta(String)
case fields(String)
case callback(String)
case number(Int)
case offset(Int)
case order(String)
case order_by(String)
case authors_only(Bool)
case type(String)
}
The code above is now available as a cocoapod susbspec at
https://github.com/evermeer/Stuff#enum
It also has an other nice enum extension for enumerating all enum values.
If the associated values were of the same type for all enum cases the following approach could help.
enum Value {
case text(NSString!), two(NSString!), three(NSString!) // This could be any other type including AnyClass
}
// Emulating "fields" datastruct for demo purposes (as if we had struct with properties).
typealias Field = (en: Value, fieldName: String)
let fields: [Field] = [(.text(nil),"f1"), (.two(nil), "f2"), (.three("Hey"), "f3")] // this is analog of "fields"
let arrayOfFieldNamesWithEmptyEnums: [String] = fields.compactMap({
switch $0.en {
case let .text(foo), let .two(foo), let .three(foo): if foo == nil { return $0.fieldName } else { return nil }}
})
print("arrayOfFieldNamesWithEmptyEnums \(arrayOfFieldNamesWithEmptyEnums)")
Many other things can be obtained similarly.
let arrayOfEnumsWithoutValues: [Value] = fields.compactMap({
switch $0.en {
case let .text(foo), let .two(foo), let .three(foo): if foo == nil { return $0.en } else { return nil }}
})
print("arrayOfEnumsWithoutValues \(arrayOfEnumsWithoutValues)")
// just to check ourselves
if let index = arrayOfEnumsWithoutValues.index(where: { if case .two = $0 { return true }; return false }) {
print(".two found at index \(index)")
}

Resources