SwiftUI 从 OpenWeather API 自动获取数据后不更新 UI

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

我在 SwiftUI 项目中遇到一个问题,即 UI 在获取数据后不会自动更新。我有一个 WeatherViewModel 获取多个位置的天气数据,并且 UI 不会反映更改,直到我通过按下按钮手动触发更新。我尝试过使用 @Published 属性和 ObservableObject,但问题仍然存在。

我尝试在代码中使用 @Published 属性和 ObservableObject。尽管做出了这些努力,UI 仍然不会自动更新。

天气数据模型:

import Foundation

import Foundation


// MARK: - Welcome
struct WeatherDataModel: Codable {
    let coord: Coord
    let weather: [Weather]
    let base: String
    let main: Main
    let visibility: Int
    let wind: Wind
    let clouds: Clouds
    let dt: Int
    let sys: Sys
    let timezone, id: Int
    let name: String
    let cod: Int
}

// MARK: - Clouds
struct Clouds: Codable {
    let all: Int
}

// MARK: - Coord
struct Coord: Codable {
    let lon, lat: Double
}

// MARK: - Main
struct Main: Codable {
    let temp, feelsLike, tempMin, tempMax: Double
    let pressure, humidity: Int

    enum CodingKeys: String, CodingKey {
        case temp
        case feelsLike = "feels_like"
        case tempMin = "temp_min"
        case tempMax = "temp_max"
        case pressure, humidity
    }
}

// MARK: - Sys
struct Sys: Codable {
    let type, id: Int
    let country: String
    let sunrise, sunset: Int
}

// MARK: - Weather
struct Weather: Codable {
    let id: Int
    let main, description, icon: String
}

// MARK: - Wind
struct Wind: Codable {
    let speed: Double
    let deg: Int
}

class Location: ObservableObject, Identifiable {
    let id = UUID()
    @Published var name: String
    @Published  var weatherDataModel: WeatherDataModel?

    init(name: String) {
           self.name = name
       }

}

class WeatherViewModel: ObservableObject {
    @Published var locations: [Location] = []

    func fetchWeather(for location: Location) {
        // Implement API request to OpenWeatherMap using URLSession
        // You'll need to replace "YOUR_API_KEY" with your actual OpenWeatherMap API key
        let apiKey = "8281a792c5f995747b19a57c8e52ea8d"
        let urlString = "https://api.openweathermap.org/data/2.5/weather?q=\(location.name)&appid=\(apiKey)"
//        let urlString = "https://api.openweathermap.org/data/2.5/weather?q=mexico&appid=\(apiKey)"

        guard let url = URL(string: urlString) else { return }

        URLSession.shared.dataTask(with: url) { data, _, error in
            guard let data = data, error == nil else {

                return }

            do {
                let decodedData = try JSONDecoder().decode(WeatherDataModel.self, from: data)
                DispatchQueue.main.async {
                    location.weatherDataModel = decodedData
                    print("Weather data fetched successfully: \(decodedData)")
                }
            } catch {
                print("Error decoding weather data: \(error)")
            }
        }.resume()
    }
  





}









另外,由于一些奇怪的原因,视图确实会使用 .onAppear 自动更新,但是当我单击其中显然没有代码的按钮时,视图会更新并显示来自 API 的数据。

接收数据的视图:

//
//  TestView.swift
//  Amun
//
//  Created by Richard Nkanga on 09/02/2024.
//

import SwiftUI

struct HomeView: View {
    @StateObject private var viewModel = WeatherViewModel()
    @State var names = ["UNITED STATES", "Montreal", "Nigeria", "poland", "London"]
    @State private var newLocation = "Canada"
    @State private var isEditing = false

    var body: some View {
        NavigationStack {
            ZStack {
                Color(.background)
                    .ignoresSafeArea(.all)
                    .onAppear {
                    }

                VStack {
                    List(viewModel.locations, id: \.id) { locs in

                        HStack {
                            Text(locs.name)
                            Text("\(Double(locs.weatherDataModel?.main.temp ?? 0))")

                            Spacer()

                            if let weatherData = locs.weatherDataModel {
                                Text("Temp: \(Int(weatherData.main.temp))°C")
                                Text("Temp: \(weatherData.name)")
                            } else {
                                Text("Loading...")
                            }
                        }
                    }
            
                    .onAppear {
                        for name in names {
                            let location = Location(name: name)
                            viewModel.locations.append(location)
                            viewModel.fetchWeather(for: location)
                        }
                    }

                    Button(action: {
                        isEditing.toggle()



                    }, label: {
                        Text(isEditing ? "Done" : "Edit")
                            .foregroundColor(Color.red)

                    })
                }
            }
            .background(Color.red)
            .navigationTitle("Weather")
        }
    }
}

#Preview {
    HomeView()
}

用户界面永远不会更新,只是一直显示正在加载...

ios swift swiftui openweathermap
1个回答
0
投票

稍微修改一下你的代码,我想出的解决方案是,首先,切换到 async/await,因为代码会更短且更具可读性。我还发现了一个编码键错误:在 Sys 结构中,类型和 id 需要是可选的。

以下是我将 REST 调用修改为异步的方法:

    func fetchWeather(for location: Location) async throws -> WeatherDataModel? {
        // Implement API request to OpenWeatherMap using URLSession
        // You'll need to replace "YOUR_API_KEY" with your actual OpenWeatherMap API key
        let apiKey = "8281a792c5f995747b19a57c8e52ea8d"
        let urlString = "https://api.openweathermap.org/data/2.5/weather?q=\(location.name)&appid=\(apiKey)"
//        let urlString = "https://api.openweathermap.org/data/2.5/weather?q=mexico&appid=\(apiKey)"

        guard let url = URL(string: urlString) else { return nil }
        
        /// Shortest async way
        do {
            let (data, _) = try await URLSession.shared.data(from: url)
            let decodedResponse = try JSONDecoder().decode(WeatherDataModel.self, from: data)
            return decodedResponse
        } catch(let error) {
            print("Error decoding weather data: \(error)")
            return nil
        }
        
        /// Longest async version
        /*return await withCheckedContinuation { contination in
            URLSession.shared.dataTask(with: url) { data, _, error in
                guard let data = data, error == nil else {

                    return }

                do {
                    let decodedData = try JSONDecoder().decode(WeatherDataModel.self, from: data)
                    DispatchQueue.main.async {
                        //location.weatherDataModel = decodedData
                        contination.resume(returning: decodedData)
                        print("Weather data fetched successfully: \(decodedData)")
                    }
                } catch {
                    print("Error decoding weather data: \(error)")
                }
            }.resume()
        }*/
    }

然后你的系统结构:

struct Sys: Codable {
    let type, id: Int?
    let country: String
    let sunrise, sunset: Int
}

正如评论中所说,我还将 Location 更改为结构:

struct Location: Identifiable {
    let id = UUID()
    var name: String
    var weatherDataModel: WeatherDataModel?

    init(name: String) {
        self.name = name
    }

}

最后,使用任务修饰符更改 onAppear,如下所示:

.task {
     for name in names {
         var location = Location(name: name)
         let weatherDataModel = try? await viewModel.fetchWeather(for: location)
         location.weatherDataModel = weatherDataModel
         /// IMPORTANT!!!  Append the location at the end.
         viewModel.locations.append(location)
      }
}

如果您将附加放在数据不会更新之前,那么在每个操作末尾使用附加非常重要。 让我知道您对此解决方案的看法!

© www.soinside.com 2019 - 2024. All rights reserved.