【发布时间】:2022-08-14 17:22:03
【问题描述】:
我的目标是DetailView() 中的更改数据。通常在这种情况下,我使用@State + @Binding 并且它可以很好地处理静态数据,但是当我尝试使用来自网络请求的数据更新 ViewModel 时,我失去了@State 的功能(新数据不会传递给@State 值,它保持为空)。我检查了网络请求和解码过程 - 一切正常。抱歉,我的代码示例有点长,但这是我发现重现问题的最短方法...
楷模:
struct LeagueResponse: Decodable {
var status: Bool?
var data: [League] = []
}
struct League: Codable, Identifiable {
let id: String
let name: String
var seasons: [Season]?
}
struct SeasonResponse: Codable {
var status: Bool?
var data: LeagueData?
}
struct LeagueData: Codable {
let name: String?
let desc: String
let abbreviation: String?
let seasons: [Season]
}
struct Season: Codable {
let year: Int
let displayName: String
}
视图模型:
class LeagueViewModel: ObservableObject {
@Published var leagues: [League] = []
init() {
Task {
try await getLeagueData()
}
}
private func getLeagueData() async throws {
let (data, _) = try await URLSession.shared.data(from: URL(string: \"https://api-football-standings.azharimm.site/leagues\")!)
guard let leagues = try? JSONDecoder().decode(LeagueResponse.self, from: data) else {
throw URLError(.cannotParseResponse)
}
await MainActor.run {
self.leagues = leagues.data
}
}
func loadSeasons(forLeague id: String) async throws {
let (data, _) = try await URLSession.shared.data(from: URL(string: \"https://api-football-standings.azharimm.site/leagues/\\(id)/seasons\")!)
guard let seasons = try? JSONDecoder().decode(SeasonResponse.self, from: data) else {
throw URLError(.cannotParseResponse)
}
await MainActor.run {
if let responsedLeagueIndex = leagues.firstIndex(where: { $0.id == id }),
let unwrappedSeasons = seasons.data?.seasons {
leagues[responsedLeagueIndex].seasons = unwrappedSeasons
print(unwrappedSeasons) // successfully getting and parsing data
}
}
}
}
意见:
struct ContentView: View {
@StateObject var vm = LeagueViewModel()
var body: some View {
NavigationView {
VStack {
if vm.leagues.isEmpty {
ProgressView()
} else {
List {
ForEach(vm.leagues) { league in
NavigationLink(destination: DetailView(league: league)) {
Text(league.name)
}
}
}
}
}
.navigationBarTitle(Text(\"Leagues\"), displayMode: .large)
}
.environmentObject(vm)
}
}
struct DetailView: View {
@EnvironmentObject var vm: LeagueViewModel
@State var league: League
var body: some View {
VStack {
if let unwrappedSeasons = league.seasons {
List {
ForEach(unwrappedSeasons, id: \\.year) { season in
Text(season.displayName)
}
}
} else {
ProgressView()
}
}
.onAppear {
Task {
try await vm.loadSeasons(forLeague: league.id)
}
}
.navigationBarTitle(Text(\"League Detail\"), displayMode: .inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
ChangeButton(selectedLeague: $league)
}
}
}
}
struct ChangeButton: View {
@EnvironmentObject var vm: LeagueViewModel
@Binding var selectedLeague: League // if remove @State the data will pass fine
var body: some View {
Menu {
ForEach(vm.leagues) { league in
Button {
self.selectedLeague = league
} label: {
Text(league.name)
}
}
} label: {
Image(systemName: \"calendar\")
}
}
}
主要目标:
- 在
DetailView()中显示选定的联赛赛季数据 - 当在
ChangeButton()中选择另一个联赛时,可以更改DetailView()中的赛季数据
-
我会在详细视图中使 vm 成为 @ObservedObject 并从父视图中传递它
-
@JoakimDanielson 将
@EnvironmentObject更改为@ObservedObject并将@StateObject传递给视图 - 结果相同,没有任何变化
标签: swift swiftui async-await binding state