SwiftUI接口参数传递与多列表温度数据重复问题咨询
问题分析与解决方案
你遇到的核心问题是所有行共享同一个全局的温度数据源,导致最后一次接口请求的数据覆盖了之前的结果,所以所有列表行显示完全相同的温度数据。
问题根源
你的AppState里的weatherAveInfos是一个单一数组,每个VegetableInfoRow在出现时都会调用接口更新这个数组。由于接口请求是异步的,后完成的请求会覆盖数组里的旧数据,最终所有行都会显示最后一次请求返回的温度数据——哪怕控制台打印的是不同结果。
解决方案
我提供两种常用修复方案,你可以根据需求选择:
方案一:让每个行独立管理自己的温度数据
这种方案让VegetableInfoRow不再依赖全局的weatherAveInfos,而是自己持有并获取对应温度数据,避免互相干扰。
修改VegetableInfoRow.swift:
import SwiftUI struct VegetableInfoRow: View { var info: DbVegetableInfos // 每个row单独存储自己的温度数据 @State private var weatherAveInfos: [WeatherAveInfos] = [] var body: some View { ScrollView(.horizontal) { HStack{ VStack{ VStack{ Text("start_date:").padding() Text(stringToStringDate(stringDate: info.start_date, format: "yyyy-MM-dd")) } } Divider() .padding() VStack{ VStack{ Text("progress_temperature:").padding() ForEach(weatherAveInfos){ tempInfo in Text(String(tempInfo.ave_temp)) } } } } } .onAppear(){ fetchWeatherAveTemp(start_date: info.start_date) } } // 单独封装当前row的温度请求逻辑 private func fetchWeatherAveTemp(start_date: String) { let endpoint: String = "https://sample.com/api/weather_ave/?start_date=\(start_date)" guard let url = URL(string: endpoint) else { print("Error: cannot create URL") return } var urlRequest = URLRequest(url: url) urlRequest.addValue("token xxxxxxxxxx", forHTTPHeaderField: "authorization") let config = URLSessionConfiguration.default let session = URLSession(configuration: config) let task = session.dataTask(with: urlRequest) { (data, response, error) in guard error == nil else { print("error calling GET for weather") return } guard let responseData = data else { print("Error: did not receive weather data") return } DispatchQueue.main.async { do{ self.weatherAveInfos = try JSONDecoder().decode([WeatherAveInfos].self, from: responseData) print("temp_info for \(start_date)") for tempInfo in self.weatherAveInfos{ print(tempInfo.ave_temp) } }catch{ print("Error: did not decode weather data") return } } } task.resume() } } func stringToStringDate(stringDate: String, format:String) -> String { let formatter: DateFormatter = DateFormatter() formatter.calendar = Calendar(identifier: .gregorian) formatter.dateFormat = "yyyy/MM/dd" guard let newDate = formatter.date(from: stringDate) else { return stringDate // 增加容错,避免强制解包崩溃 } formatter.dateFormat = format return formatter.string(from: newDate) }
方案二:用字典存储每个日期对应的温度数据(推荐,可复用数据)
这种方案在AppState里用字典映射每个start_date对应的温度数组,避免重复请求,同时保证每个行能获取到自己对应的数据。
- 修改
AppState.swift:
import SwiftUI import Foundation import Combine import UIKit class AppState: ObservableObject { @Published var arrayDbVegetableInfos:[DbVegetableInfos]? // 用字典存储,key是start_date,value是对应的温度数组 @Published var weatherAveInfosDict: [String: [WeatherAveInfos]] = [:] func makeGetCallVegetableInfos() { // 原代码保持不变 let endpoint: String = "https://sample.com/api/info/" guard let url = URL(string: endpoint) else { print("Error: cannot create URL") return } var urlRequest = URLRequest(url: url) urlRequest.addValue("token xxxxxxxxxx", forHTTPHeaderField: "authorization") let config = URLSessionConfiguration.default let session = URLSession(configuration: config) let task = session.dataTask(with: urlRequest) { (data, response, error) in guard error == nil else { print("error calling GET") print(error!) return } guard let responseData = data else { print("Error: did not receive data") return } DispatchQueue.main.async { do{ self.arrayDbVegetableInfos = try JSONDecoder().decode([DbVegetableInfos].self, from: responseData) }catch{ print("Error: did not decode") return } } } task.resume() } func makeGetCallWeatherAveTemp(start_date:String ) { // 先检查字典中是否已有数据,避免重复请求 if weatherAveInfosDict[start_date] != nil { return } let endpoint: String = "https://sample.com/api/weather_ave/?start_date=\(start_date)" guard let url = URL(string: endpoint) else { print("Error: cannot create URL") return } var urlRequest = URLRequest(url: url) urlRequest.addValue("token xxxxxxxxxx", forHTTPHeaderField: "authorization") let config = URLSessionConfiguration.default let session = URLSession(configuration: config) let task = session.dataTask(with: urlRequest) { (data, response, error) in guard error == nil else { print("error calling GET") return } guard let responseData = data else { print("Error: did not receive data") return } DispatchQueue.main.async { do{ let weatherData = try JSONDecoder().decode([WeatherAveInfos].self, from: responseData) self.weatherAveInfosDict[start_date] = weatherData print("temp_info for \(start_date)") for info in weatherData{ print(info.ave_temp) } }catch{ print("Error: did not decode") return } } } task.resume() } }
- 修改
VegetableInfoRow.swift:
import SwiftUI struct VegetableInfoRow: View { @EnvironmentObject var appState: AppState var info:DbVegetableInfos // 计算属性:获取当前start_date对应的温度数据 private var currentWeatherInfos: [WeatherAveInfos] { appState.weatherAveInfosDict[info.start_date] ?? [] } var body: some View { ScrollView(.horizontal) { HStack{ VStack{ VStack{ Text("start_date:").padding() Text(stringToStringDate(stringDate: info.start_date, format: "yyyy-MM-dd")) } } Divider() .padding() VStack{ VStack{ Text("progress_temperature:").padding() ForEach(currentWeatherInfos){ tempInfo in Text(String(tempInfo.ave_temp)) } } } } } .onAppear(){ appState.makeGetCallWeatherAveTemp(start_date: info.start_date) } } } func stringToStringDate(stringDate: String, format:String) -> String { let formatter: DateFormatter = DateFormatter() formatter.calendar = Calendar(identifier: .gregorian) formatter.dateFormat = "yyyy/MM/dd" guard let newDate = formatter.date(from: stringDate) else { return stringDate // 增加容错,避免强制解包崩溃 } formatter.dateFormat = format return formatter.string(from: newDate) }
额外优化建议
- 避免强制解包:我在
stringToStringDate里增加了guard let来替代!,防止日期格式错误导致崩溃。 - 添加加载状态:在温度数据未返回时显示加载指示器,提升用户体验。
内容的提问来源于stack exchange,提问作者Tio
相关产品推荐
相关产品推荐

