SwiftUI中如何等待城市值赋值后再执行.onAppear加载数据?
解决SwiftUI中逆地理编码完成前调用API导致城市为空的问题
这个问题我之前也遇到过——本质是异步操作的顺序问题:你的位置获取和逆地理编码都是异步完成的,但onAppear会在视图加载时立刻触发,这时候city还没被赋值,自然会导致API请求用了空值。咱们可以通过监听城市值的变化来触发API请求,同时优化位置管理器的逻辑,避免不必要的重复操作。
核心解决方案:监听城市状态变化,而非视图加载时直接请求
1. 修改HomeScreenView,替换onAppear为onChange监听
把原来在List上的.onAppear(perform: loadData)移除,改成监听city的变化,当城市有效值出现时再调用loadData。另外可以加个状态变量避免重复请求:
struct HomeScreenView: View { @State var results = [Cinema]() @ObservedObject var lm = LocationManager() @State private var hasLoadedCinemas = false // 避免重复请求 @State private var isLoading = false // 可选:添加加载状态 var latitude: String { return("\(lm.location?.latitude ?? 0)") } var longitude: String { return("\(lm.location?.longitude ?? 0)") } var placemark: String { return("\(lm.placemark?.description ?? \"XXX\")") } var status: String { return("\(lm.status ?? .notDetermined)") } var city: String { return("\(lm.placemark?.locality ?? \"empty\")") } var body: some View { VStack { // 可选:添加加载提示 if isLoading { ProgressView("加载影院中...") } List(results, id: \._id) { item in VStack(alignment: .leading) { Text(item.cinemaName) .font(.headline) Text(item.cinemaCategory) } } // 监听city的变化,当城市有效且未加载过数据时触发请求 .onChange(of: city) { newCity in guard newCity != "empty", !hasLoadedCinemas else { return } loadData() } } } func loadData() { guard let url = URL(string: "https://mycinemasapi.com/cinemasbycity/\(self.city)") else { print("Invalid URL") return } isLoading = true let request = URLRequest(url: url) URLSession.shared.dataTask(with: request) { data, response, error in defer { DispatchQueue.main.async { isLoading = false } } if let data = data { if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data) { DispatchQueue.main.async { self.results = decodedResponse.cinemas self.hasLoadedCinemas = true // 标记已加载,避免重复请求 } return } } print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")") }.resume() } }
2. 优化LocationManager,避免重复获取位置和逆地理编码
当前你的LocationManager会持续更新位置,这会导致多次触发逆地理编码,既浪费资源又可能引发不必要的API请求。可以在获取到第一次有效位置后停止更新:
extension LocationManager: CLLocationManagerDelegate { func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { self.status = status // 可选:如果用户拒绝权限,可以在这里处理提示 if status == .denied { print("用户拒绝了位置权限") } } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { guard let location = locations.last else { return } self.location = location self.geocode() // 获取到位置后停止更新,避免重复触发 manager.stopUpdatingLocation() } }
另外,建议给geocode方法的错误处理补全日志,方便调试:
private func geocode() { guard let location = self.location else { return } geocoder.reverseGeocodeLocation(location, completionHandler: { (places, error) in if let error = error { print("逆地理编码失败:\(error.localizedDescription)") self.placemark = nil return } self.placemark = places?[0] }) }
为什么这样修改有效?
onChange(of: city):只有当city从"empty"变成实际城市名称时,才会触发loadData,确保API请求时城市值已经准备好。hasLoadedCinemas:避免城市值因某些原因变化时重复请求API(比如位置微调导致逆地理编码重复触发)。- 停止位置更新:获取到一次有效位置后就停止更新,减少不必要的异步操作,提升性能。
这样修改后,你的应用就能在城市信息准备完成后再去请求影院列表,不会再出现city为空的问题了。
内容的提问来源于stack exchange,提问作者Jose Q
相关产品推荐
相关产品推荐

