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

Swift中如何在URLSession.dataTask回调内更新状态并延迟视图跳转

问题分析与解决方案

为什么completionHandler里更新状态不生效?

URLSession的dataTask回调是在后台线程执行的,而SwiftUI的@State状态变量更新必须在主线程进行。直接在后台线程修改buttonSelected,SwiftUI无法捕获状态变化,自然不会触发页面跳转。

修复步骤

  1. 主线程更新状态:在completionHandler里用DispatchQueue.main.async包裹状态更新代码,确保在主线程执行。
  2. 调整NavigationDestination位置:把navigationDestination从Button上移到NavigationStack的直接子视图层级中,避免因视图结构导致的跳转失效。
  3. 完善状态更新逻辑:先确保数据解析完成,再触发跳转,同时保证无论请求成功或失败,都能关闭加载动画。

修改后的完整代码

struct AddressInputView: View {
    @State var buttonSelected = false
    @State var radius = 10_000 // In meters
    @State var isLoading = false
    @State private var address: String = ""
    @State private var results: [Result] = []

    func onSubmit() {
        if !address.isEmpty {
            fetch()
        }
    }

    func fetch() {
        results.removeAll()
        isLoading = true

        let backendUrl = Bundle.main.object(forInfoDictionaryKey: "BACKEND_URL") as? String ?? ""

        let escapedAddress = address.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? ""
        let params = "address=\(escapedAddress)&radius=\(radius)"
        guard let fullUrl = URL(string: "\(backendUrl)/results?\(params)") else {
            isLoading = false
            return
        }

        var request = URLRequest(url: fullUrl)
        request.httpMethod = "GET"

        let session = URLSession.shared
        let task = session.dataTask(with: request, completionHandler: { data, _, error in
            // 切换到主线程处理UI相关状态
            DispatchQueue.main.async {
                defer {
                    isLoading = false
                }
                
                if let error = error {
                    print("Request failed: \(error.localizedDescription)")
                    return
                }
                
                guard let data = data else {
                    print("No data received")
                    return
                }
                
                do {
                    let serviceResponse = try JSONDecoder().decode(ResultsServiceResponse.self, from: data)
                    self.results = serviceResponse.results
                    // 请求成功后再触发跳转
                    self.buttonSelected = true
                } catch let jsonError as NSError {
                    print("JSON decode failed: ", String(describing: jsonError))
                }
            }
        })

        task.resume()
    }

    var body: some View {
        NavigationStack {
            if isLoading {
                ProgressView()
            } else {
                VStack {
                    TextField(
                        "",
                        text: $address,
                        prompt: Text("Search address").foregroundColor(.gray)
                    )
                    .onSubmit {
                        onSubmit()
                    }

                    Button(action: onSubmit) {
                        Text("Submit")
                    }
                }
            }
            // 将navigationDestination移到NavigationStack的直接子层级
            .navigationDestination(isPresented: $buttonSelected) {
                ResultView(address: $address, results: $results)
            }
        }
    }
}

关键修改点说明

  • 后台线程转主线程:用DispatchQueue.main.async包裹所有状态更新逻辑,确保SwiftUI能正确响应状态变化。
  • URL有效性校验:增加guard let校验URL是否有效,避免无效URL导致的崩溃。
  • 增强错误处理:补充请求错误、空数据的处理逻辑,避免空值崩溃并方便调试。
  • 调整导航跳转位置:把navigationDestination移到NavigationStack的子视图上,保证状态变化时能正确触发跳转。
  • defer语句保证状态重置:无论请求成功或失败,isLoading都会被设为false,避免一直显示加载动画。

内容的提问来源于stack exchange,提问作者Timo Ernst

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 14:01:18