NavigationSplitView搭配.searchable modifier崩溃问题咨询:原因与解法
NavigationStack搭配.searchable表现正常
struct NavStackListSearchView: View { @State private var items = ["Item1", "Item2", "Item3", "Item4", "Item5"] @State private var selectedItems: Set<String> = [] @State private var searchText: String = "" var filteredItems: [String] { if searchText.isEmpty { return items } else { return items.filter { $0.contains(searchText) } } } var body: some View { NavigationStack { List(selection: $selectedItems) { ForEach(filteredItems, id: \.self) { item in NavigationLink(item) { Text(item).font(.title3) } } } } .searchable(text: $searchText) .onChange(of: searchText) { _ in selectedItems = [] } } }
NavigationSplitView搭配.searchable出现崩溃
struct NavSplitListSearchView: View { @State private var items = ["Item1", "Item2", "Item3", "Item4", "Item5"] @State private var selectedItems: Set<String> = [] @State private var searchText: String = "" var filteredItems: [String] { if searchText.isEmpty { return items } else { return items.filter { $0.contains(searchText) } } } var body: some View { NavigationSplitView { List(selection: $selectedItems) { ForEach(filteredItems, id: \.self) { item in Text(item) } } } detail: { if selectedItems.isEmpty { Text("Pick an item") } else { Text("\(selectedItems.first ?? "--no item--")") } } .searchable(text: $searchText) .onChange(of: searchText) { _ in selectedItems = [] } } }
崩溃复现步骤
- 启动应用
- 点击"Item3"
- 返回(若处于紧凑模式)
- 搜索"3"
- 点击显示的唯一条目"Item3"
→ 发生崩溃
开发环境:iOS 16.2、Xcode 14.2
崩溃原因
核心问题是NavigationSplitView的List选择机制与动态过滤的数据源存在状态冲突:
- 当你先选中Item3,搜索过滤后仅保留Item3的列表时,系统尝试更新选中状态,但由于你用字符串本身作为
id,List内部会将原始列表中的Item3与过滤后列表中的Item3判定为不同实例,触发内部状态不一致导致崩溃。 - 你遗漏了NavigationSplitView对数据源稳定性的要求:直接用过滤后的动态数组作为ForEach数据源,会导致List的视图结构频繁变更,而选中状态的绑定没有与数据源更新完全同步,进而引发断言失败。
解决方法
方法一:稳定数据源+条件显示
用原始数组作为ForEach的数据源,通过hidden和opacity控制条目是否显示,保持List内部标识的稳定性:
struct NavSplitListSearchView: View { @State private var items = ["Item1", "Item2", "Item3", "Item4", "Item5"] @State private var selectedItems: Set<String> = [] @State private var searchText: String = "" var body: some View { NavigationSplitView { List(selection: $selectedItems) { ForEach(items, id: \.self) { item in Text(item) .hidden(!item.contains(searchText)) .opacity(item.contains(searchText) ? 1 : 0) // 维持布局高度一致 } } } detail: { if selectedItems.isEmpty { Text("Pick an item") } else { Text("\(selectedItems.first ?? "--no item--")") } } .searchable(text: $searchText) .onChange(of: searchText) { _ in selectedItems = [] } } }
方法二:使用带唯一ID的数据模型
定义带唯一UUID的模型,避免用内容作为标识,确保过滤前后元素的ID稳定:
struct Item: Identifiable, Hashable { let id = UUID() let name: String } struct NavSplitListSearchView: View { @State private var items = [ Item(name: "Item1"), Item(name: "Item2"), Item(name: "Item3"), Item(name: "Item4"), Item(name: "Item5") ] @State private var selectedItems: Set<Item> = [] @State private var searchText: String = "" var filteredItems: [Item] { if searchText.isEmpty { return items } else { return items.filter { $0.name.contains(searchText) } } } var body: some View { NavigationSplitView { List(selection: $selectedItems) { ForEach(filteredItems) { item in Text(item.name) } } } detail: { if selectedItems.isEmpty { Text("Pick an item") } else { Text("\(selectedItems.first?.name ?? "--no item--")") } } .searchable(text: $searchText) .onChange(of: searchText) { _ in selectedItems = [] } } }
方法二更符合SwiftUI最佳实践,尤其适合存在重复内容的数据集。
内容的提问来源于stack exchange,提问作者KlausM
相关产品推荐
相关产品推荐

