Swift,在viewmodel中获取数据并传递给view

Swift, fetching data in viewmodel and passing it to view

我正在尝试更新我的 viewModel 中的数据,这是我的 viewModel;

import SwiftUI
import CoreLocation
final class LocationViewViewModel: ObservableObject {
    static let previewWeather: Response = load("Weather.json")

    let weatherManager = WeatherManager()
    let locationManager = LocationManager.shared
    
    @Published var weather: Response
    
    init(weather: Response) {       // Remove async
        DispatchQueue.main.async {                       // Here, you enter in an async environment
            let data = await fetchData()     // Read the data and pass it to a constant
            DispatchQueue.main.async {      // Get on the main thread
                self.weather = data         // Here, change the state of you app
            }
        }
    }
    
    func fetchData() async -> Response {
        guard let weather = try? await weatherManager.getWeather(latitude: weatherManager.latitude!, longitude: weatherManager.latitude!) else { fatalError("Network Error.") }
        return weather
    }
        var city: String {
            return locationManager.getCityName()
        }
        
        var date: String {
            return dateFormatter.string(from: Date(timeIntervalSince1970: TimeInterval(weather.current.dt)))
        }
        
        var weatherIcon: String {
            if weather.current.weather.count > 0 {
                return weather.current.weather[0].icon
            }
            return "sun.max"
        }
        
        var temperature: String {
            return getTempFor(temp: weather.current.temp)
        }
        
        var condition: String {
            if weather.current.weather.count > 0 {
                return weather.current.weather[0].main
            }
            return ""
        }
        
        var windSpeed: String {
            return String(format: "%0.1f", weather.current.wind_speed)
        }
        
        var humidity: String {
            return String(format: "%d%%", weather.current.humidity)
        }
        
        var rainChances: String {
            return String(format: "%0.0f%%", weather.current.dew_point)
        }
        
  

    
    var dateFormatter: DateFormatter = {
       let formatter = DateFormatter()
        formatter.dateStyle = .medium
       return formatter
   }()
   
    var dayFormatter: DateFormatter = {
       let formatter = DateFormatter()
       formatter.dateFormat = "EEE"
       return formatter
   }()
   
    var timeFormatter: DateFormatter = {
       let formatter = DateFormatter()
       formatter.dateFormat = "hh a"
       return formatter
   }()
    
    func getTimeFor(time: Int) -> String {
        return timeFormatter.string(from: Date(timeIntervalSince1970: TimeInterval(time)))
    }
    
    func getTempFor(temp: Double) -> String {
        return String(format: "%0.1f", temp)
    }
    
    func getDayFor(day: Int) -> String {
        return dayFormatter.string(from: Date(timeIntervalSince1970: TimeInterval(day)))
    }
}

我还为我的天气管理器中的先前视图获取了该数据,因此我在我的 viewModel 中使用了相同的功能。 我的天气管理器;

final class WeatherManager {
    var longitude = LocationManager.shared.location?.coordinate.longitude
    var latitude = LocationManager.shared.location?.coordinate.latitude
    var units: String = "metric"
    
    func getWeather(latitude: CLLocationDegrees, longitude: CLLocationDegrees) async throws -> Response {
        guard let url = URL(string: "https://api.openweathermap.org/data/2.5/onecall?lat=\(latitude)&lon=\(longitude)&units=\(units)&exclude=hourly,minutely&appid=\(API.API_KEY)") else { fatalError("Invalid Url.")}
        
        let urlRequest = URLRequest(url: url)
        
        let (data, response) = try await URLSession.shared.data(for: urlRequest)
                
        guard (response as? HTTPURLResponse)?.statusCode == 200 else { fatalError("Error while fetching data") }
                
        let decodedData = try JSONDecoder().decode(Response.self, from: data)
        
        return decodedData
    }
}

但是我遇到了关于初始化我的天气的编译错误也试图让我的天气模型成为可选的但最后我得到了致命错误,它说致命错误:在展开一个可选值时意外发现 nil 如果您在许多视图和视图模型中使用获取的数据,正确的做法是什么

您的 init() 正在尝试异步 运行 并且正在更新 @Published 属性。即使您设法避免了编译错误,也无法更新 属性 来更改视图状态 (@Published),除非您在主线程上。

我的建议:

    @Published var weather = Response()     // Initialise this property in some way, the dummy values will be used by the app until you complete fetching the data
    
    init(weather: Response) {       // Remove async
        Task {                       // Here, you enter in an async environment
            let data = await fetchData()     // Read the data and pass it to a constant
            DispatchQueue.main.async {      // Get on the main thread
                self.weather = data         // Here, change the state of you app
            }
        }
    }

我希望这能奏效,但如果在 “但我遇到了编译错误...”之后会更好... 你显示了你发现的错误类型。我试着用我最好的猜测来解决上面的问题。

我们不在 SwiftUI 中使用视图模型对象。您的对象正在做 SwiftUI 自动为我们做的不必要的事情,例如格式化字符串(因此标签会在区域设置更改时自动更新)和管理异步任务(任务在视图出现时以及数据更改时启动,并且如果数据在上一个之前更改也会取消)请求结束或视图消失)。尝试 re-architecting 它以正确使用 SwiftUI 数据视图,例如

struct WeatherView: View {
    let location: Location
    @State var weather: Weather?

    var body: some View {
        Form {
            Text(weather.date, format: .dateTime) // new simpler formatting
            Text(weather.date, formatter: dateFormatter) // label is auto updated when locale changes
            Text(weather?.date == nil ? "No date" : "\(weather.date!, format: .dateTime)") // optional handling
        }
        .task(id: location) { newLocation // tasks auto cancelled and restarted when location changes
             weather = await WeatherManager.shared.getWeather(location: newLocation)
        }
    }