当其中一个属性是通过 API 调用获得的另外两个属性计算出来时,如何填充模型属性?

问题描述 投票:0回答:1

[已解决]

我没有将

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 提供除城市名称之外的所有数据,但我很难弄清楚获取它的逻辑流程。


我当前(失败)的方法是:

    创建一个名为
  1. WeatherReport
     的模型,其中包含天气预报的所有属性。
struct WeatherReport: Codable { var lat, lon: Double var city: String? let timezone: String let timezoneOffset: Int let daily: [Daily]

    我在此处添加了
  • city
     作为可选,因为调用填充这些属性的 API 将不会返回城市名称。
  • city
     通过反向地理编码确定的计算属性(提供纬度、经度和返回位置名称)。

    添加 .task 以从 API 下载数据并解码到模型中(这是有效的)。
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 }


(这就是我遇到所有麻烦的地方)

    现在我们已经有了可用的纬度/经度数据,请填充
  1. 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
    
    
  • 在原始 API 调用中,一旦成功解码
  • 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
    }


对于其他上下文,这是我正在尝试构建的视图。任何帮助将不胜感激,谢谢!

swift swiftui mvvm geocoding
1个回答
0
投票
您可以制作一个

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) } } }
    
© www.soinside.com 2019 - 2024. All rights reserved.