简体   繁体   English

无法更新/修改 SwiftUI 视图的 @state var

[英]Unable to update/modify SwiftUI View's @state var

I'm unable to update the ExampleView's message var even though I can see updateMessage() is being called.即使我可以看到正在调用updateMessage()我也无法更新 ExampleView 的message updateMessage() Here is my simplified/convoluted SwiftUI example of Playground code that isn't working.这是我的 Playground 代码的简化/复杂 SwiftUI 示例,但它不起作用。 The message var does not get updated when called in updateMessage() .message在调用时变种没有更新updateMessage() As a result, the UI Text() is not updated either.因此,UI Text()也不会更新。

Why is the @State var message not updating?为什么@State var message没有更新? What is the correct way to update it?更新它的正确方法是什么?

import SwiftUI
import PlaygroundSupport

struct ContentView: View {
    let coloredLabel = ExampleView()

    var body: some View {
        VStack {
            coloredLabel
                .foregroundColor(Color.red)
                .padding()
            Button(action: {
                self.coloredLabel.updateMessage()
            }) {
                Text("Press me")
            }

        }
    }
}

struct ExampleView: View {
    @State private var message: String = "Hello"

    var body: some View {
        Text(self.message)
    }

    func updateMessage() {
        print("updateMessage ran") // this prints
        self.message = "Updated"
    }
}

PlaygroundPage.current.setLiveView(ContentView())

You should only change State of a view Inside it's own body block.您应该只它自己的主体块更改视图的State If you need to change it from a parent view, you may want to pass the value to it from parent and make it Binding instead.如果您需要从父视图更改它,您可能希望将值从父视图传递给它并改为Binding

struct ContentView: View {
    @State private var message = "Hello"

    var body: some View {
        VStack {
            ExampleView(message: $message)
                .foregroundColor(Color.red)
                .padding()
            Button("Press me") {
                self.message = "Updated"
            }
        }
    }
}

struct ExampleView: View {
    @Binding var message: String

    var body: some View {
        Text(message)
    }
}

If you need to encapsulate messages inside the ExampleView , you can use a Bool (or an enum or etc) instead:如果您需要在ExampleView封装消息,您可以使用Bool (或enum等)代替:

struct ContentView: View {
    @State private var updated = false

    var body: some View {
        VStack {
            ExampleView(isUpdated: $updated)
                .foregroundColor(Color.red)
                .padding()
            Button("Press me") {
                self.updated = true
            }
        }
    }
}

struct ExampleView: View {
    @Binding var isUpdated: Bool
    private var message: String { isUpdated ? "Updated" : "Hello" }

    var body: some View {
        Text(message)
    }
}

actually, the variable does get updated, but your Content view doesn't get informed about it.实际上,该变量确实会更新,但您的 Content 视图并未获知它。 This is what happens:这是发生的事情:

  • ContentView gets called, it initializes coloredLabel with an ExampleView内容查看被调用,它与ExampleView初始化coloredLabel
  • you press the button in ContentView你按下 ContentView 中的按钮
  • self.coloredLabel.updateMessage() get's called self.coloredLabel.updateMessage() 被调用
  • the message is printed消息被打印
  • the variable self.coloredLabel.message is modified变量 self.coloredLabel.message 被修改
  • ContentView does not get redrawn, as it isn't notified about the change ContentView 不会被重绘,因为它没有收到有关更改的通知
  • more specifically, coloredLabel inside your Stack doesn't get updated更具体地说,您 Stack 中的colourLabel不会更新

now, you have different options: @State , @Binding and @PublishedObject, @ObservedObject .现在,您有不同的选择: @State@Binding@PublishedObject, @ObservedObject @State@Binding @PublishedObject, @ObservedObject You need one of these Publishers, so your view actually notices that it needs to do something.您需要这些发布者之一,因此您的视图实际上会注意到它需要做一些事情。

Either you draw a new ExampleView every time you press the button, in this case you can use a @State variable in ContentView :每次按下按钮时都绘制一个新的ExampleView ,在这种情况下,您可以在ContentView使用@State变量:

struct ContentView: View {
    @State private var string = "Hello"

    var body: some View {
        VStack {
            ExampleView(message: string)
                .foregroundColor(Color.red)
                .padding()
            Button(action: {
                self.string = "Updated"
            }) {
                Text("Press me")
            }

        }
    }
}

struct ExampleView: View {
    var message: String

    var body: some View {
        Text(self.message)
    }
}

which is probably not what you want.这可能不是你想要的。

Next, you can use @Binding which was already suggested.接下来,您可以使用已经建议的@Binding

And last, you can use ObservableObject @ObservedObject, @Published最后,你可以使用ObservableObject @ObservedObject, @Published

class ExampleState: ObservableObject {
    @Published var message: String = "Hello"
    func update() {
        message = "Updated"
    }
}

struct ContentView: View {
    @ObservedObject var state = ExampleState()

    var body: some View {
        VStack {
            ExampleView(state: state)
                .foregroundColor(Color.red)
                .padding()
            Button(action: {
                self.state.update()
            }) {
                Text("Press me")
            }

        }
    }
}

struct ExampleView: View {
    @ObservedObject var state: ExampleState

    var body: some View {
        Text(state.message)
    }
}

what this says is: class ExampleState: ObservableObject - this class has published variables that can be observed这说的是: class ExampleState: ObservableObject - 这个类已经发布了可以观察的变量

to resume (that's how I understand it):恢复(这就是我的理解):

  • "Hey, ContentView and ExampleView : if state.message (any value that state publishes) changes, you need to redraw your body" “嘿, ContentViewExampleView :如果state.messagestate发布的任何值)发生变化,你需要重绘你的身体”
  • "And ExampleState : after updating your message variable, publish the new value!" “和ExampleState :更新您的消息变量后,发布新值!”

lastly - for completion - there is @EnvironmentObject , as well, that way you'd only have to pass the variable to the top-views and everything down the view hierarchy would inherit it.最后 - 为了完成 - 还有@EnvironmentObject ,这样你只需要将变量传递给顶视图,视图层次结构中的所有内容都会继承它。

One of the solutions that should work, but as guys say, you can work with @Binding应该可行的解决方案之一,但正如人们所说,您可以使用@Binding

 struct ExampleView: View {

        var message: String = "Hello"

        var body: some View {
            Text(self.message)
        }

        mutating func updateMessage() {
            print("updateMessage ran")
            message = "Updated"
        }
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM