如何在 SwiftUI 列表中设置 NavigationLink

Posted

技术标签:

【中文标题】如何在 SwiftUI 列表中设置 NavigationLink【英文标题】:How to setup NavigationLink inside SwiftUI list 【发布时间】: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 我实施了您在以下示例中建议的修复,但城市列表项仍未填充到列表中。 【参考方案1】:

试试这个示例代码,对我很有效:

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

【讨论】:

以上是关于如何在 SwiftUI 列表中设置 NavigationLink的主要内容,如果未能解决你的问题,请参考以下文章

在 NavigationView 的 SwiftUI 列表中设置雪佛龙的颜色

如何在swiftUI中的List中设置空列背景颜色?

如何在 SwiftUI 中嵌入 ForEach 的 HStack 中设置相对宽度?

如何在 SwiftUI 中设置 addObserver?

如何在 SwiftUI 中设置 ScrollView 的 contentInset

在 SwiftUI 中,如何在 XcodePreview 中设置 editMode 的环境变量