SwiftUI Core Data歌单列表如何实现自定义预处理+Combine防抖搜索优化
实现方案
改动说明
- 引入Combine框架实现输入防抖,默认设置0.3秒延迟,可根据需求调整时长
- 完全保留原有自定义文本清洗、小写对比逻辑,不使用NSPredicate
- 新增防抖后搜索词状态、预计算过滤歌曲数组,减少实时计算开销
- 空搜索词直接触发结果更新,无需等待防抖延迟,提升交互体验
完整修改后代码
歌单列表代码(仅需修改这个文件,搜索栏代码无需改动)
import CoreData import SwiftUI import Combine // 新增导入Combine框架 struct SongbookView: View { @State var searchText: String = "" @State private var debouncedSearchText: String = "" @State private var filteredSongs: [Song] = [] @State private var searchCancellable: AnyCancellable? // 持有Combine订阅 @Environment(\.managedObjectContext) var managedObjectContext @FetchRequest( entity: Song.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Song.number, ascending: true)] ) var songs: FetchedResults<Song> var body: some View { NavigationView{ VStack{ SearchBar(text: $searchText) Spacer() // 直接使用预过滤的数组,避免实时计算 List(filteredSongs, id:\.objectID) { song in NavigationLink(destination: DetailView(song: song, isSelected: song.favorite)) { HStack{ Text("\(String(song.number)). ") .font(.headline) + Text(song.title ?? "Brak tytułu") if song.favorite { Spacer() Image(systemName: "heart.fill") .accessibility(label: Text("To jest ulubiona pieśń")) .foregroundColor(.red) } }.lineLimit(1) } }.id(UUID()) .listStyle(InsetListStyle()) .animation(.default) } .padding(.top, 10) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .principal) { Text("Śpiewnik") .font(.system(size: 20)) .bold() } } .onAppear { setupSearchPipeline() updateFilteredSongs(searchText: "") } // Core Data数据变更时自动更新过滤结果 .onChange(of: songs) { _ in updateFilteredSongs(searchText: debouncedSearchText) } } } // 配置Combine搜索防抖管道 private func setupSearchPipeline() { searchCancellable = $searchText .debounce(for: .seconds(0.3), scheduler: DispatchQueue.main) // 防抖延迟,可自行调整时长 .removeDuplicates() // 过滤重复输入,避免无效搜索 .sink(receiveValue: { [weak self] text in self?.updateFilteredSongs(searchText: text) }) } // 统一搜索过滤逻辑,保留原有自定义清洗规则 private func updateFilteredSongs(searchText: String) { debouncedSearchText = searchText guard !searchText.isEmpty else { filteredSongs = Array(songs) return } let lowercasedQuery = searchText.lowercased() filteredSongs = songs.filter { song in let cleanedContent = removeNumber(str: song.content?.lowercased() ?? "") return cleanedContent.contains(lowercasedQuery) || String(song.number).contains(lowercasedQuery) } } func removeNumber(str: String) -> String { var result = str let vowels: Set<Character> = ["1", "2", "3", "4", "5", "6", "7", "8", "9"] result.removeAll(where: { vowels.contains($0) }) return result } }
内容的提问来源于stack exchange,提问作者oculorum
相关产品推荐
相关产品推荐

