Reputation: 566
I have been struggling with this over and over again, so I think I'm missing something. I need to do math, make a setting, assign a value or any of a host of simple operations in reaction to some user action, such as the example shown here, and SwiftUI is wanting a View where I don't need a view. There's got to be a way around the ViewBuilder's rules. I kind of worked around this by creating an unnecessary view and executing the code I need inside the View's init(), but that seems terribly awkward.
import SwiftUI
struct ContentView: View
{
@State var showStuff = false
var body: some View
{
VStack
{
Toggle(isOn: $showStuff)
{
Text("Label")
}
if showStuff
{
UserDefaults.standard.set(true, forKey: "Something")
}
}
}
}
Upvotes: 13
Views: 7794
Reputation: 11416
struct ExecuteCode : View {
init( _ codeToExec: () -> () ) {
codeToExec()
}
var body: some View {
EmptyView()
}
}
usage:
HStack {
ExecuteCode {
print("SomeView1 was re-drawn!")
print("second print")
}
SomeView1()
}
( my first way is better - you're able to write only simple code here )
HStack {
// `let _ =` works inside of View!
let _ = print("SomeView1 was re-drawn!")
SomeView1()
}
( +- the same situation as in first way. Good enough solution. )
HStack {
let _ = { // look here. "let _ =" is required
print("SomeView1 was re-drawn!")
print("second print")
}() // look here. "()" is also required
SomeView1()
}
But other dev's possibly can not understand this code (especially if it is large), so first one is a little bit better.
Upvotes: 21
Reputation: 156
I think the best way to do this now is using onChange(of:perform:) modifier, for iOS14.0+
These simple executions (like do math, assign a value) are nothing but actions in swift terminology, which should be performed after touching any UI element because swiftUI is declarative. In your case, you can use this with any View type. Other similar options are .onAppear() or .onDisappear() (self-explanatory).
Surprisingly, apple documentation for these are actually good and elaborate.
Link - https://developer.apple.com/documentation/swiftui/view/onchange(of:perform:)
Upvotes: 0
Reputation: 11373
Views are actually so-called Function Builders, and the contents of the view body are used as arguments to to the buildBlock
function, as mentioned by @Asperi.
An alternative solution if you must run code inside this context is using a closure that returns the desired view:
VStack {
// ... some views ...
{ () -> Text in
// ... any code ...
return Text("some view") }()
// ... some views ...
}
Upvotes: 4
Reputation: 49580
In SwiftUI 2.0, there's a new ViewModifier onChange(of:perform:)
, that allows you to react to changes in values.
But you can create something similar to that with a neat trick (I forgot where I saw it, so unfortunately I can't leave proper attribution), by extending a Binding
with onChange
method:
extension Binding {
func onChange(perform action: @escaping (Value, Value) -> Void) -> Self {
.init(
get: { self.wrappedValue },
set: { newValue in
let oldValue = self.wrappedValue
DispatchQueue.main.async { action(newValue, oldValue) }
self.wrappedValue = newValue
})
}
}
You can use it like so:
Toggle(isOn: $showStuff.onChange(perform: { (new, old) in
if new {
UserDefaults.standard.set(true, forKey: "Something")
}
}))
Upvotes: 1
Reputation: 257493
You cannot do what you try to do, because actually every view block inside body
is a ViewBuidler.buildBlock
function arguments. Ie. you are in function arguments space. I hope you would not expect that expression like
foo(Toggle(), if showStuff { ... } )
would work (assuming foo
is func foo(args: View...)
. But this is what you try to do in body
.
So expressions in SwiftUI have to be out of ViewBuilder block (with some exceptions which ViewBuilder itself supports for views).
Here is a solution for your case:
SwiftUI 2.0
struct ContentView: View {
@AppStorage("Something") var showStuff = false
var body: some View {
VStack {
Toggle(isOn: $showStuff) {
Text("Label")
}
}
}
}
SwiftUI 1.0
Find in already solved SwiftUI toggle switches
Note: View.body
(excluding some action modifiers) is equivalent of UIView.draw(_ rect:)
... you don't store UserDefaults in draw(_ rect:), do you?
Upvotes: 0