You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 08:54:19