在 SwiftUI 中更新对象的绑定值

huangapple go评论60阅读模式
英文:

Updating bound value in object SwiftUI

问题

Swift初学者在这里。我正在尝试创建一个可重用的组件,它根据状态中存储的值简单地渲染一个填充或空的系统图像圆圈。我已经在下面提供了一个复制示例。现在它似乎只更新状态中的值(在formData中),但UI似乎没有更新(圆圈没有填充)。我有一种感觉,ImageText组件已经设置好了以对状态更改做出反应,但我不确定如何从那里修复。在此先行致谢!

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.

huangapple
  • 本文由 发表于 2023年7月7日 03:22:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/76631964.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定