0

So I have a View with List of subviews that take an object. These objects come from array that is in my data manager.

struct ContentView: View {
@ObservedObject var manager = AppConnectivityManager.shared

var body: some View {
    
        List {
            ForEach(manager.goals, id: \.id) { goal in
                let _ = print("goal in foreach: \(goal.goal.completitionCount) + title: \(goal.goal.title)")
                GoalView(goalWrapper:goal)
            }.listRowBackground(Color.clear)
        }
    
    }
}

This part works well and update is triggered every time I update the manager object.

The problem is that my GoalView(goalWrapper:goal) gets only updated once when the code runs first time but then when its supposed to be refreshed it stays the same. This print("progress: statement in the goal view prints always the same values but the print statement in the list gets the updates. I might be missing the concept but I though I can pass the ObservedObject down the subview and it will get updated when manager gets also updated but that is not the case.

struct GoalView: View {

@ObservedObject var goalWrapper: GoalWrapper

var body: some View {
    let _ = print("progress: \(self.goalWrapper.goal.completitionCount) + daily: \(self.goalWrapper.goal.dailyNotificationCount) + title: \(self.goalWrapper.goal.title)")
    ZStack(content: {
        GoalAnimationView(goalWrapper: self.goalWrapper).cornerRadius(10)
        VStack(alignment: .leading, spacing: nil, content: {
            Text(goalWrapper.goal.title).foregroundColor(.black).padding(.leading, 8)
                .padding(.trailing, 8)
                .padding(.top, 4)
            HStack(alignment: .center,content: {
                Text("\(goalWrapper.goal.completitionCount)/\(goalWrapper.goal.dailyNotificationCount)").padding(.top, 2).padding(.trailing, 85).padding(.bottom, 6)
                Text("\(goalWrapper.goal.completitionCount)").padding(.top, 2).padding(.trailing, 12).padding(.bottom, 12).font(.title)
            }).frame(minWidth: 0, maxWidth: .infinity, minHeight: 0,maxHeight: 35,alignment: .trailing).background(Color.clear)


        }).listRowPlatterColor(Color.blue).frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, alignment: .leading).cornerRadius(10)
        
    })
  }
}

Here is the GoalWrapper class

public class GoalWrapper: ObservableObject, Hashable {

    public static func == (lhs: GoalWrapper, rhs: GoalWrapper) -> Bool {
        return lhs.goal.id == rhs.goal.id
    }

    public func hash(into hasher: inout Hasher) {
         hasher.combine(goal.id)
    }

    @Published var goal: Goal!
    let id: String
    init(goal: Goal) {
        self.goal = goal
        self.id = goal.id
   }
}

Is there some way How I can pass the updates to the list subviews? I have found some other people having issues with lists and passing a binding to the views in list. Is that the only way ?

beowulf
  • 546
  • 1
  • 10
  • 16
  • Get rid of your wrapper and just pass your `Goal` as a `struct` (I hope it's a `struct`). – jnpdx Aug 02 '21 at 16:45

1 Answers1

1

You don't need the GoalWrapper. You should pass goal as a Binding<Goal> from ContentView to GoalView.

If you get warnings where the ForEach is, you have the id property on Goal but may need to conform to the Identifiable protocol. Otherwise add the , id: \.id back.

Code:

struct ContentView: View {
    @ObservedObject var manager = AppConnectivityManager.shared

    var body: some View {
        List {
            ForEach($manager.goals) { $goal in
                let _ = print("goal in foreach: \(goal.completitionCount) + title: \(goal.title)")
                GoalView(goal: $goal)
            }.listRowBackground(Color.clear)
        }
    }
}
struct GoalView: View {
    @Binding var goal: Goal

    var body: some View {
        let _ = print("progress: \(goal.completitionCount) + daily: \(goal.dailyNotificationCount) + title: \(goal.title)")
        ZStack(content: {
            //GoalAnimationView(goalWrapper: self.goalWrapper).cornerRadius(10) // This needs changing too
            VStack(alignment: .leading, spacing: nil, content: {
                Text(goal.title).foregroundColor(.black).padding(.leading, 8)
                    .padding(.trailing, 8)
                    .padding(.top, 4)
                HStack(alignment: .center,content: {
                    Text("\(goal.completitionCount)/\(goal.dailyNotificationCount)").padding(.top, 2).padding(.trailing, 85).padding(.bottom, 6)
                    Text("\(goal.completitionCount)").padding(.top, 2).padding(.trailing, 12).padding(.bottom, 12).font(.title)
                }).frame(minWidth: 0, maxWidth: .infinity, minHeight: 0,maxHeight: 35,alignment: .trailing).background(Color.clear)


            }).listRowPlatterColor(Color.blue).frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, alignment: .leading).cornerRadius(10)

        })
    }
}
George
  • 25,988
  • 10
  • 79
  • 133
  • 1
    I could not for some reason pass the binding directly to for each so instead I made this workaround `ForEach((0...manager.normalGoals.count - 1), id: \.self) {}` and then just `GoalView(goal: $manager.normalGoals[$0])` – beowulf Aug 02 '21 at 19:25
  • @beowulf It's part of Swift 5.5 I believe in Xcode 13. The way you do it by index should be fine, though just be sure to change your range to `0 ..< manager.normalGoals.count` instead otherwise the app will crash if the array is empty because it will create an invalid range. – George Aug 02 '21 at 19:30
  • 1
    @beowulf [Here](https://www.swiftbysundell.com/articles/bindable-swiftui-list-elements/#xcode-s-new-element-binding-syntax) is an article about that new syntax, and how you can make it work with older versions of Swift (like you did). Does not depend on OS version. Also, if you found the answer useful, an accept & upvote would be appreciated! – George Aug 02 '21 at 19:34
  • Do you know by any chance if there is some trick If I wanna delete item from the array and refresh the list? So far its crashing with index out of range. I guess its because its removed while its being iterated. – beowulf Aug 04 '21 at 20:22
  • @beowulf See my answer [here](https://stackoverflow.com/a/64514440/9607863). Try the `Binding(...)` part of that answer, and that should fix it. If that doesn't work, feel free to post a new question. – George Aug 04 '21 at 20:33