英文:
Updating bound value in object SwiftUI
问题
Swift初学者在这里。我正在尝试创建一个可重用的组件,它根据状态中存储的值简单地渲染一个填充或空的系统图像圆圈。我已经在下面提供了一个复制示例。现在它似乎只更新状态中的值(在formData
中),但UI似乎没有更新(圆圈没有填充)。我有一种感觉,Image
和Text
组件已经设置好了以对状态更改做出反应,但我不确定如何从那里修复。在此先行致谢!
import SwiftUI
struct Test: View {
@ObservedObject var formData = FormData()
var body: some View {
VStack {
Text("Performance: \(String(formData.goals.performance))")
Toggle(isOn: Binding(
get: { formData.goals.performance },
set: { formData.goals.performance = $0 }
)) {
Text("Performance")
}
ChildWrapper(isPerformanceEnabled: $formData.goals.performance)
}
}
}
struct ChildWrapper: View {
@Binding var isPerformanceEnabled: Bool
var body: some View {
VStack {
Image(systemName: isPerformanceEnabled ? "circle.fill" : "circle")
.font(.system(size: 50))
.foregroundColor(isPerformanceEnabled ? .green : .red)
.onTapGesture {
isPerformanceEnabled.toggle()
}
Text(isPerformanceEnabled ? "Enabled" : "Disabled")
}
}
}
struct Test_Previews: PreviewProvider {
static var previews: some View {
Test()
}
}
英文:
Swift beginner here. I am trying to create a reusable component that simply renders either a filled or empty systemImage circle based on a value stored in state. I've put together a reproduction example below. Right now it only appears to be updating the value in state (in formData
), but the UI doesn't seem to be updating (the circle is not filling up). I have a feeling the Image
and Text
components are set up to react to state changes, but I'm not sure how to fix from there. Thank you in advance!
import SwiftUI
struct Test: View {
@ObservedObject var formData = FormData()
var body: some View {
VStack {
Text("Performance: \(String(formData.goals.performance))")
Toggle(isOn: Binding(
get: { formData.goals.performance },
set: { formData.goals.performance = $0 }
)) {
Text("Performance")
}
ChildWrapper(isPerformanceEnabled: $formData.goals.performance)
}
}
}
struct ChildWrapper: View {
@Binding var isPerformanceEnabled: Bool
var body: some View {
VStack {
Image(systemName: isPerformanceEnabled ? "circle.fill" : "circle")
.font(.system(size: 50))
.foregroundColor(isPerformanceEnabled ? .green : .red)
.onTapGesture {
isPerformanceEnabled.toggle()
}
Text(isPerformanceEnabled ? "Enabled" : "Disabled")
}
}
}
struct Test_Previews: PreviewProvider {
static var previews: some View {
Test()
}
}
</details>
# 答案1
**得分**: 4
问题出在你的模型(`FormData`)中。
让我们来看看这段代码:
> `@ObservedObject var formData = FormData()`
**1.** 这部分是不正确的。你需要使用 `@StateObject`。原因如下:
`@ObservedObject` 暗示着所有权不是本地的。例如,你可以在层次结构中的更高位置拥有一个对象,或者将一个单例对象传递给这个视图。
文档说明如下:
> 当输入是一个 ObservableObject 并且你希望视图在对象的已发布属性更改时更新时,请将 @ObservedObject 属性添加到 SwiftUI 视图的参数中。通常,您会将 StateObject 传递给子视图。
**2.** 无需传递自定义 Binding 到你的开关。只需传递:
`Toggle(isOn: $formData.goals.performance)`
**3.** 你的模型的典型实现可以看起来像这样:
```swift
class FormData: ObservableObject {
struct Goals {
var performance: Bool = false
}
@Published var goals = Goals()
}
请注意,Goals
是一个结构体,这意味着每当它的属性发生更改时,goals
将被重新分配,并且 FormData
将发出一个 objectWillChange()
,触发视图重新评估 body
。
额外信息:
这是一个许多人常常弄错的有趣事实...
ChildWrapper
获得了一个绑定,以便能够更改 isPerformanceEnabled
,这是正确的。
但是,究竟是什么导致了重新评估?
答案是,并不是绑定的更改,而是对 FormData
的更改(通过绑定)重新评估了父视图!实际上,如果绑定更改了某些不是祖先视图将要监视的东西,将不会发生重新评估。
就是这样。希望这一切都讲得清楚。
英文:
The problem is in your model (FormData
).
Let's examine the code:
> @ObservedObject var formData = FormData()
1. This bit is incorrect. You need @StateObject
. Here is why:
@ObservedObject
implies that ownership is not local. For example you could have an object higher up in the hierarchy or a singleton passed to this view.
The documentation states:
>Add the @ObservedObject attribute to a parameter of a SwiftUI View when the input is an ObservableObject and you want the view to update when the object’s published properties change. You typically do this to pass a StateObject into a subview.
2. There is no reason to pass a custom Binding to your toggle. Just pass:
Toggle(isOn: $formData.goals.performance)
3. A typical implementation of your model could look like this:
class FormData: ObservableObject {
struct Goals {
var performance: Bool = false
}
@Published var goals = Goals()
}
note here that Goals
is a struct, which means that whenever any of its properties change, goals
would be re-assigned and FormData
would emit an objectWillChange()
triggering your view to re-evaluate the body
.
Bonus Bit:
Here is an interesting fact that many people get wrong...
ChildWrapper
got a binding in order to be able to change isPerformanceEnabled
, which is correct.
But what is exactly causing the re-evaluation?
The answer is, not the change to the binding, but the change to FormData
(through the binding) that re-evaluated the parent view!
In fact, if the binding changed something that not an ancestor view would be watching, no re-evaluation would happen.
That's all. I hope that this makes sense.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论