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

13
我无法更新ExampleView的'message'变量,即使我可以看到updateMessage()被调用。以下是我的简化/混乱的SwiftUI Playground代码示例,它无法工作。当在updateMessage()中调用时,'message'变量不会被更新。因此,UI 'Text()'也不会被更新。

为什么@State变量'message'没有更新?正确的更新方式是什么?

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())

1
你只能在视图的 body 块内设置其 State。你正在 ContentView.body 中设置 ExampleView 的状态,这是不允许的。我认为你应该使用 @Binding - Sweeper
3个回答

13
实际上,变量确实被更新了,但您的“Content View”并没有得到相关通知。具体而言,下列步骤发生了什么: ContentView被调用,它使用ExampleView初始化了coloredLabel; 在ContentView中按下按钮; self.coloredLabel.updateMessage()被调用; 消息被打印; self.coloredLabel.message 变量被修改; 由于它没有收到通知,ContentView没有被重新绘制; 更具体地说,Stack中的coloredLabel没有被更新。
现在,您有不同的选项:@State、@Binding和@PublishedObject、@ObservedObject。您需要其中一种Publisher,以便您的视图实际上会注意到需要做些什么。要么在每次按下按钮时绘制新的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)
    }
}

这可能不是你想要的。

接下来,你可以使用已经建议的@Binding

最后,你可以使用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)
    }
}

这里的意思是:

class ExampleState: ObservableObject - 这个类拥有可以被观察到的已发布变量。

简单概括一下(就是我理解的):

  • "嘿,ContentViewExampleView:如果state.message(即state发布的任何值)发生变化,你们需要重新绘制自己的内容"
  • "并且当ExampleState更新它的message变量时,发布新值!"

最后,为了完整起见——还有@EnvironmentObject,这样你只需要将变量传递给顶层视图,所有下面的视图层次结构都会继承它。


12

您应仅在视图的自身主体块内更改 State。如果您需要从父视图更改它,则可能希望从父级传递该值并将其设置为 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)
    }
}

如果您需要在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)
    }
}

你是否只能使用Bool或枚举?我想使用Int,或者在示例中使用String。 - xta
不,你可以使用任何适合你的情况的东西。关键是通过来自父级的“binding”变量触发更改。 - Mojtaba Hosseini
好的,我用绑定示例成功了。我遇到的一个问题是在 View 体中使用 Text(self.message) 是不行的,必须使用 Text(message)。谢谢! - xta
我们为什么只能在body块内更改值时才使用@State? - mskw
谁说的? - Mojtaba Hosseini

1

其中一个解决方案应该是有效的,但是有人说你可以使用@Binding

 struct ExampleView: View {

        var message: String = "Hello"

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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接