好吧,我在搜索中找不到这个确切的问题,所以如果我忽略了搜索深处的一些东西,请原谅我。
前提: 现有系统。添加编辑器。数据在 Swift Structs 中传递,通过现有的 REST API 内容提取和更新。我对上游的东西没有太多的控制权。或者更准确地说,我目前没有时间重新架构上游的任何内容(在最近继承的项目上进行单独开发)。
问题: 如前所述,数据在结构中被推送。这是一个(简化的)示例:
struct Person: Codable, Identifiable {
let id: Int
let firstname: String
let lastname: String
}
这些在视图中显示和可选择,用户可以点击其中一个来选择它,并(大概)显示编辑器。导航到编辑器是最终模型,但在本例中,为了简单起见,我只是将其推入单一/主视图中。
我的编辑器接收 Person,将值复制到 @State 变量中以进行显示和编辑,然后当用户单击“保存”时,更新将被推送到 REST 服务器,然后编辑器被关闭,并且结构在读取时被替换后退/刷新等。非常正常的工作流程。
注意:本示例中没有保存按钮和 API 代码,以保持简洁。
但是,在我的编辑器中,我从传入的 Person 结构中复制值,并且在视图的 Init 中,您可以验证值更改(请参阅此处的 Print 语句),但实际视图并未更新。大概身体没有重画?
在完全埋头于 Python 服务器端的东西大约一年后,我回到了 SwiftUI,我简直不敢相信我这么生疏。我一定错过了一些简单的东西。
我错过了什么? (代码如下)
import SwiftUI
struct ContentView: View {
@State private var selectedPerson: Person?
// could also do this if optionals and nils are scary
// @State private var selectedPerson: Person = Person(
// id: 0,
// firstname: "No one",
// lastname: "Selected"
//) // or whatever
// pretend these are populated from an API REST call
private var people: [Person] = [
Person(id: 1, firstname: "Bob", lastname: "Bobberson"),
Person(id: 2, firstname: "Joe", lastname: "Smith"),
Person(id: 3, firstname: "Taylor", lastname: "Swift") // because apparently that's what you do now
]
var body: some View {
VStack {
if let sp = self.selectedPerson {
Text("Selected: \(sp.firstname)")
.padding()
} else {
Text("No one is selected")
}
ScrollView {
ForEach(people){ person in
Text(person.firstname)
.padding()
.onTapGesture {
selectedPerson = person
}
} // all works as expected. Trouble starts below.
if let _ = self.selectedPerson {
PersonEditor(person: selectedPerson!)
}
// or this if it's more desired or more "swifty"
if let e = self.selectedPerson {
PersonEditor(person: e)
}
}
}
.padding()
}
}
struct PersonEditor: View {
// this doesn't need to exist, just including for completeness.
private var person: Person
@State private var edtFirstname: String = ""
@State private var edtLastname: String = ""
init(person: Person){
// this doesn't need to exist. included for completeness.
self.person = person
self._edtFirstname = State(wrappedValue: person.firstname)
self._edtLastname = State(wrappedValue: person.lastname)
// changing to initialValue to wrappedValue seems to not matter
// this runs and prints correct.
print("current first name is \(self.edtFirstname)")
// so why does this show the fed-in value after setting the state
// but the Text and TextField objects in the body do not?
// suggests body isn't being called despite state changing.
}
var body: some View {
VStack {
Text("Editing \(person.firstname)") // this updates correctly (as expected)
Text("Editing \(self.edtFirstname)") // this does not
TextField("firstname", text: $edtFirstname) // this never changes despite init running
TextField("lastname", text: $edtLastname) // this never changes despite init running
}
}
}
我会像你一样通过注入 Person 来解决这个问题,但使用
.onChange
而不是 init
来设置属性
所以在 PersonEditor 中
let person: Person
然后是修改器
.onChange(of: person, initial: true) { _, newPerson in
self.edtFirstname = person.firstname
self.edtLastname = person.lastname
print("current first name is \(self.edtFirstname)")
}