我有一系列的会议。每个会话都有一组记录。 侧边栏中的 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
}
我将选定的行保存在会话结构中,但在加载表时无法恢复它。
当你这样做时
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)
修饰符。