Doug
Doug

Reputation: 3180

Updating the selected object in a SwiftUI List

When using a List is SwiftUI, I have code to update the selected object which works fine. However the change to the selected object is not reflected in the List until the selected object changes.

Here is a simplified version of the class my List is displaying.

class Item: ObservableObject {
    let id = UUID()
    @Published var name: String
    
    init(name: String) {
        self.name = name
    }
}

extension Item: Hashable {
    static func == (lhs: Item, rhs: Item) -> Bool {
        lhs.id == rhs.id
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}

And here is a simplified version of my view.

struct TestView: View {
    @State var items = [
        Item(name: "Item 1"),
        Item(name: "Item 2"),
        Item(name: "Item 3"),
        Item(name: "Item 4")
    ]
    @State var selectedItem: Item?
    
    var body: some View {
        VStack {
            List(items, id: \.self, selection: $selectedItem) { item in
                Text(item.name)
            }
            
            HStack {
                Button { items.append(Item(name: "New Item")) }
                    label: { Text("Add Item") }
                
                Button { selectedItem?.name += "a" }
                    label: { Text("Update Item") }
                    .disabled(selectedItem == nil)
            }
        }
        .padding()
    }
}

I was going to use the range 0..<items.count and update the object within the array, however this crashes when the number of items changes.

Screen recording: Screen recording

Upvotes: 1

Views: 2275

Answers (1)

Asperi
Asperi

Reputation: 257543

As your items have reference type (class Item) the references in State array are not changed when you modify each of objects, so view is not refreshed.

The possible solution to your model is to separate list row into standalone view that will observe corresponding item changes

Tested with Xcode 11.4 / macOS 10.15.6

struct ListRowView: View {
    @ObservedObject var item: Item

    var body: some View {
        Text(item.name)
    }
}

so main list becomes

List(items, id: \.self, selection: $selectedItem) { item in
    ListRowView(item: item)
}

Upvotes: 5

Related Questions