Как настроить навигационную ссылку в списке SwiftUI

#ios #swift #swiftui #swiftui-list #swiftui-navigationlink

Вопрос:

Я пытаюсь настроить приложение погоды SwiftUI. когда пользователь ищет название города в текстовом поле, а затем нажимает кнопку поиска, в списке должен появиться элемент списка навигационных ссылок. Затем пользователь должен иметь возможность щелкнуть ссылку навигации и перейти к подробному просмотру. Моя цель состоит в том, чтобы поисковые навигационные ссылки заполняли список. Однако мои поисковые города не заполняются в списке, и я не уверен, почему. В contentView я настраиваю список с функцией ForEach, которая передается в cityNameList, который является экземпляром модели WeatherViewModel. Я ожидаю, что текст(город.название) должен отображаться в качестве элемента списка навигационных ссылок. Как я должен настроить представление содержимого или модель представления для заполнения списка элементами списка навигационных ссылок? Смотрите Мой код ниже:

Представление содержимого

 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)amp;units=imperialamp;appid=lt;MyAPIKeygt;") 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() -gt; String {  return weather.count gt; 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())  } }  

Комментарии:

1. грустно видеть, что вы не последовали моему совету из вашего предыдущего вопроса, как вам будет угодно. Вы не видите список NavigationLink , потому что у вас ничего нет в списке. Обратите внимание, что вы @Published var cityNameList = [WeatherModel]() не должны были @Published var cityNameList = [WeatherViewModel]() этого делать . В вашем fetchWeather() добавьте результаты (в виде модели погоды) в свой cityNameList .

2. Спасибо @workingdog! После добавления @Published var cityNameList = [WeatherModel]() , похоже, что модель погоды должна будет соответствовать определяемой. Как я могу это сделать?

3. используйте это: struct WeatherModel: Identifiable, Codable { let id = UUID() ....} . Не волнуйтесь, это не повлияет на декодирование json (Xcode предупредит вас об этом).

4. @workingdog Я обновил свой код выше, чтобы отразить ваш ответ на мой предыдущий вопрос (см. Код выше). Я также попытался передать ViewModel.cityWeather в список ForEach в списке, но я получаю следующую ошибку: Generic struct 'ForEach' requires that 'WeatherModel' conform to 'RandomAccessCollection'

5. @workingdog Я реализовал исправления, которые вы предложили в приведенном ниже примере, но элементы списка городов по-прежнему не заполняются в списке.

Ответ №1:

попробуйте что-нибудь вроде этого примера кода, который хорошо работает для меня:

 struct WeatherModel: Identifiable, Codable {  let id = UUID()  var name: String = ""  var main: CurrentWeather = CurrentWeather()  var weather: [WeatherInfo] = []    func firstWeatherInfo() -gt; String {  return weather.count gt; 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)amp;units=imperialamp;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) // lt;-- you HAVE TO deal with errors here  }  }  task.resume()  } }