0

I have an existing list, but I want to be able to add new items to it. Right now, I am using @EnvironmentObject, but when I add an element to the array, the view is not updated? I've seen solutions on the internet where you use objectWillChange.send(), but as a beginner in Swift, I don't know how to manipulate it to do what I want.

Class

class ChecklistObject: ObservableObject {
    @Published var description: String
    @Published var complete: Bool
    let ID: Int
    
    init(_ desc: String, _ complete: Bool, ID: Int){
        description = desc
        self.complete = complete
        self.ID = ID
    }
}

class Event: ObservableObject {
    @Published var Name: String
    @Published var CalendarID: Int
    var timeStart: Date
    var timeEnd: Date
    @Published var checklist = [ChecklistObject]()
    @Published var checklistSize = 0
    
    init(_ eventName: String, _ calID: Int, _ timeStart: Date, _ timeEnd: Date) {
        Name = eventName
        CalendarID = calID
        self.timeStart = timeStart
        self.timeEnd = timeEnd
        logger.log("Successfully created new event")
    }
    
    func newChecklistItem(Content: String){
        objectWillChange.send()
        checklist.append(ChecklistObject(Content, false, ID: getChecklistSize()))
        //checklistSize = checklistSize + 1
    }
    
    func getChecklistSize() -> Int {
        return checklist.count
    }
}

List

VStack (alignment: .leading) {
    Text("Checklist")
        .font(.title)
        .bold()
    ForEach(event.checklist.indices) { idx in
        ChecklistDisplayRow()
            .environmentObject(event.checklist[idx])
    }
    Spacer()
        .frame(width: 360, height: 10)
    Button(action: {
        event.newChecklistItem(Content: "New item")
        event.checklistSize = event.checklistSize + 1
    }) {
        HStack{
            if #available(OSX 11.0, *) {
                Image(systemName: "plus.circle")
            } else {
                Path{ path in
                    path.move(to: CGPoint(x: 10, y: 20))
                    path.addLine(to: CGPoint(x: 10, y:0))
                    path.move(to:CGPoint(x: 0, y: 10))
                    path.addLine(to: CGPoint(x: 20, y: 10))
                }
            }
            Text("Add new item")
                .font(.caption)
        }
    }
    .buttonStyle(PlainButtonStyle())
}

Thanks in advance.

i3ta
  • 11
  • 2
  • Your `checklist` is published, so it is not needed to call `objectWillChange.send()`, because it is sent automatically. I assume the issue is somewhere in injection Event object. Would you show complete code of view have List and parent one where it is created? – Asperi Jan 03 '21 at 07:29

1 Answers1

0

It's because of the version of ForEach that you are using. This version:

init(_ data: Range<Int>, content: @escaping (Int) -> Content)

"only reads the initial value of the provided data and doesn’t need to identify views across updates." (Apple's docs)

To get dynamic updating, you need to use one of the other forms of ForEach.

    ForEach(event.checklist, id: \.ID) { item in
        ChecklistDisplayRow()
            .environmentObject(item)
    }

You could even change the ID property of your ChecklistObject to id and then add Identifiable conformance.

    ForEach(event.checklist) { item in
        ChecklistDisplayRow()
            .environmentObject(item)
    }

(And though it's hard to say since we don't see the rest of your code, but does the item really need to be passed in as an EnvironmentObject or could it be done in the initializer for ChecklistDisplayRow?)

Patrick Wynne
  • 1,864
  • 15
  • 20
  • I passed it as an `EnvironmentObject` because I want to be able to change the value in the child view, but have it stored in the parent view because I want all of the data to be shared throughout the app. Thanks for your help! – i3ta Jan 03 '21 at 07:03