SwiftUI搜索文本变更后如何重新调用Google Books API刷新数据
问题根因
当前实现无法触发搜索重载的核心原因有4点:
- 挂载在视图上的
.task修饰符仅会在视图首次出现时执行一次,后续状态变化不会自动重新调用loadData()发起请求 - 搜索词
textSearch变化时,仅切换了无实际逻辑作用的shouldReload变量,既没有把最新搜索值同步给network实例,也没有触发新的网络请求 - 首次加载时
.onAppear给network.searchText赋值的时机晚于.task的执行时机,导致首次请求用的是Network类默认的"watermelon"关键词,不是你初始化的"knjiga" - 持有Network实例的属性用了
@ObservedObject,不符合SwiftUI的状态持有规范,视图重建时可能丢失已加载的数据
修复步骤
1. 调整ContentView逻辑
删除无用的shouldReload状态和冗余的context()方法,统一封装搜索触发逻辑,绑定搜索提交事件:
struct ContentView: View { // 视图自身创建的ObservableObject实例必须用@StateObject持有,避免视图重建时实例被销毁 @StateObject var network = Network() @State var textSearch: String = "knjiga" // 统一的搜索触发方法:同步搜索词+发起请求 private func triggerSearch() { network.searchText = textSearch Task { await network.loadData() } } var body: some View { NavigationView{ List{ ForEach(network.book, id:\.self){ item in NavigationLink{ Webview(url: URL(string: item.volumeInfo.previewLink)!) } label: { HStack{ URLImage(urlString: item.volumeInfo.imageLinks.thumbnail) Text(item.volumeInfo.title) } } } } .searchable(text: $textSearch) // 监听搜索栏提交事件(点击键盘搜索按钮时触发) .onSubmit(of: .search) { triggerSearch() } .navigationTitle("Books") .task{ // 首次加载触发默认搜索 triggerSearch() } } } }
2. 修正Network类逻辑
补充搜索词URL编码避免特殊字符导致URL失效,将UI状态更新逻辑切换到主线程:
class Network: ObservableObject{ @Published var book = [Items]() var searchText: String = "watermelon" func loadData() async { // 对搜索词做URL百分号编码,兼容空格、特殊字符、多语言文本 guard let encodedSearch = searchText.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let url = URL(string: "https://www.googleapis.com/books/v1/volumes?q=\(encodedSearch)&key=API_KEY_PRIVATE") else { return } do { let (data, _) = try await URLSession.shared.data(from: url) // @Published属性更新必须在主线程执行,避免UI异常 DispatchQueue.main.async { if let decodedResponse = try? JSONDecoder().decode(Books.self, from: data) { self.book = decodedResponse.items } } } catch { print("Request error: \(error.localizedDescription)") } } }
3. 可选修复:URLImage线程安全问题
原实现中URLSession回调在后台线程给@State属性赋值会触发线程警告,调整fetch方法:
private func fetch(){ guard let url = URL(string: urlString) else { return } let task = URLSession.shared.dataTask(with:url) { data, _, error in guard let data = data else { return } DispatchQueue.main.async { self.data = data } } task.resume() }
补充说明
- 上述实现为点击键盘搜索按钮后触发请求,如果需要输入实时搜索(边输入边搜),只需要把
.onSubmit(of: .search)替换为.onChange(of: textSearch) { _ in triggerSearch() }即可,建议添加0.3-0.5秒的防抖逻辑,避免输入过程中产生过多无效请求。 - 记得把代码里的
API_KEY_PRIVATE替换为你自己申请的Google Books API密钥。
内容的提问来源于stack exchange,提问作者Luka Šalipur
相关产品推荐
相关产品推荐

