如何通过NavigationSplitView向第二列传递数据?
问题背景
开发macOS双列应用,左侧列表展示处理单元(文件)的文件名和日期,右侧列在选中文件时展示内容并支持编辑。使用NavigationSplitView时遇到以下问题:
- 直接为@State变量赋值报错,不符合View协议
- 通过selectedUnitId获取索引失败
- 用NavigationLink跳转至UnitView后,选中新单元时TextEditor文本不更新,但已弃用的NavigationView功能正常
错误尝试代码
尝试1:直接在detail部分用TextEditor绑定本地@State
struct Unit: Codable, Hashable, Identifiable { let id: UUID var text: String var date = Date() var dateText: String { let df = DateFormatter() df.dateFormat = "EEEE, MMM d yyyy, h:mm a" return df.string(from: date) } var changed: Bool = false } final class UnitModel: ObservableObject { @AppStorage("unit") public var units: [Unit] = [] init() { self.units = self.units.sorted(by: { $0.date.compare($1.date) == .orderedDescending }) } func sortList() { self.units = self.units.sorted(by: { $0.date.compare($1.date) == .orderedDescending }) } } struct ContentView: View { @EnvironmentObject private var data: UnitModel @State var selectedUnitId: UUID? @State var text: String = "" var body: some View { NavigationSplitView { List(data.units, selection: $selectedUnitId) { currentunit in VStack(alignment: .leading) { Text(currentunit.filename) Text(currentunit.dateText) } } } detail: { VStack(alignment: .leading) { TextEditor(text: $text) } } } }
尝试2:用NavigationLink跳转至UnitView
struct ContentView: View { @EnvironmentObject private var data: UnitModel @State var selectedNoteId: UUID? var body: some View { NavigationSplitView { List(data.units, selection: $selectedNoteId) { currentunit in NavigationLink{ UnitView(unit: currentunit, text: currentunit.text) } label: { VStack(alignment: .leading) { Text(currentunit.filename) Text(currentunit.dateText) } } } } detail: { Text("Select a unit.") } } } struct UnitView: View { @EnvironmentObject private var data: UnitModel var unit: Unit @State var text: String var body: some View { VStack(alignment: .leading) { TextEditor(text: $text) } } }
旧的可用(已弃用)代码
NavigationView { List(data.units, selection: $selectedNoteId) { currentunit in NavigationLink( destination: UnitView(unit: currentunit, text: currentunit.text), label: { VStack(alignment: .leading) { Text(currentunit.filename) Text(currentunit.dateText) } } ) } }
核心问题分析
- 本地@State变量是单向状态,无法自动响应选中单元的变化,且在View的body中直接赋值会违反纯函数要求
- NavigationSplitView基于selection机制复用detail视图,而非像旧NavigationView那样每次跳转重建视图,传递副本无法同步更新
- 原UnitModel中@AppStorage直接存储数组,无法正确发布变化通知,导致视图无法感知数据源更新
正确实现方案
步骤1:修正UnitModel的可观察性与数据持久化
struct Unit: Codable, Hashable, Identifiable { let id: UUID var filename: String // 补充缺失的filename属性 var text: String var date = Date() var dateText: String { let df = DateFormatter() df.dateFormat = "EEEE, MMM d yyyy, h:mm a" return df.string(from: date) } var changed: Bool = false } final class UnitModel: ObservableObject { @AppStorage("unit") private var unitsData: Data = Data() @Published var units: [Unit] = [] init() { // 从AppStorage解码数据 if let decoded = try? JSONDecoder().decode([Unit].self, from: unitsData) { units = decoded.sorted(by: { $0.date > $1.date }) } } // 保存数据到AppStorage func saveUnits() { if let encoded = try? JSONEncoder().encode(units) { unitsData = encoded } } func sortList() { units = units.sorted(by: { $0.date > $1.date }) saveUnits() } }
步骤2:ContentView中绑定选中的Unit实例
struct ContentView: View { @EnvironmentObject private var data: UnitModel @State private var selectedUnit: Unit? var body: some View { NavigationSplitView { // 使用绑定数组,让列表直接关联数据源 List($data.units, selection: $selectedUnit) { $unit in VStack(alignment: .leading) { Text(unit.filename) Text(unit.dateText) } } } detail: { // 根据选中状态展示对应视图 if let selectedUnit = $selectedUnit { UnitView(unit: selectedUnit) } else { Text("Select a unit.") } } } }
步骤3:UnitView直接绑定Unit的text属性
struct UnitView: View { @Binding var unit: Unit @EnvironmentObject private var data: UnitModel var body: some View { VStack(alignment: .leading) { TextEditor(text: $unit.text) // 编辑文本时自动保存到数据源 .onChange(of: unit.text) { _ in data.saveUnits() } } } }
方案说明
- 用
@Published修饰UnitModel的units数组,确保数据源变化能触发视图更新 - 列表使用
$data.units绑定,选中的是Unit的引用而非副本,直接同步到detail视图 - UnitView通过
@Binding绑定选中的Unit,编辑文本直接修改数据源,选中新单元时自动更新TextEditor内容 - 修正AppStorage的存储方式,用Data序列化数组,避免直接存储Codable类型的潜在问题
内容的提问来源于stack exchange,提问作者Aleph
相关产品推荐
相关产品推荐

