Swiftui If Let Inside View

SwiftUI - alternative to if let with a conditional closure

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!")
}
}
}

SwiftUI if let inside View

Or you can create your own IfLet view builder:

import SwiftUI

struct IfLet<Value, Content, NilContent>: View where Content: View, NilContent: View {

let value: Value?
let contentBuilder: (Value) -> Content
let nilContentBuilder: () -> NilContent

init(_ optionalValue: Value?, @ViewBuilder whenPresent contentBuilder: @escaping (Value) -> Content, @ViewBuilder whenNil nilContentBuilder: @escaping () -> NilContent) {
self.value = optionalValue
self.contentBuilder = contentBuilder
self.nilContentBuilder = nilContentBuilder
}

var body: some View {
Group {
if value != nil {
contentBuilder(value!)
} else {
nilContentBuilder()
}
}
}
}

extension IfLet where NilContent == EmptyView {

init(_ optionalValue: Value?, @ViewBuilder whenPresent contentBuilder: @escaping (Value) -> Content) {
self.init(optionalValue, whenPresent: contentBuilder, whenNil: { EmptyView() })
}
}

Using this, you can now do the following:

var body: some View {
IfLet(pieceOfData) { realData in
// realData is no longer optional
}
}

Want to respond if your optional is nil?

var body: some View {
IfLet(pieceOfData, whenPresent: { realData in
// realData is no longer optional
DataView(realData)
}, whenNil: {
EmptyDataView()
})
}

How to treat if-let-else as a single view in SwiftUI?

Make it closure argument a view builder, like

extension View {
func statusBar<V: View>(@ViewBuilder statusBar: () -> V) -> some View {
self.modifier(StatusBarView(statusBar: statusBar))
}
}

the same can be done in init of modifier, but not required specifically for this case of usage.

Tested with Xcode 13.4 / iOS 15.5

Conditionally use view in SwiftUI

The simplest way to avoid using an extra container like HStack is to annotate your body property as @ViewBuilder, like this:

@ViewBuilder
var body: some View {
if user.isLoggedIn {
MainView()
} else {
LoginView()
}
}

SwiftUI conditionally rendering one view or another

When you initialize a View (let's say A) in the body of another View, what happens is that A is passed as an argument to some special functions generated by the compiler: this system of having implicit function calls in a context (in this case the body of this View) is called "function builders", and they can be customized to have different behaviors. The one used in SwiftUI is ViewBuilder: it "collects" all the Views that you make in the body and "merges" them in a single one (that's why the return type of body is some View).

ViewBuilder contains some tricks to handle language constructs like if statements by embedding logic like "show one view or the other" but, as of the current version of Swift (5.2), it doesn't support most other tools like 'if let, guard let, do catch'. Some of these will become available in the next Swift version.

One of the unsupported things is the ternary operator ?:. In your example, the first line works because you're returning the same value for both the true and the false branches, but in the second line you're returning Views of different types, resulting in an error. Note that the same logic, used in a ViewBuilder context (Group) works just fine:

Group {
if demoModel.isLoggedIn {
Text("Logged in")
} else {
LoginView()
}
}

And that's because ViewBuilder knows how to manage simple if statements.

SwiftUI when can i use if statement in result builder?

ToolbarContentBuilder does not allow if statements, it's designed to use ToolbarItemGroup, where you can use if like a regular ViewBuilder:

var body: some View {
NavigationView {
Text("Hello, world!")
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
if foo { Button("foo") {} }
}
}
}
}

Declare a temporary variable or constant inside a closure that returns some View (SwiftUI)

This is due to a limitation where the Swift compiler only tries to infer a closure's return type if it is a single expression. Closure's that are processed by a result builder, such as @ViewBuilder, are not subject to this limitation. Importantly, this limitation also doesn't affect functions (only closures).

I was able to make this work by moving the closure to a method inside the structure. Note: this is the same as @cluelessCoder's second solution, just excluding the @ViewBuilder attribute.

struct GameView: View {
@State private var cards = [
Card(value: 100),
Card(value: 20),
Card(value: 80),
]

var body: some View {
MyListView(items: cards, content: cardView)
}

func cardView(for card: Card) -> some View {
let label = label(for: card) // only called once, and can be reused.
return Text(label)
}

func label(for card: Card) -> String {
return "Card with value \(card.value)"
}
}

Thanks to @cluelessCoder. I would have never stumbled upon this discovery without their input and helpful answer.



Related Topics



Leave a reply



Submit