SwiftUI中基于Picker选择联动加载对应数据的问题求助
问题描述
我有两个API分别用于获取大陆和国家数据,希望实现选择大陆后自动更新对应国家列表的功能。目前用Picker分别调用API,但无法根据选中的大陆选项触发国家数据更新。我是Swift编程新手,术语若有不当请见谅,恳请帮忙解决。最终预期还能实现:选中国家后加载对应州/省数据选项。
原ContentView代码
import SwiftUI struct ContentView: View { @StateObject private var ContinentView = ContinentListViewModel() @State private var isExpanded = false @State private var selected = "Asia" @StateObject private var CountryView = CountryViewModels() @State private var Expanding = false @State private var isselected = "india" var body: some View { VStack { Section{ Form{ Picker("Country*", selection: $isselected) { ForEach(CountryView.countries, id: \.id) { countries in Text(countries.value).tag(countries.value) } } .onAppear(perform: CountryView.fetchCountry) Picker("Continent*", selection: $selected) { ForEach(ContinentView.continents, id: \.id) { continents in Text(continents.value).tag(continents.value) .onTapGesture { self.selected = continents.value withAnimation { self.isExpanded.toggle() } } } } .onAppear(perform: ContinentView.loadData) } } } .padding() } }
原CountryViewModel代码
import Foundation import SwiftUI struct CountryList: Hashable, Codable { let id: Int let value: String } class CountryViewModels: ObservableObject { @Published var countries: [CountryList] = [] func fetchCountry() { guard let url = URL(string: "https://arvfsmmasterdatamicroservice.azurewebsites.net/api/MasterData/getCountriesByContinentId") else { return } let task = URLSession.shared.dataTask(with: url) { [weak self] data, _, error in guard let data = data, error == nil else { return } // convert to JSON do { let countries = try JSONDecoder().decode([CountryList].self, from: data) DispatchQueue.main.sync { self?.countries = countries } } catch { print(error) } } task.resume() } }
原ContinentViewModel代码
import Foundation import SwiftUI struct ContinentList: Hashable, Codable, Identifiable { let id: Int let value: String } class ContinentListViewModel: ObservableObject { @Published var continents: [ContinentList] = [] @StateObject private var ContinentView = ContinentListViewModel() // 多余的声明 func loadData() { guard let url = URL(string: "https://arvfsmmasterdatamicroservice.azurewebsites.net/api/MasterData/getContinents") else { return } let request = URLRequest(url: url) URLSession.shared.dataTask(with: request) { [weak self] data, continents, error in guard let data = data, error == nil else { return } //convert to JSON do { let continents = try JSONDecoder().decode([ContinentList].self, from: data) DispatchQueue.main.async { self?.continents = continents } } catch { print(error) } }.resume() } }
解决方案
核心问题分析
- 国家API需要传入大陆ID才能返回对应国家列表,但原代码中
fetchCountry方法未传递参数,导致API无法过滤数据。 - 未监听大陆选择的变化,无法触发国家数据的重新加载。
- ContinentViewModel中存在多余的
@StateObject声明,会引发逻辑错误。
分步修复代码
1. 修复ContinentViewModel
移除内部多余的@StateObject声明:
import Foundation import SwiftUI struct ContinentList: Hashable, Codable, Identifiable { let id: Int let value: String } class ContinentListViewModel: ObservableObject { @Published var continents: [ContinentList] = [] func loadData() { guard let url = URL(string: "https://arvfsmmasterdatamicroservice.azurewebsites.net/api/MasterData/getContinents") else { return } let request = URLRequest(url: url) URLSession.shared.dataTask(with: request) { [weak self] data, _, error in guard let data = data, error == nil else { return } do { let continents = try JSONDecoder().decode([ContinentList].self, from: data) DispatchQueue.main.async { self?.continents = continents } } catch { print(error) } }.resume() } }
2. 修改CountryViewModel,支持传入大陆ID
更新fetchCountry方法,接收大陆ID作为参数并拼接正确的API URL:
import Foundation import SwiftUI struct CountryList: Hashable, Codable, Identifiable { // 添加Identifiable,简化ForEach使用 let id: Int let value: String } class CountryViewModels: ObservableObject { @Published var countries: [CountryList] = [] // 修改方法,接收continentId参数 func fetchCountry(by continentId: Int) { // 拼接带参数的API URL guard let url = URL(string: "https://arvfsmmasterdatamicroservice.azurewebsites.net/api/MasterData/getCountriesByContinentId?continentId=\(continentId)") else { return } let task = URLSession.shared.dataTask(with: url) { [weak self] data, _, error in guard let data = data, error == nil else { return } do { let countries = try JSONDecoder().decode([CountryList].self, from: data) DispatchQueue.main.async { // 改用async,避免主线程阻塞 self?.countries = countries } } catch { print(error) } } task.resume() } }
3. 重构ContentView,实现联动逻辑
- 监听大陆选择的变化,触发国家数据加载
- 调整Picker的tag为大陆ID(而非名称),方便传递参数给API
- 初始化时加载大陆数据,并自动加载默认大陆的国家列表:
import SwiftUI struct ContentView: View { @StateObject private var continentVM = ContinentListViewModel() @StateObject private var countryVM = CountryViewModels() @State private var selectedContinentId: Int? = nil // 存储选中的大陆ID @State private var selectedCountry: String = "" var body: some View { VStack { Form { // 大陆选择Picker Picker("Continent*", selection: $selectedContinentId) { Text("请选择大陆").tag(nil as Int?) ForEach(continentVM.continents) { continent in Text(continent.value).tag(continent.id as Int?) } } .onAppear(perform: continentVM.loadData) // 监听大陆选择变化,加载对应国家 .onChange(of: selectedContinentId) { newId in guard let id = newId else { countryVM.countries = [] selectedCountry = "" return } countryVM.fetchCountry(by: id) selectedCountry = "" // 切换大陆后清空国家选择 } // 国家选择Picker(仅当有大陆选中时启用) Picker("Country*", selection: $selectedCountry) { Text("请先选择大陆").tag("") ForEach(countryVM.countries) { country in Text(country.value).tag(country.value) } } .disabled(selectedContinentId == nil) } } .padding() } }
后续扩展州/省数据的思路
按照同样的逻辑扩展即可:
- 创建
StateProvinceList模型和StateProvinceViewModel,实现根据国家ID获取州/省数据的方法 - 在ContentView中添加
@StateObject private var stateVM = StateProvinceViewModel()和@State private var selectedCountryId: Int? - 监听
selectedCountry的变化,调用州/省API加载数据 - 添加州/省选择Picker,绑定
selectedState
内容的提问来源于stack exchange,提问作者CodeBee
相关产品推荐
相关产品推荐

