sgtpotatoe
sgtpotatoe

Reputation: 430

SwiftUI is it possible to observe additions to the "selection" variable that is passed to selectable lists in EditMode

I´m making a pretty simple use of the swiftUI List and EditMode in my project. It´s something like:

    @State var selection = Set<UUID>() 

    List(selection: $selection) {
                ForEach(items) { item in
                          Text(item.title)
                    }
    }

So, when EditMode is activated and items in the list get pressed selection Set gets updated. What I would like is to observe these changes, more precisely, the size of the Set. For this I added property observers as such:

    willSet {
        let newCount = newValue.count
        updateButtonText(count: newCount)
    }
    
    didSet {
        let newCount = selection.count
        updateButtonText(count: newCount)
    }

But this only gets called when I reset the selection property. But not when items get selected. So I will assume appending items to a Set doesn't trigger the property observers, or perhaps its something more intrinsic as the selection parameter is handled behind the scenes by List.

The question however, remains the same, how can I keep a live counter of the selected items through EditMode? (I wouldn't want to build my own Edit Mode)

Upvotes: 0

Views: 367

Answers (1)

jnpdx
jnpdx

Reputation: 52555

The most straightforward approach is probably using onChange:


struct ContentView : View {
    @State var selection = Set<UUID>()
    @State var items : [Item] = [.init(title: "Test1"),
                                 .init(title: "Test2"),
                                 .init(title: "Test3")]
    
    var body: some View {
        NavigationView {
            List(selection: $selection) {
                        ForEach(items) { item in
                                  Text(item.title)
                            }
            }
            .navigationBarItems(trailing: EditButton())
            .onChange(of: selection) { newSelection in
                print(newSelection.count)
            }
        }
    }
}

That being said, if all you're doing is updating a label, you don't even need that -- the view will be re-rendered when @State var selection changes, so you can just do:

struct ContentView : View {
    @State var selection = Set<UUID>()
    @State var items : [Item] = [.init(title: "Test1"),
                                 .init(title: "Test2"),
                                 .init(title: "Test3")]
    
    var body: some View {
        NavigationView {
            VStack {
                Text("Number: \(selection.count)")
                List(selection: $selection) {
                    ForEach(items) { item in
                              Text(item.title)
                        }
                }
            }
            .navigationBarItems(trailing: EditButton())
        }
    }
}

Finally, just for completion, another approach would be to use a view model and Combine:

import SwiftUI
import Combine

class ViewModel : ObservableObject {
    @Published var selection = Set<UUID>()
    @Published var items : [Item] = [.init(title: "Test1"),
                                 .init(title: "Test2"),
                                 .init(title: "Test3")]
    
    var cancellable : AnyCancellable?
    
    init() {
        cancellable = $selection.sink {
            print($0.count)
        }
    }
}

struct ContentView : View {
    @StateObject private var viewModel = ViewModel()
    
    var body: some View {
        NavigationView {
            List(selection: $viewModel.selection) {
                ForEach(viewModel.items) { item in
                                  Text(item.title)
                            }
            }
            .navigationBarItems(trailing: EditButton())
        }
    }
}

Upvotes: 1

Related Questions