Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI - alternative to if let with a conditional closure

I'm trying to implement the following in SwiftUI:

struct PersonView: View {

    @State private var age: Int? = 0

    var body: some View {
        VStack {
            Text("Just a test")
            if let self.age > 0 {
                Text("Display Age: \(age)")
            } else {
                Text("Age must be greater than 0!")
            }
        }
    }
}

But, in SwiftUI, if let results in the following error:

Closure containing control flow statement cannot be used with function builder 'ViewBuilder'

So after researching this topic, I came across a recommendation to use .map to unwrap the age optional. Thus, I've modified to code within the VStack as follows:

Text("Just a test")
self.age.map {elem in
    if elem > 0 {
        Text("Display Age: \(elem)")
    } else {
        Text("Age must be greater than 0!")
    }
}

Including a conditional within the .map closure, however, results in the following errors at the line calling the VStack:

' (ViewBuilder.Type) -> (C0, C1) -> TupleView<(C0, C1)>' requires that '()' conform to 'View'

Type '()' does not conform to protocol 'View'

Any suggestions for how to get past the 2nd set of errors? Or, is there another approach for unwrapping optionals and evaluating them in SwiftUI? Really like SwiftUI but can't believe that unwrapping optionals has been a headache!

like image 591
Vee Avatar asked Dec 28 '19 06:12

Vee


People also ask

What does SwiftUI do with IF statements?

What SwiftUI will do is take the if statement and produce optional views that are nil or not nil depending on the conditional logic.

What are closures in Swift?

Closures Closures are self-contained blocks of functionality that can be passed around and used in your code. Closures in Swift are similar to blocks in C and Objective-C and to lambdas in other programming languages. Closures can capture and store references to any constants and variables from the context in which they’re defined.

How do I apply different configurations to a view in SwiftUI?

Whether it’s a simple checkbox value or an OS availability check, there are many cases in which you want to apply different configurations to your views. A View Modifier in SwiftUI modifies the View with given configurations. An example is applying a background color to a View.

What are some familiar SwiftUI syntax like foreach and if?

Tuesday. August 27, 2019 Looking for the tl;dr? Skip down to the Workarounds section below. In working with the SwiftUI domain specific language (DSL), you will see some familiar syntax like forEach and if <boolean condition> { }.


Video Answer


3 Answers

Swift 5.3 (Xcode 12)

Now you can use conditional binding right in the view builder:

if let age = age {
    if age > 0 {
        Text("Display Age: \(age)")
    } else {
        Text("Age must be greater than 0!")
    }
} else {
    Text("Age not found")
}

Refactor (Workes in older Swifts too)

You can refactor your code to something more basic like using a function:

var body: some View {
    VStack {
        Text("Just a test")
        Text(text(age: age)) // Using the function
    }
}

func text(age: Int?) -> String { // Defining the function
    guard let age = age else { return "Age not found" }
    if age > 0 { return "Display Age: \(age)" }
    else { return "Age must be greater than 0!" }
}

In general, use functions where you need to cleanup your code. I hope future versions of Swift will support this directly as we expect.

like image 177
Mojtaba Hosseini Avatar answered Oct 21 '22 14:10

Mojtaba Hosseini


For such cases I prefer the following approach

struct PersonView: View {

    @State private var age: Int? = 0

    var body: some View {
        VStack {
            Text("Just a test")
            AgeText
        }
    }

    private var AgeText: some View {
        if let age = self.age, age > 0 {
            return Text("Display Age: \(age)")
        } else {
            return Text("Age must be greater than 0!")
        }
    }
}
like image 39
Asperi Avatar answered Oct 21 '22 14:10

Asperi


You are trying to do two check on the value of age: first you are making sure it is not nil and then checking that it is greater than 0. You can use map to get rid of potential nil and then a ternary operator to conditionally change the text displayed:

var body: some View {
    VStack {
        Text("Just a test")
        age.map { Text( $0 > 0 ? "Display Age: \($0)" : "Age must be greater than 0!") }
    }
}
like image 6
LuLuGaGa Avatar answered Oct 21 '22 13:10

LuLuGaGa