4 Replies
      Latest reply on May 21, 2020 6:16 AM by Blinker73
      Blinker73 Level 1 Level 1 (0 points)

        Hi, I am new to developpement in xcode and am trying to build my first app in SwiftUI.

        I am struggling with multi selection in a list of CoreData Items.

         

        I build an application comparing the functionality when using an Array versus when using CoreData

        The Array tab is working as I expect but the Core data tab is not.

        Am I doing something wrong or is it a bug?

         

        You can see further details/comments in the code.

         

        Thanks,

         

        import SwiftUI
        
        
        // ########################################################################################
        // XCode
        //   Version 11.4.1 (11E503a)
        // Simulator
        //   Version 11.4.1 (921.9)
        //   SimulatorKit 581.9.1
        //   CoreSimulator 704.12.1
        // Device
        //   iPhone SE (2nd generation)
        // ########################################################################################
        
        
        
        // ########################################################################################
        // the CoreData entity is named MyNumber
        // I don't think extending it as Identifiable has any effect but it shouldn't hurt to add it
        // ########################################################################################
        extension MyNumber: Identifiable {
            // ########################################################################################
            // the entity has these two fields
            // @NSManaged public var id: UUID?
            // @NSManaged public var stringValue: String?
            // ########################################################################################
        }
        
        struct ContentView: View {
            
            // Tab variables
            @State private var tabSelection = 0
            
            // Array variables
            @State var arrayNumbers = ["One","Two","Three","Four","Five","Six","Seven","Eight","Nine","Ten"]
            @State var arrayEditMode = EditMode.inactive
            @State var arraySelection = Set<String>()
            
            // Core Data variables
            @State var cdEditMode = EditMode.inactive
            // ########################################################################################
            // Am I using the correct type ? Set<MyNumber>() : I also tried Set<UUID>() without success
            // ########################################################################################
            // # Using Set<MyNumber>()
            @State var cdSelection = Set<MyNumber>()
            // ########  OR  ############
            // # Using Set<UUID>()
            // @State var cdSelection = Set<UUID>()
            // #####################################################################
            @Environment(\.managedObjectContext) var moc
            @FetchRequest(entity: MyNumber.entity(), sortDescriptors:[]) var cdNumbers: FetchedResults<MyNumber>
        
            
            
            var body: some View {
                
                TabView(selection: $tabSelection){
                    // Array Tab
                    VStack{
                        Text("Array").font(.largeTitle)
                        NavigationView {
                            
                            List(selection: $arraySelection) {
                                ForEach(arrayNumbers, id: \.self) { number in
                                    Text(number)
                                }
                                .onDelete(perform: arrayOnSwipeDelete)
                            }
                            .navigationBarTitle("Selected count \(arraySelection.count)")
                            .navigationBarItems(leading: arrayDeleteButton, trailing: EditButton())
                            .environment(\.editMode, self.$arrayEditMode)
                        }
                    }
                    .tabItem {
                        VStack {
                            Image(systemName: "checkmark")
                            Text("Array")
                            
                        }
                    }
                    .tag(0)
                    
                    // Core Data Tab
                    
                    VStack{
                        VStack{
                            Text("Core Data").font(.largeTitle)
                            Button("Add coredata numbers") {
                                for number in self.arrayNumbers {
                                  let cdNumber = MyNumber(context: self.moc)
                                   cdNumber.id = UUID()
                                   cdNumber.stringValue = "\(number)"
                                }
                                try? self.moc.save()
                            }.disabled(cdEditMode.isEditing)
                        }
                        NavigationView {
                            List(selection: $cdSelection) {
                                ForEach(cdNumbers, id: \.id) { number in
                                    Text(number.stringValue ?? "NULL")
                                }
                                // #####################################################################
                                // Problem #1
                                // Without the onDelete, the left selection column does not appear at all
                                // To reproduce this
                                // 1 - click "Add coredata records"
                                // 2 - Click "Edit"
                                // #####################################################################
                                // Problem #2
                                // With the onDelete, rows can be selected but $cdSelection stays empty
                                // To reproduce this
                                // 1 - click "Add coredata records"
                                // 2 - Click "Edit"
                                // 3 - Select records (notice the selection count is not updated)
                                // 4 - Even with some selected records, the trashcan stays disabled because of the disabled event
                                // 5 - Commenting out the disabled event and clicking on the trashcan should delete records but it does nothing
                                // #####################################################################
                                .onDelete(perform: cdOnSwipeDelete)
                            }
                            .navigationBarTitle("Selected count \(cdSelection.count)")
                            .navigationBarItems(leading: cdDeleteButton, trailing: EditButton())
                            .environment(\.editMode, self.$cdEditMode)
                        }}
                        .tabItem {
                            VStack {
                                Image(systemName: "xmark")
                                Text("CoreData")
                            }
                    }
                    .tag(1)
                    
                }
                
            }
            init() {
                // Global Navigation bar customizations
                UINavigationBar.appearance().largeTitleTextAttributes = [
                    .font : UIFont.systemFont(ofSize: 18, weight: UIFont.Weight.bold)]
            }
            
            
            // array functions
            private var arrayDeleteButton: some View {
                return Button(action: arrayDeleteNumbers) {
                    if arrayEditMode == .active {
                        Image(systemName: "trash")
                    }
                }.disabled(arraySelection.count <= 0)
            }
            
            private func arrayDeleteNumbers() {
                for id in arraySelection {
                    if let index = arrayNumbers.lastIndex(where: { $0 == id })  {
                        arrayNumbers.remove(at: index)
                    }
                }
                arraySelection = Set<String>()
            }
            
            // #####################################################################
            // Array selecction is not affected by the presence of the arrayOnSwipeDelete function
            // #####################################################################
            private func arrayOnSwipeDelete(with indexSet: IndexSet) {
                indexSet.forEach { index in
                    arrayNumbers.remove(at: index)
                }
            }
            
            
            // Core Data Functions
            private var cdDeleteButton: some View {
                return Button(action: cdDeleteNumbers) {
                    if cdEditMode == .active {
                        Image(systemName: "trash")
                    }
                // #####################################################################
                // Even with a commented .disable rule, the trash can deletes nothing
                // #####################################################################
                }.disabled(cdSelection.count <= 0)
        
            }
            
            private func cdDeleteNumbers() {
                for selectedItem in self.cdSelection{
                    // #####################################################################
                    // # Using Set<MyNumber>()
                    self.moc.delete(selectedItem)
                    // ########  OR  ############
                    // # Using Set<UUID>()
                    // self.moc.delete(cdNumbers.first(where:  {$0.id == selectedItem})!)
                    // #####################################################################
                }
                try? self.moc.save()
                
                // #####################################################################
                // # Using Set<MyNumber>()
                cdSelection = Set<MyNumber>()
                // ########  OR  ############
                // # Using Set<UUID>()
                // cdSelection = Set<UUID>()
                // #####################################################################
            }
            
            // #####################################################################
            // Core data selecction is affected by the presence of the cdOnSwipeDelete function
            // IE: The selection column is shown in edit mode only if
            // this functuion is referenced by ForEach{}.onDelete()
            // #####################################################################
            private func cdOnSwipeDelete(with indexSet: IndexSet) {
                
                indexSet.forEach { index in
                    let number = cdNumbers[index]
                    self.moc.delete(number)
                }
                try? self.moc.save()
            }
            
            
        }
        
        struct ContentView_Previews: PreviewProvider {
            static var previews: some View {
                ContentView()
            }
        }