SwiftUI中TextField无法更新其他类@Published ObservedObject变量
解决SwiftUI TextField无法更新NetworkingManager中@Published变量的问题
嘿,我帮你揪出问题根源啦!你的代码里有两个关键问题,导致输入的城市名没法同步到天气请求里:
问题分析
- 实例不共享:
ContentView和ForecastView各自创建了独立的NetworkingManager实例。你在首页输入的城市名存在第一个实例里,而天气请求用的是第二个全新的实例,它的inputCity自然还是默认的"Sydney"。 - URL硬编码城市名:就算实例共享了,你的
load()方法里还写死了q=Sydney,根本没用到inputCity变量,请求永远只会拉悉尼的天气。
修复方案
1. 共享同一个NetworkingManager实例
我们要把首页的NetworkingManager传递给详情页,让两个页面用同一个实例:
修改ContentView的NavigationLink
NavigationLink(destination: ForecastView(networkingManager: networkingManager)) { Text("Go") }
修改ForecastView的定义
让它接收外部传入的实例,而不是自己初始化:
struct ForecastView: View { @ObservedObject var networkingManager: NetworkingManager // 移除原来的实例初始化代码 // ... 其余代码保持不变 }
2. 修复API请求URL,使用inputCity变量
把load()方法里的硬编码城市名换成inputCity,还要把HTML转义的&改成正常的&,同时加上URL编码处理(避免带空格的城市名出错):
func load() { let apiKey = "d81d2f91438e7897ceb3c457c25" // 对城市名做URL编码,处理空格等特殊字符 guard let encodedCity = inputCity.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let url = URL(string: "https://api.openweathermap.org/data/2.5/forecast?q=\(encodedCity)&units=metric&APPID=\(apiKey)") else { print("无效的城市名或URL") return } print("请求的城市是: \(inputCity)") URLSession.shared.dataTask(with: url) { (data, response, err) in if let err = err { print("请求错误: \(err.localizedDescription)") return } guard let data = data else { print("未获取到数据") return } do { let weatherInfo = try JSONDecoder().decode(WeatherAPIResponse.self, from: data) DispatchQueue.main.async { self.weatherCity = weatherInfo.city self.weatherList = weatherInfo.list } } catch { print("解析错误: \(error.localizedDescription)") } }.resume() }
额外优化:把原来的
try!换成了do-catch,避免解析失败时直接崩溃,还加了错误打印方便调试。
修改后的完整代码
ContentView.swift
import SwiftUI struct ContentView: View { @ObservedObject var networkingManager = NetworkingManager() var body: some View { NavigationView { VStack { Text("Please Enter a City Name") TextField("City Name, i.e Sydney", text: $networkingManager.inputCity) .textFieldStyle(.roundedBorder) // 加个样式更美观 NavigationLink(destination: ForecastView(networkingManager: networkingManager)) { Text("Go") .padding() .background(Color.blue) .foregroundColor(.white) .cornerRadius(8) } } .padding() .navigationBarTitle("Weather") } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
NetworkingManager.swift
import Foundation import Combine class NetworkingManager : ObservableObject { @Published var inputCity = "Sydney" @Published var weatherList = [WeatherInfoList]() @Published var weatherCity = CityInformation(name: "Sydney", country: "AU") func load() { let apiKey = "d81d2f91438e7897ceb3c457c25" guard let encodedCity = inputCity.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let url = URL(string: "https://api.openweathermap.org/data/2.5/forecast?q=\(encodedCity)&units=metric&APPID=\(apiKey)") else { print("无效的城市名或URL") return } print("请求的城市是: \(inputCity)") URLSession.shared.dataTask(with: url) { (data, response, err) in if let err = err { print("请求错误: \(err.localizedDescription)") return } guard let data = data else { print("未获取到数据") return } do { let weatherInfo = try JSONDecoder().decode(WeatherAPIResponse.self, from: data) DispatchQueue.main.async { self.weatherCity = weatherInfo.city self.weatherList = weatherInfo.list } } catch { print("解析错误: \(error.localizedDescription)") } }.resume() } } // 补充完整模型定义让代码可运行 struct WeatherAPIResponse: Codable { let city: CityInformation let list: [WeatherInfoList] } struct CityInformation: Codable { let name: String let country: String } struct WeatherInfoList: Codable, Identifiable { let id = UUID() // 实现Identifiable,List无需手动指定id let dt: Int let main: MainWeather // 日期格式化计算属性 var date: String { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd HH:mm" return formatter.string(from: Date(timeIntervalSince1970: TimeInterval(dt))) } } struct MainWeather: Codable { let temp: Double // 若原代码是rTemp,可修改为let rTemp: Double }
ForecastView.swift
import SwiftUI struct ForecastView: View { @ObservedObject var networkingManager: NetworkingManager var body: some View { NavigationView { List(networkingManager.weatherList) { weather in HStack { VStack(alignment: .leading) { Text(weather.date) .font(.headline) Text("\(networkingManager.weatherCity.name), \(networkingManager.weatherCity.country)") .font(.subheadline) .foregroundColor(.gray) } Spacer() VStack(alignment: .trailing) { Text(String(format: "%.1f°C", weather.main.temp)) // 格式化温度保留一位小数 .font(.title3) Image(systemName: "cloud.sun.fill") .foregroundColor(.yellow) } } } .onAppear() { self.networkingManager.load() } .navigationBarTitle("Weather Forecast") } } } struct ForecastView_Previews: PreviewProvider { static var previews: some View { ForecastView(networkingManager: NetworkingManager()) } }
额外提示
- 记得把
apiKey换成你自己的OpenWeatherMap密钥,否则请求会失败。 - 可以给TextField添加空输入验证,避免无效请求。
内容的提问来源于stack exchange,提问作者user1975231
相关产品推荐
相关产品推荐

