[已解决]
我没有将
reverseGeocode
视为异步函数,导致计时问题和数据无法正确填充。使用 async await
解决了这个问题。
解决方案:
func getCurrentWeather(latitude: CLLocationDegrees, longitude: CLLocationDegrees) async throws -> WeatherReport? {
do {
guard let url = URL(string: "https://api.openweathermap.org/data/3.0/onecall?lat=\(latitude)&lon=\(longitude)&exclude=current,minutely,hourly, alerts&appid=19054ace749a2f641842133743dfdfd9&units=imperial") else { throw NetworkError.badURL}
let (data, response) = try await URLSession.shared.data(from: url)
guard let response = response as? HTTPURLResponse else { throw NetworkError.badResponse }
guard response.statusCode >= 200 && response.statusCode < 300 else { throw NetworkError.badStatus }
let decodedData = try JSONDecoder().decode(WeatherReport.self, from: data)
let decodedDataWithCity = try await decodedData.reverseGeocode()
return decodedDataWithCity
} catch NetworkError.badURL {
print("Error creating URL")
} catch NetworkError.badResponse {
print("Didn't get a valid response")
} catch NetworkError.badStatus {
print("Didn't get a 2xx status code from response")
} catch {
print("An error occurred downloading the data")
}
return nil
}
func reverseGeocode() async throws -> WeatherReport? {
let geocoder = CLGeocoder()
let location = CLLocation(latitude: lat, longitude: lon)
var report: WeatherReport?
do {
let placemarks = try await geocoder.reverseGeocodeLocation(location)
let placemark = placemarks.first
report = WeatherReport(lat: lat, lon: lon, city: placemark?.locality, timezone: timezone, timezoneOffset: timezoneOffset, daily: daily)
} catch {
print("Error: \(error.localizedDescription)")
}
return report
}
func getCurrentWeather(latitude: CLLocationDegrees, longitude: CLLocationDegrees) async throws -> WeatherReport? {
do {
guard let url = URL(string: "https://api.openweathermap.org/data/3.0/onecall?lat=\(latitude)&lon=\(longitude)&exclude=current,minutely,hourly, alerts&appid=19054ace749a2f641842133743dfdfd9&units=imperial") else { throw NetworkError.badURL}
let (data, response) = try await URLSession.shared.data(from: url)
guard let response = response as? HTTPURLResponse else { throw NetworkError.badResponse }
guard response.statusCode >= 200 && response.statusCode < 300 else { throw NetworkError.badStatus }
let decodedData = try JSONDecoder().decode(WeatherReport.self, from: data)
let decodedDataWithCity = decodedData.reverseGeocode(latitude: decodedData.lat, longitude: decodedData.lon)
return decodedDataWithCity
} catch NetworkError.badURL {
print("Error creating URL")
} catch NetworkError.badResponse {
print("Didn't get a valid response")
} catch NetworkError.badStatus {
print("Didn't get a 2xx status code from response")
} catch {
print("An error occurred downloading the data")
}
return nil
}
[原帖]
我正在构建一个简单的天气应用程序,它显示:
OpenWeatherMap API 提供除城市名称之外的所有数据,但我很难弄清楚获取它的逻辑流程。
我当前(失败)的方法是:
WeatherReport
的模型,其中包含天气预报的所有属性。
struct WeatherReport: Codable {
var lat, lon: Double
var city: String?
let timezone: String
let timezoneOffset: Int
let daily: [Daily]
city
作为可选,因为调用填充这些属性的 API 将不会返回城市名称。
city
通过反向地理编码确定的计算属性(提供纬度、经度和返回位置名称)。
func getCurrentWeather(latitude: CLLocationDegrees, longitude: CLLocationDegrees) async throws -> WeatherReport? {
do {
guard let url = URL(string: "https://api.openweathermap.org/data/3.0/onecall?lat=\(latitude)&lon=\(longitude)&exclude=current,minutely,hourly, alerts&appid=19054ace749a2f641842133743dfdfd9&units=imperial") else { throw NetworkError.badURL}
let (data, response) = try await URLSession.shared.data(from: url)
guard let response = response as? HTTPURLResponse else { throw NetworkError.badResponse }
guard response.statusCode >= 200 && response.statusCode < 300 else { throw NetworkError.badStatus }
let decodedData = try JSONDecoder().decode(WeatherReport.self, from: data)
return decodedData
} catch NetworkError.badURL {
print("Error creating URL")
} catch NetworkError.badResponse {
print("Didn't get a valid response")
} catch NetworkError.badStatus {
print("Didn't get a 2xx status code from response")
} catch {
print("An error occurred downloading the data")
}
return nil
}
(这就是我遇到所有麻烦的地方)
city
属性。
尝试#1
reverseGeocode
函数,该函数采用纬度/经度 (CLLocationDegrees) 并返回城市名称(字符串)。
didSet
属性观察器调用
reverseGeocode
函数来计算
city
'var' declarations with multiple variables cannot have explicit getters/setters
var lat, lon: Double {
didSet {
self.city = reverseGeocode(latitude: lat, longitude: lon)
}
}
func reverseGeocode(latitude: CLLocationDegrees, longitude: CLLocationDegrees) -> String {
let geocoder = CLGeocoder()
let location = CLLocation(latitude: latitude, longitude: longitude)
var cityName: String
geocoder.reverseGeocodeLocation(location) { placemarks, error in
print("in geocoder.reverseGeocodeLocation function")
// ensure no error
guard error == nil else { return }
// ensure there are placemarks
if let placemarks = placemarks,
let placemark = placemarks.first {
cityName = placemark.locality ?? "Current location"
}
}
return cityName
}
尝试#2
reverseGeocode
方法以返回
WeatherReport
而不是
String
WeatherReport
(不包括城市名称) - 我们调用
reverseGeocode
方法来生成包含城市名称的新
WeatherReport
。
WeatherReport
来生成视图的异步任务上。最初认为
reverseGeocode
方法不起作用,但在其中添加了一些打印语句来确认它是有效的。在等待
WeatherReport
进入时,视图卡在加载旋转器上。
@EnvironmentObject private var locationManager: LocationManager
private let weatherManager = WeatherManager()
@State var weatherReport: WeatherReport?
var body: some View {
ZStack {
// if location exists...
if let location = locationManager.location {
// ..and weather report exists
if let weatherReport = weatherReport {
// show WeatherView
WeatherView(weatherReport: weatherReport)
} else {
// show loading spinner
LoadingView()
.task {
do {
// obtain weather report
weatherReport = try await weatherManager.getCurrentWeather(latitude: location.latitude, longitude: location.longitude)
} catch {
print("Unable to get weather info - Error: \(error.localizedDescription)")
}
}
}
func getCurrentWeather(latitude: CLLocationDegrees, longitude: CLLocationDegrees) async throws -> WeatherReport? {
do {
guard let url = URL(string: "https://api.openweathermap.org/data/3.0/onecall?lat=\(latitude)&lon=\(longitude)&exclude=current,minutely,hourly, alerts&appid=19054ace749a2f641842133743dfdfd9&units=imperial") else { throw NetworkError.badURL}
let (data, response) = try await URLSession.shared.data(from: url)
guard let response = response as? HTTPURLResponse else { throw NetworkError.badResponse }
guard response.statusCode >= 200 && response.statusCode < 300 else { throw NetworkError.badStatus }
let decodedData = try JSONDecoder().decode(WeatherReport.self, from: data)
let decodedDataWithCity = decodedData.reverseGeocode(latitude: decodedData.lat, longitude: decodedData.lon)
return decodedDataWithCity
} catch NetworkError.badURL {
print("Error creating URL")
} catch NetworkError.badResponse {
print("Didn't get a valid response")
} catch NetworkError.badStatus {
print("Didn't get a 2xx status code from response")
} catch {
print("An error occurred downloading the data")
}
return nil
}
func reverseGeocode(latitude: CLLocationDegrees, longitude: CLLocationDegrees) -> WeatherReport {
let geocoder = CLGeocoder()
let location = CLLocation(latitude: latitude, longitude: longitude)
var report: WeatherReport
geocoder.reverseGeocodeLocation(location) { placemarks, error in
print("in geocoder.reverseGeocodeLocation function")
// ensure no error
guard error == nil else { return }
// ensure there are placemarks
if let placemarks = placemarks,
let placemark = placemarks.first {
report = WeatherReport(lat: lat, lon: lon, city: placemark.locality, timezone: timezone, timezoneOffset: timezoneOffset, daily: daily)
print("report: \(report)")
}
}
return report
}
CityView
,例如
struct CityView: View {
@Environment(\.myGeocoder) var myGeocoder
let lat, lon: Double
@State var city: City?
var body: some View {
Text(city.name ?? "Loading...")
.task(id: [lat, lon]) {
do {
self.city = try await myGeocoder.reverseGeocode(latitude: lat, longitude: lon)
}
catch {
print(error.localizedDescription)
}
}
}