Swiftui:引用相同观察到的对象时,数组在所有视图中均不更新

发布于 2025-01-24 18:00:27 字数 11785 浏览 5 评论 0原文

我是一个相当新手的开发人员。我编写了一个用于跟踪室内抱石的应用程序,全部爬上了一个非常大的文件。它运行良好,很难维护。现在,我正在努力将其分成多个文件并遇到一些问题。

我创建了此视图模型,以维护所有要更新的变量,然后由任何视图使用。这是我分开的第一件,当我所有的观点仍然在同一文件中时,它都可以正常工作。

首先,我尝试将我的标题在另一个文件中单击一个按钮时将标题更新到一个文件中,而标题值在我所谓的“ variablesetupmodel”中保持。我得到了帮助 71975332”一个>现在正在起作用。我试图将对文本变量所做的工作扩展到我在同一文件中维护的数组。这是我现在的问题的相关部分。

文件:

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

当应用首次构建时,variablesetupmodel所有内容都正确加载。它从Coredata中拉出完整列表,并将其写入本地变量攀登供我操纵。但是,当我将新的攀登保存到数组中时,它会正确地写入核心数据,但是我的图形都完全相同。当我关闭应用程序并再次构建时,我添加了新的攀爬,然后加载。我正在使用相同的功能来加载页面加载的图形,就像我点击“保存”重新加载时,我一直坚持如何无法工作。我已经花了几天的时间了,没有取得任何进展。

这是增加新攀登的视图。

文件:

@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")
                }
}

PRINT仅在加载页面时仅显示数组中的最后一项。不包括contentView.AddNewClimb的新值(等级:ViewModel.SeledectedGrade,尝试:viewModel.AttEmptscount,状态:viewModel.completeStatus)

我在写新的攀登> to > contentview.addnewclimb所以我去那里看看什么viewmodel.climbs看起来像是那里的

文件:contentView

contentview.addnewclimb()

loadclimbs()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 function code> code> print> /代码>打印正确的最新攀登。因此,它是正确写给Coredata的。然后,我调用loadClimbs()写入variableseTupModel()。攀登

在我的LoadClimbs函数中,print(“查看模型更新:”,viewModel.climbs.first!)也反映了正确的值。

因此顺序: 我点击提交,将新的攀爬写入核心数据,然后将新的核心数据值写入我的loadclimbs()函数中的viewModel.climbs,但仍然有效。然后,我的提交按钮中的最后一个操作是打印相同的ViewModel.Climbs值,并且它仅具有出现在页面加载上的数组的版本,而不是loadclimbs()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!)])
                                    }
                                }
                            }
                        }
                    }
                }
        }
    }
}

I am a fairly novice developer. I wrote an app for tracking indoor bouldering climbs all in 1 really large file. It was working fine, just hard to maintain. Now I am working on splitting it into multiple files and having some issues.

I created this view model to maintain all variables that are to be updated and then used by any view. This was the first piece I split out, and it worked just fine when all of my views were still in the same file.

I started by trying to get my titles to update in one file when a button was clicks in another file, with the title value maintained in what I called "VariableSetupModel". I got help here and now that is working. I tried to extend what I did for the text variable to an array I am maintaining in the same file. Here is the relevant part of this to my issue now.

File: VariableSetupModel

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

Everything loads correctly when the app first builds. It pulls in the full list from CoreData and writes it to a local variable climbs for me to manipulate. But when I save a new climb to the array, it write to core data correctly, but my graphs all stay exactly the same. When I close the app and build again, the new climb I added then loads. I am using the same functions to load the graph on page load as I am when I hit save to reload, so I am stuck on how it's not working. I've spent a few days on this and made no progress.

Here is the view that adds a new climb.

File: CurrentProjectView

@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")
                }
}

That print only shows the last item in the array when the page loaded. Does not include the new value added by contentView.addNewClimb(grade: viewModel.selectedGrade, attempts: viewModel.attemptsCount, status: viewModel.completeStatus)

I am printing after I write the new climb to contentView.addNewClimb so I went there to see what viewModel.climbs looks like there

File: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()
    }

In my addNewClimb functionprint(newClimb) prints the correct latest climb. So it is written to CoreData Correctly. Then I call loadClimbs() to write to VariableSetupModel().climbs.

In my loadClimbs function, print("View model updated:", viewModel.climbs.first!) also reflects the correct value.

So in order:
I hit submit, writes the new climb to core data, I then write the new core data values to viewModel.climbs in my loadClimbs() function and that still works. And then the last action in my submit button is to print the same viewModel.climbs value, and it only has a version of the array that appeared on page load, not the same version of the array printed in loadClimbs(). Therefore, my TodaysSessionView() view also does not get an updated version of viewModel.climbs either, which is what I ultimately want.

I have been running in circles around this and cannot figure it out. Any help would be appreciated. Thank you.

Edit: Here is the view where viewModel.climbs is bring used. It ends up looking like this
enter image description here

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!)])
                                    }
                                }
                            }
                        }
                    }
                }
        }
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

永言不败 2025-01-31 18:00:27

好吧,我让它起作用。我要做的就是将自己的视图中的功能移至ViewModel。我猜想将所有内容保持在同一范围内。我的猜测是ContentView中的所有内容都是它自己的副本。我不是100%确定的,但是现在可以使用。

Ok I got it working. All I had to do was move the functions I had in my view to my viewModel. Im guessing to keep everything in the same scope. My guess is that everything in ContentView was it's own copy. I'm not 100% certain on that, but it works now.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文