SwiftUI:当引用观察到的相同内容时,数组不会在所有视图中更新 Object

SwiftUI: Array Not Updating In All Views When Referencing The Same Observed Object

我是一个相当新手的开发者。我在一个非常大的文件中编写了一个用于跟踪室内抱石攀登的应用程序。它工作正常,只是很难维护。现在我正在努力将它分成多个文件并遇到一些问题。

我创建这个视图模型是为了维护所有要更新然后由任何视图使用的变量。这是我拆分出来的第一块,当我所有的视图都在同一个文件中时,它工作得很好。

我开始尝试让我的标题在另一个文件中单击按钮时在一个文件中更新,标题值保留在我所谓的“VariableSetupModel”中。我得到了帮助 ,现在一切正常。我试图将我对文本变量所做的操作扩展到我在同一文件中维护的数组。现在这是与我的问题相关的部分。

文件:VariableSetupModel

final class VariableSetupModel: ObservableObject {
    //static let shared = VariableSetupModel()
    
    //Climbs List
    @Published var climbs:[NSManagedObject] = []
}

首次构建应用程序时,一切都正确加载。它从 CoreData 中提取完整列表并将其写入局部变量 climbs 供我操作。但是当我将新的攀登保存到阵列时,它会正确写入核心数据,但我的图表都保持完全相同。当我关闭应用程序并再次构建时,我添加的新攀登然后加载。我使用相同的函数在页面加载时加载图表,就像我点击保存重新加载时一样,所以我一直在思考它是如何不工作的。我在这上面花了几天时间,但没有取得任何进展。

这里是添加新爬升的视图。

文件:当前项目视图

@EnvironmentObject var viewModel: VariableSetupModel
    
let contentView = ContentView()
    
var body: some View {

Button(action:{
                    contentView.addNewClimb(grade: viewModel.selectedGrade, attempts: viewModel.attemptsCount, status: viewModel.completeStatus)
                    
                    contentView.updateGraphArrays()
                    
                    print("submit button pressed: \(viewModel.climbs.first!)")
                }){
                    Text("Save")
                }
}

该打印仅显示页面加载时数组中的最后一项。不包括 contentView.addNewClimb(grade: viewModel.selectedGrade, attempts: viewModel.attemptsCount, status: viewModel.completeStatus)

增加的新值

我把新的爬升写到 contentView.addNewClimb 之后我正在打印所以我去那里看看 viewModel.climbs 看起来像什么

文件:ContentView

contentView.addNewClimb()

loadClimbs()

@ObservedObject var viewModel = VariableSetupModel()

var body: some View {
    VStack (alignment: .leading) {
        TodaysSessionView()

        CurrentProjectView().onAppear{
                    self.loadClimbs()
                    self.updateGraphArrays()
                }
    }.environmentObject(viewModel)
}


func addNewClimb(grade: Int, attempts: Int, status: String){
        guard let appDelegate =
            UIApplication.shared.delegate as? AppDelegate else {
                return
        }
        
        //where we are saving
        let managedContext =
            appDelegate.persistentContainer.viewContext
        
        // type of entity we are creating
        let entity =
            NSEntityDescription.entity(forEntityName: "Climb",
                                       in: managedContext)!
        
        //creating one new instance
        let newClimb = NSManagedObject(entity: entity,
                                       insertInto: managedContext)
        
        
        //setting the value of each property
        newClimb.setValue(grade, forKeyPath: "grade")
        newClimb.setValue(attempts, forKeyPath: "attempts")
        newClimb.setValue(status, forKeyPath: "passfail")
        newClimb.setValue(Date(), forKeyPath: "climbdate")
    
        do {
            //try to save
            try managedContext.save()
            print("saved successfully!!!!")
            print(newClimb)
            
            self.loadClimbs()
        } catch let error as NSError {
            //if cannot save then print error
            print("Could not save. \(error), \(error.userInfo)")
        }
        
    }

func loadClimbs(){
        guard let appDelegate =
            UIApplication.shared.delegate as? AppDelegate else {
                return
        }
        
        let managedContext =
            appDelegate.persistentContainer.viewContext
        
        let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Climb")
        
        //sort
        let sort = NSSortDescriptor(key: "climbdate", ascending: false)
        fetchRequest.sortDescriptors = [sort]
        
        do {
            //set the climbs variable to all values
            viewModel.climbs = try managedContext.fetch(fetchRequest)
            
            print("View model updated:", viewModel.climbs.first!)

        } catch let error as NSError {
            print("Could not fetch. \(error), \(error.userInfo)")
        }
        
        self.updateGraphArrays()
    }

在我的 addNewClimb 函数中print(newClimb) 打印正确的最新攀登。所以它被正确地写入了CoreData。然后我调用 loadClimbs() 写入 VariableSetupModel().climbs.

在我的 loadClimbs 函数中,print("View model updated:", viewModel.climbs.first!) 也反映了正确的值。

所以顺序是: 我点击提交,将新的攀登写入核心数据,然后我将新的核心数据值写入我的 loadClimbs() 函数中的 viewModel.climbs 并且仍然有效。然后我的提交按钮中的最后一个操作是打印相同的 viewModel.climbs 值,它只有页面加载时出现的数组版本,而不是 loadClimbs() 中打印的数组版本.因此,我的 TodaysSessionView() 视图也没有得到 viewModel.climbs 的更新版本,这正是我最终想要的。

我一直运行围绕着这个问题转了一圈,想不通。任何帮助,将不胜感激。谢谢。

编辑:这里是使用 viewModel.climbs 的视图。它最终看起来像这样

struct TodaysSessionView: View {
    @EnvironmentObject var viewModel: VariableSetupModel
    let contentView = ContentView()
    
    var body: some View {
        let layout = [
            GridItem(.adaptive(minimum: 50)),
            GridItem(.adaptive(minimum: 50)),
            GridItem(.adaptive(minimum: 50)),
            GridItem(.adaptive(minimum: 50)),
            GridItem(.adaptive(minimum: 50))
        ]

        //Get last climb time
        let lastClimb = (viewModel.climbs.first as? Climb)?.climbdate
        
        if lastClimb ?? Date() < Date().addingTimeInterval(-28800) {
            Text("Last Session")
                .font(.title)
                .padding(.top)
                
        }else{
            Text("Todays Session")
                .font(.title)
                .padding(.top)
        }
        
        ScrollView{
            
                LazyVGrid (columns: layout, spacing: 15) {
                    ForEach(viewModel.climbs.reversed(), id: \.self){ thisClimb in
                        let gradeText = (thisClimb as? Climb)?.grade
                        let attemptsText = (thisClimb as? Climb)?.attempts
                        let passfailText = (thisClimb as? Climb)?.passfail ?? "climb string err"
                        let climbdateText = (thisClimb as? Climb)?.climbdate
                        
                        //how far back to pull into this. Maybe it should be -12 hours from the last climb
                        let cutoff = lastClimb!.addingTimeInterval(-28800)
                     
                        if climbdateText! > cutoff {
                            if passfailText == "Pass" {
                                VStack{
                                    ZStack {
                                        if attemptsText == 1 {
                                            Circle()
                                                .fill(Color("whiteblack-bg"))
                                                .frame(width: 50, height: 50)
                                                .overlay(RoundedRectangle(cornerRadius: 25)
                                                .strokeBorder(viewModel.gradesColor[Int(gradeText!)], lineWidth: 3))

                                            ZStack{
                                                Circle()
                                                    .fill(viewModel.gradesColor[Int(gradeText!)])
                                                    .frame(width: 22, height: 22)
                                                    
                                                Circle()
                                                    .fill(Color("whiteblack-bg"))
                                                    .frame(width: 18, height: 18)
                                                
                                                Image(systemName: "bolt.fill")
                                                    .foregroundColor(Color("flashColor"))
                                                    .font(.footnote)
                                            }.offset(x: 0, y: 23)
                                            
                                        }else{
                                            Circle()
                                                .fill(Color("whiteblack-bg"))
                                                .frame(width: 50, height: 50)
                                                .overlay(RoundedRectangle(cornerRadius: 25)
                                                .strokeBorder(viewModel.gradesColor[Int(gradeText!)], lineWidth: 3))
                                            
                                            ZStack{
                                                Circle()
                                                    .fill(viewModel.gradesColor[Int(gradeText!)])
                                                    .frame(width: 22, height: 22)
                                                
                                                Circle()
                                                    .fill(Color("whiteblack-bg"))
                                                    .frame(width: 18, height: 18)
                                                    
                                                Text("\(attemptsText!)")
                                                    .foregroundColor(Color("whiteblack"))
                                                    .font(.footnote)
                                            }.offset(x: 0, y: 23)
                                        }
                                        
                                        Text(viewModel.gradesV[Int(gradeText!)])
                                    }
                                }
                            }else{
                                //Didnt pass
                                VStack{
                                    ZStack{
                                        Circle()
                                            .fill(Color("whiteblack-bg"))
                                            .frame(width: 50, height: 50)
                                            .overlay(RoundedRectangle(cornerRadius: 25)
                                                        .strokeBorder(viewModel.gradesColor[Int(gradeText!)], lineWidth: 3))
                                        
                                        ZStack{
                                            Circle()
                                                .fill(Color(.red))
                                                .frame(width: 22, height: 22)
                                                
                                            
                                            
                                            Image(systemName: "xmark")
                                                .foregroundColor(.white)
                                                .font(.footnote)
                                        }.offset(x: 0, y: 22)
                                        
                                        Text(viewModel.gradesV[Int(gradeText!)])
                                    }
                                }
                            }
                        }
                    }
                }
        }
    }
}

好的,我成功了。我所要做的就是将我在视图中拥有的功能移动到我的 viewModel 中。我猜想将所有内容都保持在同一范围内。我的猜测是 ContentView 中的所有内容都是它自己的副本。我对此不是 100% 确定,但它现在有效。