如何在NavigationSplitView中为侧边栏中的每个项目选择每个表格

问题描述 投票:0回答:1

我有一系列的会议。每个会话都有一组记录。 侧边栏中的 NavigationSplitView 中有会话。详细视图中有一个记录表。当用户单击会话时,新表将显示在详细视图中。 我想保留每个表/会话的记录选择。 现在,如果我在第一个会话中选择第 5 行,然后切换到第二个会话,则第 5 行将被选中。

struct ContentView: View {
    @ObservedObject var model: Model
    @State var selection: TestSession.ID?

    var body: some View {
        if model.sessions.count > 0 {
            NavigationSplitView(sidebar: {
                List(selection: $selection) {
                    ForEach(model.sessions, id: \.id) { session in
                        Text("\(session.name)")
                    }
                }
            }, detail: {
                if selection != nil {
                    let session = model[selection!]
                    if session != nil {
                        SessionView(model: model, session: session!, selection: session!.selectedRecord)
                    }
                }
            })
        } else {
            Text("No sessions")
        }
    }
}

struct SessionView: View {
    @ObservedObject var model: Model
    var session: TestSession
    @State var selection: TestRecord.ID?

    var body: some View {
        VStack {
            Text("Session \(session.id) - \(session.name) - \(session.records.count)")
            Text("Selected row: \(session.selectedRecord)")
            Table(of: TestRecord.self, selection: $selection) {
                TableColumn("Id") { rec in
                    Text("\(String(rec.id))")
                    .font(.system(.body, design: .monospaced))
                }
                TableColumn("Value") { rec in
                    Text("\(String(rec.value))")
                    .font(.system(.body, design: .monospaced))
                }
            } rows: {
                ForEach(session.records) { rec in
                    TableRow(rec)
                }
            }

            Spacer()
        }
        .onChange(of: selection) { oldValue, newValue in
            model.setSelectedRecord(selection, for: session.id)
        }
    }
}

class Model: ObservableObject {
    @Published var sessions: [TestSession] = []

    init() {
        let n = Int.random(in: 5..<10)
        for i in 0..<n {
            var s = TestSession(name: "session \(i + 1)")
            let m = Int.random(in: 10..<20)
            for j in 0..<m {
                s.addRecord("\(j + 1)")
            }
            self.sessions.append(s)
        }
    }

    subscript(index: UUID) -> TestSession? {
        for i in 0..<self.sessions.count {
            if self.sessions[i].id == index {
                return self.sessions[i]
            }
        }
        return nil
    }

    func setSelectedRecord(_ index: Int?, for session: TestSession.ID) {
        for i in 0..<self.sessions.count {
            if self.sessions[i].id == session {
                return self.sessions[i].selectedRecord = index
            }
        }
    }
}

struct TestSession: Identifiable
{
    let id = UUID()
    let name: String
    var records: [TestRecord] = []
    var selectedRecord: Int?

    init(name: String) {
        self.name = name
    }

    mutating func addRecord(_ value: String) {
        let r = TestRecord(id: self.records.count, value: String(self.records.count))
        self.records.append(r)
    }
}

struct TestRecord: Identifiable {
    let id: Int
    let value: String
}

我将选定的行保存在会话结构中,但在加载表时无法恢复它。

swiftui navigationsplitview
1个回答
0
投票

当你这样做时

SessionView(model: model, session: session!, selection: session!.selectedRecord)

您正在从外部初始化

@State
(
SessionView
) 的
selection
。这几乎总是不会做你想做的事。在视图的生命周期中,初始化
@State
只能工作一次。之后,它将保持不变,直到
SessionView
中的某些内容改变它。更改导航拆分视图侧栏列表选择不会改变它。

这就是为什么您应该始终使用属性初始化程序来初始化

@State
inline。这也是为什么
@State
应标记为
private
,这样您就不会意外地通过参数初始化它。

由于

selection
来自外部且
SessionView
可以改变它,所以它应该是
@Binding
,而不是
@State

struct SessionView: View {
    @Binding var selection: TestRecord.ID?
    ...
}

将该逻辑移至

setSelectedRecord
的设置器中,而不是
subscript
。这允许您从下标形成
Binding

// in Model...
subscript(index: UUID) -> TestSession? {
    get {
        sessions.first(where: { $0.id == index })
    }
    set {
        if let newValue, let i = sessions.firstIndex(where: { $0.id == newValue.id }) {
            sessions[i] = newValue
        }
    }
}
// in ContentView, this is how you would pass the Binding
detail: {
    if let selection, let session = Binding($model[selection]) {
        SessionView(model: model, session: session.wrappedValue, selection: session.selectedRecord)
    }
}

您现在也可以删除

.onChange(of: selection)
修饰符。

© www.soinside.com 2019 - 2024. All rights reserved.