【发布时间】:2021-12-23 15:53:59
【问题描述】:
我正在尝试设置一个 SwiftUI 天气应用程序。当用户在文本字段中搜索城市名称然后点击搜索按钮时,NavigationLink 列表项应出现在列表中。然后,用户应该能够单击导航链接并重新定向到详细视图。我的目标是让搜索到的导航链接填充列表。但是,我的搜索城市没有出现在列表中,我不知道为什么。在 ContentView 中,我设置了一个带有 ForEach 函数的列表,该函数传入 cityNameList,它是 WeatherViewModel 的一个实例。我的期望是 Text(city.title) 应该显示为 NavigationLink 列表项。我应该如何配置 ContentView 或 ViewModel 以使用 NavigationLink 列表项填充列表?请参阅下面的我的代码:
内容视图
import SwiftUI
struct ContentView: View {
// Whenever something in the viewmodel changes, the content view will know to update the UI related elements
@StateObject var viewModel = WeatherViewModel()
@State private var cityName = ""
var body: some View {
NavigationView {
VStack {
TextField("Enter City Name", text: $cityName).textFieldStyle(.roundedBorder)
Button(action: {
viewModel.fetchWeather(for: cityName)
cityName = ""
}, label: {
Text("Search")
.padding(10)
.background(Color.green)
.foregroundColor(Color.white)
.cornerRadius(10)
})
List {
ForEach(viewModel.cityWeather, id: \.id) { city in
NavigationLink(destination: DetailView(detail: viewModel)) {
HStack {
Text(city.cityWeather.name)
.font(.system(size: 32))
}
}
}
}
Spacer()
}
.navigationTitle("Weather MVVM")
}.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
视图模型
import Foundation
class WeatherViewModel: ObservableObject {
//everytime these properties are updated, any view holding onto an instance of this viewModel will go ahead and updated the respective UI
@Published var cityWeather: WeatherModel = WeatherModel()
func fetchWeather(for cityName: String) {
guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=\(cityName)&units=imperial&appid=<MyAPIKey>") else {
return
}
let task = URLSession.shared.dataTask(with: url) { data, _, error in
// get data
guard let data = data, error == nil else {
return
}
//convert data to model
do {
let model = try JSONDecoder().decode(WeatherModel.self, from: data)
DispatchQueue.main.async {
self.cityWeather = model
}
}
catch {
print(error)
}
}
task.resume()
}
}
型号
import Foundation
struct WeatherModel: Identifiable, Codable {
var id = UUID()
var name: String = ""
var main: CurrentWeather = CurrentWeather()
var weather: [WeatherInfo] = []
func firstWeatherInfo() -> String {
return weather.count > 0 ? weather[0].description : ""
}
}
struct CurrentWeather: Codable {
var temp: Float = 0.0
}
struct WeatherInfo: Codable {
var description: String = ""
}
详细视图
import SwiftUI
struct DetailView: View {
var detail: WeatherViewModel
var body: some View {
VStack(spacing: 20) {
Text(detail.cityWeather.name)
.font(.system(size: 32))
Text("\(detail.cityWeather.main.temp)")
.font(.system(size: 44))
Text(detail.cityWeather.firstWeatherInfo())
.font(.system(size: 24))
}
}
}
struct DetailView_Previews: PreviewProvider {
static var previews: some View {
DetailView(detail: WeatherViewModel.init())
}
}
【问题讨论】:
-
很遗憾看到你没有从你之前的问题中接受我的建议,适合你自己。您没有看到
NavigationLink的列表,因为列表中没有任何内容。请注意,您应该拥有@Published var cityNameList = [WeatherModel]()而不是@Published var cityNameList = [WeatherViewModel]()。在您的fetchWeather()中将结果(以 WeatherModel 的形式)添加到您的cityNameList。 -
感谢@workingdog!添加
@Published var cityNameList = [WeatherModel]()后,看起来 WeatherModel 需要符合可识别的。我该怎么做? -
使用这个:
struct WeatherModel: Identifiable, Codable { let id = UUID() ....}。不用担心这不会影响 json 解码(Xcode 会警告你)。 -
@workingdog 我更新了上面的代码以反映您对我之前问题的回答(参见上面的代码)。我还尝试将 viewModel.cityWeather 传递到列表中的 ForEach 中,但出现以下错误:
Generic struct 'ForEach' requires that 'WeatherModel' conform to 'RandomAccessCollection' -
@workingdog 我实施了您在以下示例中建议的修复,但城市列表项仍未填充到列表中。
标签: ios swift swiftui swiftui-list swiftui-navigationlink