为啥将 ViewModel 添加到我的 SwiftUI 应用程序后我的 UI 没有更新?

Posted

技术标签:

【中文标题】为啥将 ViewModel 添加到我的 SwiftUI 应用程序后我的 UI 没有更新?【英文标题】:Why doesn't my UI update after adding a ViewModel to my SwiftUI app?为什么将 ViewModel 添加到我的 SwiftUI 应用程序后我的 UI 没有更新? 【发布时间】:2021-04-30 13:35:16 【问题描述】:

我一直在关注Peter Friese YT series on SwiftUI & Firebase,从第 1 部分和第 2 部分开始。现在我正在尝试将我在那里学到的知识转移到我自己的小测试应用程序中,特别是在组合中添加 ViewModel

彼得的应用程序有一个单屏界面,而我想使用第二个屏幕为新元素输入几条数据,当用户单击“保存新元素”时,它被添加到数组中,然后返回到第一个屏幕。

在添加 ViewModel 代码之前,它完全按照预期工作:创建新元素、添加到数组、更新 UI。它也适用于更新现有元素。

添加ViewModel 代码后,它几乎可以工作了。创建新元素,添加到数组中。 但是,UI 没有更新,我不明白为什么不更新。

这是我的代码:

import Foundation
import SwiftUI
import Combine

struct Tournament: Identifiable 
    var id = UUID().uuidString
    var name: String
    var location: String = "Franchises"
    var date: Date = Date()


#if DEBUG
var blankTournament = Tournament(name: "")
var testTournamentData = [
    Tournament(name: "Tournament 1"),
    Tournament(name: "Tournament 2"),
    Tournament(name: "Tournament 3"),
]
#endif

struct ContentView: View 
    @ObservedObject var tournamentListVM = TournamentListVM()

    var body: some View 
        NavigationView 
            VStack 
                List(tournamentListVM.tournamentCellVMs)  tournamentCellVM  in
                    NavigationLink(
                        destination: TournamentDetails(tournament: tournamentCellVM.tournament),
                        label:  TournamentCell(tournamentCellVM: tournamentCellVM) 
)
            .navigationTitle("All in Two (\(tournamentListVM.tournamentCellVMs.count))")
            .navigationBarItems(
                trailing: NavigationLink(
                    destination: TournamentDetails(addingNewTournament: true))
                         Image(systemName: "plus") 
)

struct TournamentCell: View 
    @ObservedObject var tournamentCellVM: TournamentCellVM

    var body: some View 
        VStack(alignment: .leading) 
            Text(tournamentCellVM.tournament.name)
        
    


struct TournamentDetails: View 
    @Environment(\.presentationMode) var presentationMode // used to dismiss the view
    @ObservedObject var tournamentListVM = TournamentListVM()

    @State var tournament = blankTournament

    var addingNewTournament: Bool = false
    var index: Int? // needed when updating an existing record

    var body: some View 
        Form 
            Section(header: Text("Tournament Info")) 
                TextField("Name", text: $tournament.name)
                TextField("Location", text: $tournament.location)
                DatePicker(selection: $tournament.date, displayedComponents: .date)  Text("Date") 
            
            Section  Button(action: 
                if addingNewTournament   tournamentListVM.createTournament(tournament) 
                else                             tournamentListVM.updateTournament(tournament) 
                presentationMode.wrappedValue.dismiss()
            )  Text(addingNewTournament ? "Save Tournament" : "Update Tournament") 


class TournamentListVM: ObservableObject 
    @Published var tournamentCellVMs = [TournamentCellVM]()

    private var cancellables = Set<AnyCancellable>()

    init() 
        self.tournamentCellVMs = testTournamentData.map  tournament in
            TournamentCellVM(tournament: tournament)
        
    

    func createTournament(_ tournament: Tournament) 
        print (tournamentCellVMs.count)
        tournamentCellVMs.append(TournamentCellVM(tournament: tournament))
        print (tournamentCellVMs.count)
    

    func updateTournament(_ tournament: Tournament) 
    


class TournamentCellVM: ObservableObject, Identifiable 
    @Published var tournament: Tournament

    var id = ""

    private var cancellables = Set<AnyCancellable>()

    init(tournament: Tournament) 
        self.tournament = tournament

        $tournament
            .map  tournament in tournament.id 
            .assign(to: \.id, on: self)
            .store(in: &cancellables)

【问题讨论】:

【参考方案1】:

当我准备发布我的问题时,SO 向我指出了其他可能类似的问题。 One of them helped me find the answer to my question.我在这里发布我的解决方案,作为自己和他人的资源。

在那篇文章中,我了解到如果列表动态变化,那么您必须使用.indices

以下是我所做的更改,现在可以使用:

    struct ContentView:
List(tournamentListVM.tournamentCellVMs)  tournamentCellVM  in
    NavigationLink(
        destination: TournamentDetails(tournament: tournamentCellVM.tournament),
        label:  TournamentCell(tournamentCellVM: tournamentCellVM) 
)
.navigationTitle("All in Two (\(tournamentListVM.tournamentCellVMs.count))")
.navigationBarItems(
    trailing: NavigationLink(
    destination: TournamentDetails(addingNewTournament: true))
         Image(systemName: "plus") 
)

成为

List 
    ForEach (tournamentListVM.tournamentCellVMs.indices, id: \.self)  index in
        NavigationLink(
            destination: TournamentDetails(tournamentListVM: tournamentListVM, tournament: tournamentListVM.tournamentCellVMs[index].tournament),
            label:  TournamentCell(tournamentCellVM: tournamentListVM.tournamentCellVMs[index]) 
)
.navigationTitle("All in Two (\(tournamentListVM.tournamentCellVMs.count))")
.navigationBarItems(
    trailing: NavigationLink(
    destination: TournamentDetails(tournamentListVM: tournamentListVM, addingNewTournament: true))
         Image(systemName: "plus") 
)
    struct TournamentDetails:

@ObservedObject var tournamentListVM = TournamentListVM()

成为

@ObservedObject var tournamentListVM: TournamentListVM

    class TournamentListVM

@Published var tournamentCellVMs = [TournamentCellVM]()

成为

@Published var tournamentCellVMs: [TournamentCellVM]

就是这样。而且效果很好。

【讨论】:

以上是关于为啥将 ViewModel 添加到我的 SwiftUI 应用程序后我的 UI 没有更新?的主要内容,如果未能解决你的问题,请参考以下文章