Match optional in switch statement - swift

Given a non-optional value, how can I match against an optional in a switch statement?
For example:
let test = "some string"
let x: String? = nil
let y: String? = "some string"
let z: String? = "another string"
switch test {
case x:
print(x)
case y:
print(y)
case z:
print(z)
default: break
}
results in:
Expression pattern of type 'String?' cannot match values of type 'String'
for each case...
I've read the swift docs on patterns and switch but I can't seem to find a way of making this work.
I know I can work around this, but there must be a way of making this work...
Edit
As requested, here is my actual use case. Note all text fields are optional...
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
// TODO: Replace with switch if possible
// switch textField {
// case nameTextField:
// usernameTextField?.becomeFirstResponder()
// case usernameTextField:
// dateOfBirthTextField?.becomeFirstResponder()
// case dateOfBirthTextField:
// phoneTextField?.becomeFirstResponder()
// case phoneTextField:
// phoneTextField?.resignFirstResponder()
// default: break
// }
if textField == nameTextField {
usernameTextField?.becomeFirstResponder()
} else if textField == usernameTextField {
dateOfBirthTextField?.becomeFirstResponder()
} else if textField == dateOfBirthTextField {
phoneTextField?.becomeFirstResponder()
} else if textField == phoneTextField {
phoneTextField?.resignFirstResponder()
}
return false
}

Swift 4.0
You can check with optional case as like below.
Hope this will help you.
let test = "some string"
let x: String? = nil
let y: String? = "some string"
let z: String? = "another string"
switch test {
case let val where val == x:
print(x)
case let val where val == y:
print(y)
case let val where val == z:
print(z)
default:
break
}

For your actual use-case: I would create an array of all non-nil
text fields. Then you can lookup the current text field in that
array and make the next one in the list the first responder:
let fields = [nameTextField, usernameTextField, dateOfBirthTextField, phoneTextField]
.flatMap { $0 }
if let idx = fields.index(of: textField), idx + 1 < fields.count {
fields[idx + 1].becomeFirstResponder()
} else {
textField.resignFirstResponder()
}
This is less error-prone and can easily be modified if the number or
order of the text fields changes.

Related

Given a Swift `Any` type can I determine if it's an `Optional`?

Given a value of type Any is it possible to check and see if it's an Optional or not?
This code doesn't work because instead of checking to see if it's optional or not it's trying to cast it, and it passes
let a: Any = "5"
switch a {
case let optional as Optional<Any>:
if case .some(let value) = optional {
print("wrapped value of `\(a)` is `\(value)`")
}
default:
print("\(a) is not an optional")
}
Base on #dfri's solution
private func isOptional(input: Any) -> Bool {
let mirror = Mirror(reflecting: input)
let style = mirror.displayStyle
switch style {
case .some(.optional):
return true
default:
return false
}
}
You can use runtime introspection using Mirror:
let foo: String? = "foo"
let bar: String = "bar"
var a: Any = foo
// if wrapping an optional, the reflection of the value has
// a displaystyle "optional"
if let displayStyle = Mirror.init(reflecting: a).displayStyle {
print(displayStyle) // optional
}
// for a non-optional fundamental native type: no displaystyle
a = bar
if let displayStyle = Mirror.init(reflecting: a).displayStyle {
print(displayStyle)
} // prints nothing
Optional/non-optional example where the underlying type is user-defined (non native):
struct Foo {}
let foo: Foo? = Foo()
let bar: Foo = Foo()
var a: Any = foo
// if wrapping an optional, the reflection of the value has
// a displaystyle "optional"
if let displayStyle = Mirror(reflecting: a).displayStyle {
print(displayStyle) // optional
}
// for a non-optional non-fundamental type:
a = bar
if let displayStyle = Mirror(reflecting: a).displayStyle {
print(displayStyle) // struct
}
If you don't want need to use the binded displayStyle variable (e.g. for printing) but simply want check whether the wrapped value is any kind of optional, you can add a boolean clause to the if statement that holds the optional binding of the displayStyle case,
if let displayStyle = Mirror(reflecting: a).displayStyle,
displayStyle == .optional {
// is an optional ...
}
... or remove the binding entirely in favour of a single conditional expression using the nil coalescing operator (??)
if Mirror(reflecting: a).displayStyle ?? .class == .optional {
// is an optional
}
Note however that for all the methods above, this simply tells you as dev whether the type wrapped by the Any instance is optional or not: Swifts typing system still knows nothing of the sort.
let a: Any = "5"
let b: Any? = "5"
if type(of: a) == Optional<Any>.self {
print("a is optional")
} else {
print("a is not optional")
}
if type(of: b) == Optional<Any>.self {
print("b is optional")
} else {
print("b is not optional")
}
/*
a is not optional
b is optional
*/
another example ...
let a: Any = 5
let b: Any? = 5
let c: Any = "5"
let d: Any? = "5"
let arr: [Any] = [a,b as Any,c,d as Any]
arr.forEach { (x) in
print(type(of: x))
}
/*
Int
Optional<Any>
String
Optional<Any>
*/

Pattern match and conditionally bind in a single Switch statement

Is there a way to write this if/else if/else ladder as a switch statement?
let x: Any = "123"
if let s = x as? String {
useString(s)
}
else if let i = x as? Int {
useInt(i)
}
else if let b = x as? Bool {
useBool(b)
}
else {
fatalError()
}
Here's my attempt:
switch x {
case let s where s is String: useString(s)
case let i where i is Int: useInt(i)
case let b where b is Bool: useBool(b)
default: fatalError()
}
It successfully chooses the right path, but s/i/b are still of type Any. The is check doesn't have any effect in casting them. This forces me to force cast with as! before usage.
Is there a way to switch on the type, and bind it to a name, all in one switch statement?
Sure, you can use the conditional casting pattern case let x as Type:
let x: Any = "123"
switch x {
case let s as String:
print(s) //use s
case let i as Int:
print(i) //use i
case let b as Bool:
print(b) //use b
default:
fatalError()
}

Satisfy one of multiple constraints in an if let construct

I want to satisfy multiple constraints in an if let construct. I know we can use a "," (comma) to unwrap multiple values but they both have to be successfully unwrapped.
For example :
var str: String? = "Hello"
var x: Int? = 10
if let intValue = x, stringValue = str {
// do something here.
} else {
}
I want if one of the conditions is successfully unwrapped, then a block will execute.
for example:
class CustomClass {
var x = 10
static func someValue() -> String? {
return "some"
}
}
var flag: Bool? = false
var x: Int? = 10
var status: String
in this i want if either customclass someValue function or x value any of successfully unwrapped and flag is true then code executes
You can create a tuple and use a switch like so:
switch (str, x) {
case (.Some,.Some):
print("Both have values")
case (.Some, nil):
print("String has a value")
case (nil, .Some):
print("Int has a value")
case (nil, nil):
print("Neither has a value")
}

Is there also a shortcut to prevent result from being nil or empty in Swift?

I can use the following expression to prevent a variable from being nil:
let result: String = someTextField.text ?? ""
Is there also a shortcut to prevent result from being nil or empty in Swift?
You can try to check it using the .isEmpty method or else use a default value
let result = (someTextField.text ?? "").isEmpty ? "default" : string
?? is called Nil Coalescing Operator
The nil coalescing operator (a ?? b) unwraps an optional a if it contains a value, or returns a default value b if a is nil. The expression a is always of an optional type. The expression b must match the type that is stored inside a.
According to you question, ?? just is the shortcut of preventing result from being nil.
And there is no shortcut to preventing result from being both nil and empty. As empty is type string, it's different from nil.
Here is the solution:
let result: String =
someTextField.text?.isEmpty() != false ? "is null or empty" : someTextField.text!
Yes you can do it with ?? and ? but if you want you can define your own short cut.
// So we can apply to any type by extending the type to conform to the protocol
protocol CanBeEmpty
{
var isEmpty: Bool { get }
}
extension String: CanBeEmpty
{
// String doesn't need the var cos it already has it
}
extension Optional where Wrapped: CanBeEmpty
{
func unwrappedOrDefault(def: Wrapped) -> Wrapped
{
switch self
{
case .None:
return def
case .Some(let wrapped):
return wrapped.isEmpty ? def : wrapped
}
}
}
let foo: String? = "foo"
let fooUnwrapped = foo.unwrappedOrDefault("default") // "foo"
let bar : String? = ""
let barUnwrapped = bar.unwrappedOrDefault("default") // "default"
let baz: String? = nil
let bazUnwrapped = baz.unwrappedOrDefault("default") // "default"
If you think that is too much typing, create an operator
infix operator ??? {}
func ???<T: CanBeEmpty> (a: Optional<T>, b: T) -> T
{
return a.unwrappedOrDefault(b)
}
foo ??? "default" // "foo"
bar ??? "default" // "default"
baz ??? "default" // "default"

Nicer syntax for ternary with a let?

Is there a nicer way to do the assignment to DEF in the following example? I want to convert type A to Type B, but still preserve the nil possibility whenever I can.
Can't seem to stumble into a better way of doing this, however. Suggestions?
class ABC {
var DEF: Int?
func X (someValue: Int8?) {
DEF = someValue != nil ? Int(someValue) : nil
}
}
Swift 1:
class ABC {
var DEF: Int?
func X (someValue: Int8?) {
DEF = someValue.map{Int($0)}
}
}
Swift 2:
class ABC {
var DEF: Int?
func X (someValue: Int8?) {
DEF = someValue.map(Int.init)
}
}
map() takes an optional, unwraps it, and applies a function to it. If the optional resolves to nil, map() returns nil.
You are describing optional map:
var i: Int? = 2
let j = i.map { $0 * 2 } // j = .Some(4)
i = nil
let k = i.map { $0 * 2 } // k = nil
Think of this map like array or other collection map, where optionals are collections that have either zero (nil) or one (non-nil) element.
Note, if the operation you want to perform itself returns an optional, you need flatMap to avoid getting a double-optional:
let s: String? = "2"
let i = s.map { Int($0) } // i will be an Int??
let j = s.flatMap { Int($0) } // flattens to Int?