处理Swiftui中的动态列表

发布于 2025-02-06 10:37:28 字数 2657 浏览 1 评论 0原文

我在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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

我是有多爱你 2025-02-13 10:37:29

@binding不知道或不知道您的价值是否可观察到,仅在更改值时就在乎。由于您正在修改参考类型的属性(dataprovider),因此您不会更改存储在绑定中的值,因此没有任何内容触发重新绘制。

在右窗格中有一个观察到的对象是更有意义的:

struct RightPane: View {
    @ObservedObject var dataProvider: DataProvider

    var body: some View {
        List(dataProvider.data, id: \.self) { dataProvider in
                Text(dataProvider.name)
        }
    }
}

将条件的物体移回左窗格:

NavigationView {
            List(data, id: \.self, selection: $dataProvider) { element in
                Text(element.name)
            }
            if let selection = dataProvider {
                RightPane(dataProvider: selection)
            } else {
                Text("Select in left pane")
            }
        }

不过,这只能解决您当前的问题。如果您在数组中突变单个数据提供商的属性,则同一件事也适用于左窗格 - 数组本身没有突变,因此您的状态变量不会触发重复。您应该将存储为@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:

struct RightPane: View {
    @ObservedObject var dataProvider: DataProvider

    var body: some View {
        List(dataProvider.data, id: \.self) { dataProvider in
                Text(dataProvider.name)
        }
    }
}

And move the conditional back to the left pane:

NavigationView {
            List(data, id: \.self, selection: $dataProvider) { element in
                Text(element.name)
            }
            if let selection = dataProvider {
                RightPane(dataProvider: selection)
            } else {
                Text("Select in 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文