为何SwiftData的@Query会忽略搜索关键词?
问题:SwiftData @Query 搜索过滤不生效的原因?
我写了下面的SwiftUI + SwiftData代码,想用@Query结合搜索文本过滤Item列表,但输入关键词后items数组完全没变化;换成在内存中过滤的写法却能正常工作。
不生效的代码:
import SwiftUI import SwiftData @Model final class Item { @Attribute(.unique) var id = UUID() private(set) var name: String init(name: String) { self.name = name } } struct ContentView: View { @State private var searchText: String @Query private var items: [Item] init(searchText: String = "") { _searchText = State(initialValue: searchText) _items = Query(filter: #Predicate<Item> { if searchText.isEmpty { return true } else { return $0.name.localizedStandardContains(searchText) } }, sort: [SortDescriptor(\Item.name)]) } var body: some View { NavigationStack { List { Section("Items") { ForEach(items) { item in Text(item.name) } } }.searchable(text: $searchText) } } }
生效的内存过滤写法:
var filteredItems: [Item] { guard !searchText.isEmpty else { return items } return items.filter { $0.name.localizedStandardContains(searchText) } }
请问我遗漏了什么关键内容?
原因分析
不生效的核心问题
你在初始化里创建的@Query是静态的——它只会用初始化时的searchText初始值生成过滤条件,之后searchText变化时,这个Query的过滤规则不会自动更新。@Query的初始化参数不会自动响应State变量的变化,相当于把过滤条件“固定”在了初始状态。
为什么内存过滤写法生效
filteredItems是一个计算属性,每次searchText改变时,SwiftUI都会重新计算这个属性的值,自然能实时过滤内存中的items数组,所以能正常响应搜索输入。
正确的@Query动态过滤写法
要让@Query响应searchText变化,需要在searchText更新时重新创建Query,比如用onChange监听变化:
struct ContentView: View { @State private var searchText: String = "" @Query private var items: [Item] init() { // 初始化时先设置默认排序 _items = Query(sort: [SortDescriptor(\Item.name)]) } var body: some View { NavigationStack { List { Section("Items") { ForEach(items) { item in Text(item.name) } } } .searchable(text: $searchText) // 监听searchText变化,更新Query的过滤条件 .onChange(of: searchText) { _, newText in _items = Query(filter: #Predicate<Item> { newText.isEmpty ? true : $0.name.localizedStandardContains(newText) }, sort: [SortDescriptor(\Item.name)]) } } } }
或者也可以用动态生成FetchDescriptor的方式,直接通过ModelContext获取过滤后的结果:
struct ContentView: View { @State private var searchText: String = "" @Environment(\.modelContext) private var modelContext private var items: [Item] { let predicate = searchText.isEmpty ? #Predicate<Item> { _ in true } : #Predicate<Item> { $0.name.localizedStandardContains(searchText) } let descriptor = FetchDescriptor<Item>(predicate: predicate, sortBy: [SortDescriptor(\Item.name)]) return (try? modelContext.fetch(descriptor)) ?? [] } var body: some View { NavigationStack { List { Section("Items") { ForEach(items) { item in Text(item.name) } } } .searchable(text: $searchText) } } }
内容的提问来源于stack exchange,提问作者Whirlwind
相关产品推荐
相关产品推荐

