Swift中如何在URLSession.dataTask回调内更新状态并延迟视图跳转
问题分析与解决方案
为什么completionHandler里更新状态不生效?
URLSession的dataTask回调是在后台线程执行的,而SwiftUI的@State状态变量更新必须在主线程进行。直接在后台线程修改buttonSelected,SwiftUI无法捕获状态变化,自然不会触发页面跳转。
修复步骤
- 主线程更新状态:在completionHandler里用
DispatchQueue.main.async包裹状态更新代码,确保在主线程执行。 - 调整NavigationDestination位置:把
navigationDestination从Button上移到NavigationStack的直接子视图层级中,避免因视图结构导致的跳转失效。 - 完善状态更新逻辑:先确保数据解析完成,再触发跳转,同时保证无论请求成功或失败,都能关闭加载动画。
修改后的完整代码
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
相关产品推荐
相关产品推荐

