Picker切换时如何保留DetailView中选中的内容
我用NavigationSplitView配合Picker在主视图展示两个列表,选中列表项时DetailView能正常更新,但切换Picker选项时,DetailView经常会变成无内容状态。需要实现在用户选中新内容前,始终保留当前DetailView的内容。
复现代码
import SwiftUI struct Model1: Identifiable, Hashable { let id = UUID() let title: String let description: String } struct Model2: Identifiable, Hashable { let id = UUID() let name: String let address: String } struct CustomData: Identifiable { var id = UUID() var docs: [Model1] var lites: [Model2] init(docs: [Model1], lites: [Model2]) { self.docs = docs self.lites = lites } } @MainActor final class TestViewModel: ObservableObject { @Published var data: CustomData? func loadData() { let docs = [Model1(title: "title1", description: "desc1"), Model1(title: "title2", description: "desc2"), Model1(title: "title3", description: "desc3")] let lites = [Model2(name: "value1", address: "desc1"), Model2(name: "value2", address: "desc2"), Model2(name: "value3", address: "desc3")] self.data = CustomData(docs: docs, lites: lites) } func lites() -> [Model2] { return data?.lites ?? [] } func docs() -> [Model1] { return data?.docs ?? [] } } struct TestView2: View { @State private var subpage = 0 @ObservedObject var viewModel = TestViewModel() var body: some View { NavigationSplitView { VStack { Picker("Select option", selection: $subpage) { Text("Lites").tag(0) Text("Docs").tag(1) } .pickerStyle(.segmented) VStack() { switch(subpage){ case 0: List(viewModel.lites()) { lite in NavigationLink(value: lite) { HStack (alignment: .top) { Text(lite.name) .padding(.top, 10) .padding(.leading, 5) .font(.system(.headline)) } } } .navigationDestination(for: Model2.self) { lite in DetailView2(path: lite.name) } case 1: List(viewModel.docs()) { doc in NavigationLink(value: doc) { HStack (alignment: .top) { Text(doc.title) .padding(.top, 10) .padding(.leading, 5) .font(.system(.subheadline)) } } } .navigationDestination(for: Model1.self) { doc in DetailView2(path: doc.title) } default: Text("No docs") } } .accentColor(.blue) .padding(.top, 10) .scrollContentBackground(.hidden) .background(.gray.opacity(0.1)) } } detail: { DetailView2(path: "Example Text") } .onAppear(){ viewModel.loadData() } } } struct DetailView2: View { var path: String var body: some View { VStack(spacing: .infinity) { Text(path) } .navigationBarHidden(true) } } struct TestView2_Previews: PreviewProvider { static var previews: some View { TestView2() .previewInterfaceOrientation(.landscapeRight) .previewDevice("iPad (9th generation)") } }
问题展示

问题原因
- 切换Picker选项时,原列表对应的
navigationDestination会被从视图树中移除,导致之前选中的内容失去导航目标,DetailView被重置为默认占位文本(甚至因状态不匹配出现空白)。 - 默认DetailView是固定静态文本,未与选中内容绑定,无法持久化保存选中状态。
解决思路
- 在ViewModel中新增
@Published属性,用枚举统一管理两种类型的选中内容。 - 将DetailView的内容与该选中属性绑定,不再依赖
navigationDestination的自动跳转逻辑。 - 点击列表项时直接更新选中属性,确保切换Picker时选中状态不丢失。
修改后的完整代码
import SwiftUI struct Model1: Identifiable, Hashable { let id = UUID() let title: String let description: String } struct Model2: Identifiable, Hashable { let id = UUID() let name: String let address: String } // 新增枚举,兼容两种选中类型 enum SelectedItem: Hashable { case model1(Model1) case model2(Model2) } struct CustomData: Identifiable { var id = UUID() var docs: [Model1] var lites: [Model2] init(docs: [Model1], lites: [Model2]) { self.docs = docs self.lites = lites } } @MainActor final class TestViewModel: ObservableObject { @Published var data: CustomData? // 新增:保存当前选中的内容 @Published var selectedItem: SelectedItem? func loadData() { let docs = [Model1(title: "title1", description: "desc1"), Model1(title: "title2", description: "desc2"), Model1(title: "title3", description: "desc3")] let lites = [Model2(name: "value1", address: "desc1"), Model2(name: "value2", address: "desc2"), Model2(name: "value3", address: "desc3")] self.data = CustomData(docs: docs, lites: lites) } func lites() -> [Model2] { return data?.lites ?? [] } func docs() -> [Model1] { return data?.docs ?? [] } } struct TestView2: View { @State private var subpage = 0 @ObservedObject var viewModel = TestViewModel() var body: some View { NavigationSplitView { VStack { Picker("Select option", selection: $subpage) { Text("Lites").tag(0) Text("Docs").tag(1) } .pickerStyle(.segmented) VStack { switch subpage { case 0: List(viewModel.lites()) { lite in // 点击时直接更新选中项 Button(action: { viewModel.selectedItem = .model2(lite) }) { HStack(alignment: .top) { Text(lite.name) .padding(.top, 10) .padding(.leading, 5) .font(.system(.headline)) } } // 高亮当前选中的项 .background(viewModel.selectedItem == .model2(lite) ? Color.accentColor.opacity(0.1) : Color.clear) } case 1: List(viewModel.docs()) { doc in Button(action: { viewModel.selectedItem = .model1(doc) }) { HStack(alignment: .top) { Text(doc.title) .padding(.top, 10) .padding(.leading, 5) .font(.system(.subheadline)) } } .background(viewModel.selectedItem == .model1(doc) ? Color.accentColor.opacity(0.1) : Color.clear) } default: Text("No docs") } } .accentColor(.blue) .padding(.top, 10) .scrollContentBackground(.hidden) .background(.gray.opacity(0.1)) } } detail: { // 根据选中项显示对应内容,无选中时保留默认文本 Group { switch viewModel.selectedItem { case .model1(let doc): DetailView2(path: doc.title) case .model2(let lite): DetailView2(path: lite.name) case nil: DetailView2(path: "Example Text") } } } .onAppear { viewModel.loadData() } } } struct DetailView2: View { var path: String var body: some View { VStack(spacing: .infinity) { Text(path) } .navigationBarHidden(true) } } struct TestView2_Previews: PreviewProvider { static var previews: some View { TestView2() .previewInterfaceOrientation(.landscapeRight) .previewDevice("iPad (9th generation)") } }
关键修改点
- 新增
SelectedItem枚举,统一管理两种类型的选中内容。 - ViewModel中添加
@Published var selectedItem: SelectedItem?,持久化保存选中状态。 - 将原
NavigationLink替换为Button,点击时直接更新selectedItem,同时添加选中状态高亮背景。 - DetailView不再依赖
navigationDestination,直接根据viewModel.selectedItem显示对应内容,无选中时保留默认文本。 - 移除原有的
navigationDestination,避免切换Picker时导航目标失效的问题。
内容的提问来源于stack exchange,提问作者Ariel Antonio Fundora
相关产品推荐
相关产品推荐

