处理Swiftui中的动态列表
我在swiftui
中有一个navigationView
,其中左窗格和右窗格显示名称列表。左窗格是可选的,每个选择都在右手窗格中都有自己的列表。左窗格和右窗格都从同一类型中获取其数据是类dataprovider
。这是为了简单性而进行的,不是因为这是一项要求。
UI中有一个按钮,可以在左窗格和右侧窗格上添加数据,以供左窗格的第一个元素添加数据。
我在Mac上运行此操作,而不是在iPhone或iPad上运行。
代码看起来像这样:
//
// DemoApp.swift
// Shared
//
// Created by Fred Appelman on 08/06/2022.
//
import SwiftUI
@main
struct DemoApp: App {
var body: some Scene {
WindowGroup {
LeftPane()
}
}
}
class DataProvider: ObservableObject, Identifiable, Hashable {
static func == (lhs: DataProvider, rhs: DataProvider) -> Bool {
lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) { hasher.combine(id) }
let id = UUID()
@Published var name: String
@Published var data: [DataProvider]
init(name: String, data: [DataProvider]) {
self.name = name
self.data = data
}
}
struct RightPane: View {
@Binding var dataProvider: DataProvider?
var body: some View {
if let dataProvider = dataProvider {
List(dataProvider.data, id: \.self) { dataProvider in
Text(dataProvider.name)
}
} else {
Text("Select in left pane")
}
}
}
struct LeftPane: View {
@State var data: [DataProvider] = [
DataProvider(name: "left-a", data: [
DataProvider(name: "right-a1", data: []),
DataProvider(name: "right-a2", data: [])
]),
DataProvider(name: "left-b", data: [
DataProvider(name: "right-b1", data: []),
DataProvider(name: "right-b2", data: [])
]),
]
@State private var dataProvider: DataProvider?
var body: some View {
NavigationView {
List(data, id: \.self, selection: $dataProvider) { element in
Text(element.name)
}
RightPane(dataProvider: $dataProvider)
}
Button("Add extra data")
{
// Add to left pane
let extraDataLeftPane = DataProvider(name: "left-c", data: [
DataProvider(name: "right-c1",data: []),
DataProvider(name: "right-c2",data: [])
])
data.append(extraDataLeftPane)
// Add to right pane
data[0].data.append(DataProvider(name: "right-a3", data: []))
}
}
}
启动此应用程序时,可以选择左窗格元素,并显示右窗格值。到目前为止,一切都很好。
然后选择左窗格中的顶部元素,然后按按钮添加额外的数据。
这些额外的数据立即显示在左窗格中,但没有在右窗格中显示。它仅在左窗格中单击,然后重新选择顶部条目时才显示。
那么,我该怎么做才能立即在右手窗格中显示数据?
I have a NavigationView
in SwiftUI
where the left pane and right pane show a list of names. The left pane is selectable and each selection has its own list in the right hand pane. Both the left pane and right pane are getting their data from the same type being the class DataProvider
. This is done for simplicity and not because that is a requirement.
There is a button in the UI that adds data to both the left pane and to the right hand pane for the first element of the left pane.
I am running this on a Mac and not on an iPhone or iPad.
The code looks like this:
//
// DemoApp.swift
// Shared
//
// Created by Fred Appelman on 08/06/2022.
//
import SwiftUI
@main
struct DemoApp: App {
var body: some Scene {
WindowGroup {
LeftPane()
}
}
}
class DataProvider: ObservableObject, Identifiable, Hashable {
static func == (lhs: DataProvider, rhs: DataProvider) -> Bool {
lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) { hasher.combine(id) }
let id = UUID()
@Published var name: String
@Published var data: [DataProvider]
init(name: String, data: [DataProvider]) {
self.name = name
self.data = data
}
}
struct RightPane: View {
@Binding var dataProvider: DataProvider?
var body: some View {
if let dataProvider = dataProvider {
List(dataProvider.data, id: \.self) { dataProvider in
Text(dataProvider.name)
}
} else {
Text("Select in left pane")
}
}
}
struct LeftPane: View {
@State var data: [DataProvider] = [
DataProvider(name: "left-a", data: [
DataProvider(name: "right-a1", data: []),
DataProvider(name: "right-a2", data: [])
]),
DataProvider(name: "left-b", data: [
DataProvider(name: "right-b1", data: []),
DataProvider(name: "right-b2", data: [])
]),
]
@State private var dataProvider: DataProvider?
var body: some View {
NavigationView {
List(data, id: \.self, selection: $dataProvider) { element in
Text(element.name)
}
RightPane(dataProvider: $dataProvider)
}
Button("Add extra data")
{
// Add to left pane
let extraDataLeftPane = DataProvider(name: "left-c", data: [
DataProvider(name: "right-c1",data: []),
DataProvider(name: "right-c2",data: [])
])
data.append(extraDataLeftPane)
// Add to right pane
data[0].data.append(DataProvider(name: "right-a3", data: []))
}
}
}
When this application is started the left pane elements can be selected and the right pane values are shown. So far so good.
Then select the top element in the left pane and push the button to add extra data.
This extra data shows immediately in the left pane but does not show in the right pane. It will only show if you click away in the left pane and then re-select the top entry.
So, what can I do to make the data show immediately in the right hand pane?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
@binding
不知道或不知道您的价值是否可观察到,仅在更改值时就在乎。由于您正在修改参考类型的属性(dataprovider
),因此您不会更改存储在绑定中的值,因此没有任何内容触发重新绘制。在右窗格中有一个观察到的对象是更有意义的:
将条件的物体移回左窗格:
不过,这只能解决您当前的问题。如果您在数组中突变单个数据提供商的属性,则同一件事也适用于左窗格 - 数组本身没有突变,因此您的状态变量不会触发重复。您应该将存储为
@StateObject
的根数据提供商对象,然后从该对象的数据属性中驱动左窗格。然后,您将需要使用一个特定的视图,该视图将每个单独的提供商带有并观察到直接分配的字符串的
文本
,以便注意更改。另一种选择是,每个数据提供商都会观察其子女,并发送对象会发生变化,但是如果您有这些对象的复杂树,这些对象将导致许多不必要的重新绘制。A
@Binding
doesn't know or care if your value is observable, it only cares if the value is changed. Since you're modifying a property of a reference type (DataProvider
), you're not altering the value stored in the binding, so nothing is triggering a redraw.It makes more sense to have an observed object in the right pane:
And move the conditional back to the left pane:
This only solves your current problem, though. The same thing is going to apply to the left pane if you mutate a property of an individual data provider in the array - there is no mutation of the array itself, so your state variable will not trigger a redraw. You should have a root data provider object stored as a
@StateObject
, and drive your left pane from the data property of that object.You'll then want to use a specific view which takes and observes each individual provider, rather than a
Text
with a directly assigned string, so that changes are noticed. The alternative is that each data provider observes its children and sends object will change, but if you have a complex tree of these objects that is going to lead to a lot of unnecessary redraws.