protocol: MyProtocol {
var showView: Bool { get set }
}
struct MyView: View, MyProtocol {
var showView = false
var body: some View {
ZStack(){
AnotherView(myProtocol: self)
if showView {
// code
}
}
}
}
struct AnotherView: View {
var myProtocol: MyProtocol
var body: some View {
ZStack(){
Text("Text")
.onTapGesture{
myProtocol.showView = true // xcode complains on this line
}
}
}
}
I have this code above. I want when the Text
is tapped, the showView
variable changes value and then do whatever I want here if showView {}
This is not my actual code, it's just a minimal reproduction example, and I'm forced to use protocol
because AnotherView
is a reusable view to be used from multiple sides, so I want to change the value of showView
of only the current parent view
But XCode
keeps complaining, saying:
Cannot assign to property: 'self' is immutable
How can I change the value of the parent View's Protocol's property from the child view?
You should check that your “minimal reproduction example“ produces the error you describe. Yours does not, because of syntax errors in the declaration of MyProtocol
. The correct declaration is
protocol MyProtocol {
var showView: Bool { get set }
}
Correcting that results in the error you describe.
@vadian gives the correct answer in a comment. Mutable state in a SwiftUI view needs to be stored in some sort of DynamicProperty
, most commonly by using one of the property wrappers @State
, @Binding
, or @ObservedObject
.
But it is difficult to understand how to fix your example, because in your example, you have a MyView
that passes itself to AnotherView
and expects the AnotherView
to mutate the passed-in MyView
. That is not really how SwiftUI is designed to work. In SwiftUI, mutable state needs to be more cleanly separated from views.
Perhaps this captures your intended effect:
struct MyViewState: MyProtocol {
var showView = false
}
struct MyView: View {
@State var state: any MyProtocol = MyViewState()
var body: some View {
ZStack(){
AnotherView(myProtocol: $state)
if state.showView {
// code
}
}
}
}
struct AnotherView: View {
@Binding var myProtocol: any MyProtocol
var body: some View {
ZStack {
Text("Text")
.onTapGesture {
myProtocol.showView = true // xcode complains on this line
}
}
}
}
Note that, because AnotherView
expects a MyProtocol
existential, I had to explicitly declare state
to be an existential (any MyProtocol
) in MyView
. You can learn more about existentials by watching WWDC 2022: Embrace Swift generics and WWDC 2022: Design protocol interfaces in Swift.
It's probably better to avoid using existentials here and instead make AnotherView
generic, like this:
struct MyView: View {
@State var state = MyViewState()
var body: some View {
ZStack(){
AnotherView(myProtocol: $state)
if state.showView {
// code
}
}
}
}
struct AnotherView<My: MyProtocol>: View {
@Binding var myProtocol: My
var body: some View {
ZStack {
Text("Text")
.onTapGesture {
myProtocol.showView = true // xcode complains on this line
}
}
}
}