import SwiftUI
struct MyView: View {
@State var toggle = false
let toggleBinding: Binding<Bool>
init() {
// error: escaping closure captures mutating 'self' parameter
self.toggleBinding = Binding<Bool>(get: { self.toggle }, set: { self.toggle = $0 })
}
var body: some View {
// I want to avoid making the binding here, do it once in init() instead
let toggleBindingIsOkayHere = Binding<Bool>(get: { self.toggle }, set: { self.toggle = $0 })
Text("Help")
}
}
As the error said, in the escaping closure, you're capturing and mutating self
(actually self.toggle
). This is not allowed.
Even if you can bypass that, you still have the problem of using self
before all of its variables are initialized (toggleBinding
specifically).
One way to achieve what you want would be to make it lazy:
lazy private(set) var toggleBinding = $toggle
PS.
Getting Binding
is very cheap. Is there any particular reason you're avoiding that?
No particular reason. Just want to know if I can avoid making the binding inside var body
, just want to learn if I can just make the binding once.
lazy private(set) var toggleBinding = $toggle
I actually need to observe the Binding.set()
and do something, so I have to customize Binding<Bool>
with my own define setter()...
lazy var toggleBinding = $toggle
compile, but not
lazy var toggleBinding = Binding<Bool>(get: { self.toggle }, set: { self.toggle = $0 })
same "error: escaping closure captures mutating 'self' parameter"
Why using $toggle
is okay? Doesn't it also need to capture self
in the generated code?
Even if you could get it to work, I think you would get an error for accessing a @State
variable outside the body of the view.
The binding is passed to something like a Toggle
. I don't believe it will have any access scope problem with whatever the Toggle
does.
$toggle
(probably) doesn't capture the container MyView
, it capture only the wrapper State<Bool>
.
You can do
lazy private(set) var toggleBinding = Binding<Bool>(get:
{ [_toggle] in
_toggle.wrappedValue
}, set: { [_toggle] in
_toggle.wrappedValue = $0
})
But since you plan on using it in body
which is immutable, you'll need to initialize it before body is called.
var toggleBinding: Binding<Bool>!
init() {
toggleBinding = Binding<Bool>(get:
{ [_toggle] in
_toggle.wrappedValue
}, set: { [_toggle] in
_toggle.wrappedValue = $0
})
}
Though as @suyashsrijan said, it could easily get you in a situation where SwiftUI may behave unpredictably. In fact, it causes my view not to be updated (even when I use $toggle
).
There is no way to make this work. self
simply does not have a persistent, unique identity for value types that could possibly be captured by an escaping closure. Even if you unwisely find a way to capture a pointer to the place in memory that the self
variable is bound to during some specific init
call, that value can be moved and/or copied around to different places in memory, immediately making that pointer stale. In fact, copying view values and otherwise taking advantage of the non-identity of self
is a significant part of how SwiftUI does some of the things it does.
Thank you for the explanation
SwiftUI is simply amazing, so well designed. Hopefully some explanation of how things work is forthcoming. Right now I only find out what's not allowed by making mistake, like mutating @State inside view body, it doesn't crash, but there is runtime warning...
I suggest to change the attribution of self.toggleBinding to a function with a Binding returning type. That worker for me.