如何将城市名称转换为CLLocation并获取多地点天气数据
问题解决方案
一、城市名称转CLLocation的正确实现
你已经用到了CLGeocoder的geocodeAddressString方法,但当前的回调式写法在SwiftUI异步环境下不够友好,建议重构为async/await风格的方法,直接返回CLLocation对象:
修改LocationManager,添加异步解析方法:
func location(for cityName: String) async throws -> CLLocation { return try await withCheckedThrowingContinuation { continuation in let geocoder = CLGeocoder() geocoder.geocodeAddressString(cityName) { placemarks, error in if let error = error { continuation.resume(throwing: error) return } guard let placemark = placemarks?.first, let location = placemark.location else { continuation.resume(throwing: NSError(domain: "LocationError", code: -1, userInfo: [NSLocalizedDescriptionKey: "无法解析该城市坐标"])) return } continuation.resume(returning: location) } } }
调用这个方法就能直接获取对应城市的CLLocation,无需手动处理坐标转换。
二、多地点天气数据的存储策略
不建议直接存储Weather对象,原因如下:
Weather包含实时数据,会随时间过期,存储过期数据没有实际意义Weather是不可变对象,维护数组会增加不必要的复杂度,且不利于数据刷新
推荐方案:
- 保留现有
weatherLocations: [String]数组,仅存储用户输入的城市名称 - 维护一个
[String: Weather]字典,用于缓存每个城市的实时天气数据,在需要展示或刷新时,再通过城市名称转坐标请求WeatherKit数据
三、代码修改示例
修改ContentView
调整天气数据存储方式,优化每个城市的天气请求逻辑:
struct ContentView: View { let weatherService: WeatherService @ObservedObject var locationManager: LocationManager @State private var weatherMap: [String: Weather] = [:] // 用字典缓存城市与天气的对应关系 @State var searchLocation: String = "" var body: some View { NavigationView { VStack { // 统一搜索栏,避免每个Tab重复渲染 HStack { TextField("输入城市名称", text: $searchLocation) .padding(.horizontal) .frame(width: 200) .background { RoundedRectangle(cornerRadius: 5) .foregroundColor(Color(UIColor.lightGray)) } Button(action: { guard !searchLocation.trimmingCharacters(in: .whitespaces).isEmpty else { return } locationManager.weatherLocations.append(searchLocation) searchLocation = "" }) { Image(systemName: "plus") } } .padding(.horizontal) if locationManager.weatherLocations.isEmpty { Text("暂无添加的城市") .padding() } else { TabView { ForEach(locationManager.weatherLocations, id: \.self) { city in VStack(spacing: 16) { Text(city) .font(.title) if let weather = weatherMap[city] { VStack(spacing: 8) { Text("更新时间:\(weather.currentWeather.date.formatted(date: .numeric, time: .shortened))") Text("当前温度:\(weather.currentWeather.temperature.formatted())") } } else { ProgressView("加载天气数据中...") } } .task { do { // 解析城市坐标 let location = try await locationManager.location(for: city) // 请求天气数据 let weather = try await weatherService.weather(for: location) // 存入缓存字典 weatherMap[city] = weather } catch { print("加载\(city)天气失败:\(error.localizedDescription)") } } } } .tabViewStyle(.page) } } } } }
额外注意事项
- 确保
Info.plist中添加了NSLocationWhenInUseUsageDescription权限描述,否则地理编码可能失败 - 地理编码存在模糊匹配风险(重名城市),可在解析后增加用户确认环节提升准确性
- 可添加下拉刷新功能,让用户手动触发天气数据更新
内容的提问来源于stack exchange,提问作者Lance Addison
相关产品推荐
相关产品推荐

