how to use `if case` statement as boolean in swift - ios

I have this enum (with associated type)
enum CookieType {
case regular(type: Int)
case gem(type: GemType)
}
struct Cookie {
let type: CookieType
let otherStuff...
}
Now if i want to do pattern matching, i can do this with no problem:
if case .gem == cookie.type {
}
However, I want to use case .gem == cookie.type as a boolean. The following gives error
var cookies: [Cookie] {
return [cookieA, cookieB]
}
var gems: [Cookie] {
return cookies.filter { case $0.type == .gem } // this has error
}
which means case $0.type == .gem is not a boolean. How can I deal with this?

case .gem = cookie.type is not a boolean expression, and if statements not only accept boolean expressions. However, in the closure argument for filter, you must write an Bool expression or a block that returns Bool.
One way to do that is:
cookies.filter {
if case $0.type = .gem { return true }
else { return false }
}
Or, you can add convenient properties to CookieType that gives you Bool values, if you tend to do this a lot:
enum CookieType {
case regular(type: Int)
case gem(type: GemType)
var isRegular: Bool {
if case .regular = self { return true }
else { return false }
}
var isGem: Bool {
if case .gem = self { return true }
else { return false }
}
}
Then you can do:
cookies.filter(\.isGem)

Normally, if case and switch statements are abstractions based on the pattern matching operator ~=, but that's not how enums with associated types are implemented. As such, for the time being, you need to reverse-engineer how it might have been done, to allow this:
cookies.filter { CookieType.regular ~= $0.type }
I do not believe it is possible to avoid the explicit CookieType there, but it still reads better than all alternatives.
/// Match `enum` cases with associated values, while disregarding the values themselves.
/// - Parameter case: Looks like `Enum.case`.
public func ~= <Enum, AssociatedValue>(
case: (AssociatedValue) -> Enum,
instance: Enum
) -> Bool {
Mirror.associatedValue(of: instance, ifCase: `case`) != nil
}
public extension Mirror {
/// Get an `enum` case's `associatedValue`.
static func associatedValue<AssociatedValue>(
of subject: Any,
_: AssociatedValue.Type = AssociatedValue.self
) -> AssociatedValue? {
guard let childValue = Self(reflecting: subject).children.first?.value
else { return nil }
if let associatedValue = childValue as? AssociatedValue {
return associatedValue
}
let labeledAssociatedValue = Self(reflecting: childValue).children.first
return labeledAssociatedValue?.value as? AssociatedValue
}
/// Get an `enum` case's `associatedValue`.
/// - Parameter case: Looks like `Enum.case`.
static func associatedValue<Enum, AssociatedValue>(
of instance: Enum,
ifCase case: (AssociatedValue) throws -> Enum
) rethrows -> AssociatedValue? {
try associatedValue(of: instance).filter {
.equate(try `case`($0), to: instance) {
Self(reflecting: $0).children.first?.label
}
}
}
}
public extension Optional {
/// Transform `.some` into `.none`, if a condition fails.
/// - Parameters:
/// - isSome: The condition that will result in `nil`, when evaluated to `false`.
func filter(_ isSome: (Wrapped) throws -> Bool) rethrows -> Self {
try flatMap { try isSome($0) ? $0 : nil }
}
}
public extension Equatable {
/// Equate two values using a closure.
static func equate<Wrapped, Equatable: Swift.Equatable>(
_ optional0: Wrapped?, to optional1: Wrapped?,
using transform: (Wrapped) throws -> Equatable
) rethrows -> Bool {
try optional0.map(transform) == optional1.map(transform)
}
}

Inspired by #Jessy and SwiftLee, here is my solution:
// -----------------------
// CaseReflectable
// -----------------------
// designed for enums only
// (use it on other types not recommended)
protocol CaseReflectable {}
// default behaviors.
extension CaseReflectable {
/// case name
var caseName: String {
let mirror = Mirror(reflecting: self)
// enum cases:
// - normal case: no children
// - case with associated values: one child (with label)
guard let label = mirror.children.first?.label else {
return "\(self)" // normal case
}
// case with associated values
return label
}
/// associated values
var associatedValues: Any? {
// if no children, a normal case, no associated values.
guard let firstChild = Mirror(reflecting: self).children.first else {
return nil
}
return firstChild.value
}
}
// --------------------------
// custom operator ~=
// --------------------------
/// match enum cases with associated values, while disregarding the values themselves.
/// usage: `Enum.enumCase ~= instance`
func ~= <Enum: CaseReflectable, AssociatedValue>(
// an enum case (with associated values)
enumCase: (AssociatedValue) -> Enum, // enum case as function
// an instance of Enum
instance: Enum
) -> Bool
{
// if no associated values, `instance` can't be of `enumCase`
guard let values = instance.associatedValues else { return false }
// if associated values not of the same type, return false
guard values is AssociatedValue else { return false }
// create an instance from `enumCase` (as function)
let case2 = enumCase(values as! AssociatedValue)
// if same case name, return true
return case2.caseName == instance.caseName
}
// ------------
// Enum
// ------------
// enum with associated values
// (conforms to `CaseReflectable`)
enum Enum: CaseReflectable {
case int(Int)
case int2(Int)
case person(name: String, age: Int)
case str(String)
}
// ------------
// main
// ------------
let a: Enum = .int(3)
Enum.int ~= a // true
Enum.int2 ~= a // false
let joe = Enum.person(name: "joe", age: 8)
Enum.person ~= joe // true
Enum.int ~= joe // false
// array of enum cases
let items: [Enum] = [
.int(1), .str("hi"), .int(2)
]
// filter enum cases
let filtered = items.filter { Enum.int ~= $0 }
print(filtered) // [Enum.int(1), Enum.int(2)]

Related

Role of underscore "_" in conditional statement (if else) in Swift

Can somebody please explain the role of "_" in Swift code below.
var guessWasMade: Bool {
if let _ = game.guesses[currentQuestion] {
return true
} else {
return false
}
}
I understand how it is usually used as unnamed parameter in for-loops and functions.
But could not find any explanation for if else statement.
Tutorial explains it as guessWasMade checks game.guesses for a value. If a value is found we know the user has made a guess at the question.
game.guesses is an array of integers.
This is part of class declaration. Full code looks like this
class GameViewModel: ObservableObject {
// MARK: - Published properties
// 2
#Published private var game = Game()
// MARK: - Internal properties
// 3
var currentQuestion: Question {
game.currentQuestion
}
// 4
var questionProgressText: String {
"\(game.currentQuestionIndex + 1) / \(game.numberOfQuestions)"
}
// 1
var guessWasMade: Bool {
if let _ = game.guesses[currentQuestion] {
return true
} else {
return false
}
}
// MARK: - Internal Methods
// 2
func makeGuess(atIndex index: Int) {
game.makeGuessForCurrentQuestion(atIndex: index)
}
// 3
func displayNextScreen() {
game.updateGameStatus()
}
}
If let statements check to see if the value is nil. If it is, then the statement is false. If it isn't, then the value is assigned to a variable.
var optionalValue: Int? = 42
if let nonOptionalValue = optionalValue {
print(nonOptionalValue) // -> 42
} else {
print("optionalValue is nil")
}
So, when you do if let _ = game.guesses[...], you are checking to see if game.guesses[...] is nil. If it isn't, then you are ignoring the value with a wildcard pattern (_), which will match anything.
Because you are ignoring the value, it is the same as saying
if game.guesses[...] != nil {
return true
} else {
return false
}
Then, because you are simply returning the value of the condition, you can just return the condition itself:
var guessWasMade: Bool {
game.guesses[currentQuestion] != nil
}

How to return to the parent function in a nested functions inclosure that also has a return type in Swift

I realized when I created the question title that this question is difficult to properly convey to someone. So let me explain please.
Basically I have a function that requires a return statement in this case it's a Bool. Now inside of this function. I have another function that requires a separate return as well. I want to be able to call the return statement for the main/parent function inside of the Nest one.
Code:
func ParentFunction() -> Bool {
// This function represents a real function where you have a to have a return type.
// So in other word you can not take off the fact that you have to have a -> String.
func MyNestedFunction() -> String {
// Here is where you would return the nested functions statement
// but I'd rather not return the string and just end everything and return the Bool from the parent function
return "Hello"
// This is the parent functions return here I want to be able to stop any other process that the ParentFunction might do and just return here.
return true
}
// This is the parent functions return as well.
return true
}
** More of the actual Code here:**
func MyFunction() -> Bool {
tagger.enumerateTags(in: tagger.string!.startIndex..<tagger.string!.endIndex, unit: .paragraph, scheme: .nameTypeOrLexicalClass) { (tagResult, tokenRange) -> Bool in
if let tag = tagResult, tag == "nil" {
print("Found")
}
return true
}
// I would like it If we have already printed found in the function above then don't continue on to this function below
tagger.enumerateTags(in: tagger.string!.startIndex..<tagger.string!.endIndex, unit: .word, scheme: .nameTypeOrLexicalClass) { (tagResult, tokenRange) -> Bool in
if tagResult != nil {
print("Found")
}
return true
}
return true
}
I don't know the details of your enumerateTags function, but since it has a completion handler you should add one to your function, and call it from the completion handler from the enumerateTags function.
func MyFunction(completion: #escaping (Bool) -> Void) {
tagger.enumerateTags(in: tagger.string!.startIndex..<tagger.string!.endIndex, unit: .paragraph, scheme: .nameTypeOrLexicalClass) { (tagResult, tokenRange) -> Bool in
if let tag = tagResult, tag == "nil" {
print("Found")
completion(true)
}
return true
}
// I would like it If we have already printed found in the function above then don't continue on to this function below
tagger.enumerateTags(in: tagger.string!.startIndex..<tagger.string!.endIndex, unit: .word, scheme: .nameTypeOrLexicalClass) { (tagResult, tokenRange) -> Bool in
if tagResult != nil {
print("Found")
completion(true)
}
return true
}
}

iOS Swift: Array of Range

I have a Player class that stores a rating property of type Int:
class Player {
typealias Rating: Int
var rating: Rating = 0
}
I then have various Range instances that specify the level that a given player is at:
private let level1Range = 0 ..< 100
private let level2Range = 100 ..< 500
I can then switch on the player rating property to obtain the level that the player is at:
switch rating {
case level1Range:
print("On Level 1")
case level2Range:
print("On Level 2")
default:
break
}
I want to be able to say what the next level is and how far away the player is from that next level.
I'm not sure of the best way to go out this problem. I started by making an array:
private var ratingRanges: [Range] {
return [level1Range, level2Range]
}
But I get the error:
Reference to generic type 'Range' requires arguments in <...>
Insert '<<#Bound: Comparable#>>'
If this worked, I guess I could then find the first non-zero value:
ratingRanges.first(where: { $0.min() - self.rating > 0 })
in order to locate the next range.
Or is there a more efficient method to achieve this?
Thanks for any help
You need to provide the generic placeholder type of the Range:
private var ratingRanges: [Range<Rating>] {
return [level1Range, level2Range]
}
Or simpler, with automatic type inference, as a (lazy) stored property:
private lazy var ratingRanges = [level1Range, level2Range]
Determining the next range can then be done as
func nextRange() -> Range<Rating>? {
return ratingRanges.first(where: { $0.lowerBound > rating})
}
My solution is to create Level enum:
enum Level: Int {
case level1 = 1
case level2
init?(rating: Int) {
switch rating {
case Level.level1.range:
self = .level1
case Level.level2.range:
self = .level2
default:
return nil
}
}
var range: CountableRange<Int> {
switch self {
case .level1:
return level1Range
case .level2:
return level2Range
}
}
}
And then all you need to do is to add following methods to your Player class:
func nextLevel() -> Level? {
guard let currentLevel = Level(rating: rating) else {
return nil
}
guard let nextLevel = Level(rawValue: currentLevel.rawValue + 1) else {
return nil
}
return nextLevel
}
func distanceTo(level: Level) -> Int {
let levelLowerBound = level.range.lowerBound
return levelLowerBound - rating
}
May you should to keep the maximum value of range only. For example, instead of
private let level1Range = 0 ..< 100
private let level2Range = 100 ..< 500
you can use
private let level1MaxRating = 100
private let level2MaxRating = 500
and compare with
switch rating {
case 0...level1MaxRating:
print("level 1")
case (level1MaxRating+1)...level2MaxRating:
print("level 2")
}

Assign list of variadic parameters to variable

This is the case:
public enum NodeFeature: UInt16 {
case relay = 0x01
case proxy = 0x02
case friend = 0x04
case lpn = 0x08
}
public struct NodeFeatures {
public let rawValue: UInt16
public init(rawValue: UInt16) {
self.rawValue = rawValue
}
public init(features: NodeFeature...) {
var rawValue = UInt16(0)
for feature in features {
rawValue |= feature.rawValue
}
self.rawValue = rawValue
}
public func hasFeature(_ feature: NodeFeature) -> Bool {
return rawValue & feature.rawValue > 0
}
}
And this is a response from server:
"feature": {
"relay": true,
"proxy": false,
"friend": false,
"lowPower": false
}
Now I need to create an instance of NodeFeatures with only true values:
var features = [NodeFeature]() // how to declare and prepare the list of args?
if true {
features.append(.first)
} else if true {
features.append(.third)
}
let wrapper = NodeFeatures(features: features) //... to pass it as a variable to the initializer.
But the error is following:
Cannot convert value of type '[NodeFeature]' to expected argument type 'NodeFeatures'
You cannot pass an array to a function taking a variadic argument,
or dynamically "build" a variadic argument list, compare e.g.
Passing an array to a function with variable number of args in Swift.
But fortunately, the type has another initializer
public init(rawValue: UInt16)
which you can use in different ways.
Option 1: Use an integer bit mask to assemble the features
instead of an array:
var rawFeatures = UInt16(0)
if condition {
rawFeatures |= NodeFeature.relay.rawValue
} else if condition {
rawFeatures |= NodeFeature.proxy.rawValue
}
let wrapper = NodeFeatures(rawValue: rawFeatures)
Option 2: Keep your array, but compute the combined raw value
to create the NodeFeatures value:
var features = [NodeFeature]()
if condition {
features.append(.relay)
} else if condition {
features.append(.proxy)
}
let rawFeatures = features.reduce(0, { $0 | $1.rawValue })
let wrapper = NodeFeatures(rawValue: rawFeatures)
Option 3: Define another initializer taking an array argument
in an extension:
extension NodeFeatures {
public init(features: [NodeFeature]) {
let rawValue = features.reduce(0, { $0 | $1.rawValue })
self.init(rawValue: rawValue)
}
}
Now you can pass your array directly:
var features = [NodeFeature]()
if condition {
features.append(.relay)
} else if condition {
features.append(.proxy)
}
let wrapper = NodeFeatures(features: features)

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