简体   繁体   English

如何在 SwiftUI 列表中设置 NavigationLink

[英]How to setup NavigationLink inside SwiftUI list

I am attempting to set up a SwiftUI weather app.我正在尝试设置 SwiftUI 天气应用程序。 when the user searches for a city name in the textfield then taps the search button, a NavigationLink list item should appear in the list.当用户在文本字段中搜索城市名称然后点击搜索按钮时,导航链接列表项应出现在列表中。 Then, the user should be able to click the navigation link and re-direct to a detail view.然后,用户应该能够单击导航链接并重新定向到详细信息视图。 My goal is to have the searched navigation links to populate a list.我的目标是让搜索到的导航链接填充列表。 However, my search cities are not populating in the list, and I'm not sure why.但是,我的搜索城市没有出现在列表中,我不知道为什么。 In ContentView, I setup a list with a ForEach function that passes in cityNameList, which is an instance of the WeatherViewModel.在 ContentView 中,我设置了一个带有 ForEach 函数的列表,该函数传入 cityNameList,它是 WeatherViewModel 的一个实例。 My expectation is that Text(city.title) should display as a NavigationLink list item.我的期望是 Text(city.title) 应该显示为 NavigationLink 列表项。 How should I configure the ContentView or ViewModel to populate the the list with NavigationLink list items?我应该如何配置 ContentView 或 ViewModel 以使用 NavigationLink 列表项填充列表? See My code below:请参阅下面的我的代码:

ContentView内容视图

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()
    }
}

ViewModel视图模型

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()
    }
}

Model模型

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 = ""
}

DetailView详细视图

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())
    }
}

try something like this example code, works well for me:试试这个示例代码,对我来说效果很好:

struct WeatherModel: Identifiable, Codable {
    let 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 = ""
}

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.cityNameList) { city in
                        NavigationLink(destination: DetailView(detail: city)) {
                            HStack {
                                Text(city.name).font(.system(size: 32))
                            }
                        }
                    }
                }
                Spacer()
            }.navigationTitle("Weather MVVM")
        }.navigationViewStyle(.stack)
    }
}

struct DetailView: View {
    var detail: WeatherModel
    
    var body: some View {
        VStack(spacing: 20) {
            Text(detail.name).font(.system(size: 32))
            Text("\(detail.main.temp)").font(.system(size: 44))
            Text(detail.firstWeatherInfo()).font(.system(size: 24))
        }
    }
}

class WeatherViewModel: ObservableObject {
    @Published var cityNameList = [WeatherModel]()
    
    func fetchWeather(for cityName: String) {
        guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=\(cityName)&units=imperial&appid=YOURKEY") else { return }
        
        let task = URLSession.shared.dataTask(with: url) { data, _, error in
            guard let data = data, error == nil else { return }
            do {
                let model = try JSONDecoder().decode(WeatherModel.self, from: data)
                DispatchQueue.main.async {
                    self.cityNameList.append(model)
                }
            }
            catch {
                print(error) // <-- you HAVE TO deal with errors here
            }
        }
        task.resume()
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM