如何延迟SwiftUI视图渲染直至@Published对象加载完成
问题核心原因
- SwiftUI视图在
onAppear执行前就会完成首次body渲染,此时weatherResponse作为可选值默认是nil,你代码里直接用!强制解包,会立刻触发空值崩溃,根本等不到异步网络请求返回。 - 你的代码里还有一处关键错误:
@StateObject是仅能在SwiftUI View结构体中使用的属性包装器,在ObservableObject类里用@StateObject持有LocationManager会导致生命周期管理失效,这也是getMainWeather()内打印语句不执行的核心诱因。
修复方案
- 修正属性包装器误用:将
WeatherNetworking类中的@StateObject var locationManager = LocationManager()替换为@ObservedObject var locationManager = LocationManager()。 - 移除所有强制解包逻辑:针对可选类型的
weatherResponse做分支渲染,数据未返回时显示加载态,数据返回后再渲染天气内容,从根源避免空值访问。 - (可选优化)如果需要等定位权限申请完成、拿到有效经纬度再发请求,可以在
WeatherNetworking中监听定位状态变化,自动触发天气请求,避免用默认经纬度(0,0)发起无效请求。
修正后的视图代码
struct MainWeatherView: View { @StateObject var weatherNetworking = WeatherNetworking() var body: some View { VStack { // 先解包weatherResponse,非空再渲染实际内容 if let weatherData = weatherNetworking.weatherResponse { let currentCondition = weatherData.current.weather[0] Image(systemName: weatherNetworking.getConditionName(weatherID: currentCondition.id)) .resizable() .aspectRatio(contentMode: .fit) .scaleEffect(0.75) .padding() HStack { VStack{ Text("Temperature") .fontWeight(.bold) .font(.system(size: 24)) Text("Humidity") .fontWeight(.bold) .font(.system(size: 24)) // 其余原有文本字段 } VStack { Text("\(weatherData.current.temp, specifier: "%.2f") °F") .fontWeight(.bold) .font(.system(size: 24)) Text("\(weatherData.current.humidity, specifier: "%.0f") %") .fontWeight(.bold) .font(.system(size: 24)) // 其余原有文本字段 } } } else { // 数据加载中状态,可根据需求替换为骨架屏、占位图 ProgressView("天气数据加载中") } // 如果有加载错误可以在这里加错误提示 if let error = weatherNetworking.loadError { Text("加载失败:\(error.localizedDescription)") .foregroundColor(.red) } } .onAppear { self.weatherNetworking.getMainWeather() } } }
修正后的网络类代码
class WeatherNetworking: ObservableObject { // 类中持有ObservableObject用@ObservedObject,不要用@StateObject @ObservedObject var locationManager = LocationManager() @Published var weatherResponse: WeatherResponse? @Published var loadError: Error? func getMainWeather() { let requestURL = URL(string: "https://api.openweathermap.org/data/2.5/onecall?appid=redacted&exclude=minutely&units=imperial&lat=\(locationManager.lastLocation?.coordinate.latitude ?? 0)&lon=\(locationManager.lastLocation?.coordinate.longitude ?? 0)") guard let url = requestURL else { return } URLSession.shared.dataTask(with: url) { [weak self] data, response, error in guard let self = self else { return } // 回到主线程更新UI相关属性 DispatchQueue.main.async { if let error = error { self.loadError = error return } guard let validData = data else { self.loadError = NSError(domain: "WeatherError", code: -1, userInfo: [NSLocalizedDescriptionKey: "无有效返回数据"]) return } do { let decodedResult = try JSONDecoder().decode(WeatherResponse.self, from: validData) self.weatherResponse = decodedResult } catch { self.loadError = error } } }.resume() } }
提示:如果你的定位是异步获取的,不要在
onAppear里直接调用请求,可以给LocationManager的lastLocation加didSet监听,或者在WeatherNetworking初始化时用Combine订阅locationManager.$lastLocation,拿到非空的有效定位后再触发网络请求,避免请求参数错误。
内容的提问来源于stack exchange,提问作者John Harrington
相关产品推荐
相关产品推荐

