SwiftUI selection in lists not working on reused cells

Viewed 545

Consider the following project with two views. The first view presents the second one:

import SwiftUI

struct ContentView: View {

    private let data = 0...1000

    @State private var selection: Set<Int> = []
    @State private var shouldShowSheet = false

    var body: some View {
        self.showSheet()
        //self.showPush()
    }

    private func showSheet() -> some View {
        Button(action: {
            self.shouldShowSheet = true
        }, label: {
            Text("Selected: \(selection.count) items")
        }).sheet(isPresented: self.$shouldShowSheet) {
            EditFormView(selection: self.$selection)
        }
    }

    private func showPush() -> some View {
        NavigationView {
            Button(action: {
                self.shouldShowSheet = true
            }, label: {
                NavigationLink(destination: EditFormView(selection: self.$selection),
                               isActive: self.$shouldShowSheet,
                               label: {
                                Text("Selected: \(selection.count) items")
                })

            })
        }
    }
}
struct EditFormView: View {

    private let data = 0...1000

    @Binding var selection: Set<Int>
    @State private var editMode: EditMode = .active

    init(selection: Binding<Set<Int>>) {
        self._selection = selection
    }

    var body: some View {
        List(selection: self.$selection) {
            ForEach(data, id: \.self) { value in
                Text("\(value)")
            }
        }.environment(\.editMode, self.$editMode)
    }
}

Steps to reproduce:

  • Create an app with the above two views
  • Run the app and present the sheet with the editable list
  • Select some items at random indexes, for example a handful at index 0-10 and another handful at index 90-100
  • Close the sheet by swiping down/tapping back button
  • Open the sheet again
  • Scroll to indexes 90-100 to view the selection in the reused cells

Expected:

  • The selected indexes as you had will be in “selected state”

Actual:

  • The selection you had before is not marked as selected in the UI, even though the binding passed to List contains those indexes.

This occurs both on the “sheet” presentation and the “navigation link” presentation.

If you select an item in the list, the “redraw” causes the original items that were originally not shown as selected to now be shown as selected.

Is there a way around this?

1 Answers

It looks like EditMode bug, worth submitting feedback to Apple. The possible solution is to use custom selection feature.

Here is a demo of approach (modified only part). Tested & worked with Xcode 11.4 / iOS 13.4

struct EditFormView: View {

    private let data = 0...1000

    @Binding var selection: Set<Int>

    init(selection: Binding<Set<Int>>) {
        self._selection = selection
    }

    var body: some View {
        List(selection: self.$selection) {
            ForEach(data, id: \.self) { value in
                self.cell(for: value)
            }
        }
    }

    // also below can be separated into standalone view
    private func cell(for value: Int) -> some View {
        let selected = self.selection.contains(value)
        return HStack {
            Image(systemName: selected ? "checkmark.circle" : "circle")
                .foregroundColor(selected ? Color.blue : nil)
                .font(.system(size: 24))
                .onTapGesture {
                    if selected {
                        self.selection.remove(value)
                    } else {
                        self.selection.insert(value)
                    }
                }.padding(.trailing, 8)
            Text("\(value)")
        }
    }
}
Related