Undo in SwiftData deletes all data at once.

When the following models in SwiftData,

@Model
final class UndoRedoData {
    var id: [Int]
    
    init(id: [Int]) {
        self.id = id
    }
}

I created the following code.

struct ContentView: View {
    @ObservedObject var swiftDataViewModel = SwiftDataArrayViewModel.shared
    
    @State private var idArray: [Int] = [1,2,3,4]
    
    @State private var firstviewSwich: Bool = true
    @State private var twoviewSwich: Bool = false
    @State private var threeviewSwich: Bool = false
    
    var body: some View {
        VStack {
            if firstviewSwich == true {
                Button(action: addItem) {
                    Text("1.New Item")
                }
            }
            
            if twoviewSwich == true {
                Button {
                    forArrayData()
                } label: {
                     Text("2.Data Road")
                }
            }
            
            if threeviewSwich == true {
                Button(action: undoItem) {
                    Text("3.Undo")
                }
            }
        }
    }
    
    private func addItem() {
        withAnimation {
            let newItem = UndoRedoData(id: [1,2,3,4])
            swiftDataViewModel.taskContext.insert(newItem)
            
            do {
                try swiftDataViewModel.taskContext.save()
            } catch {
                print(error)
            }
            
            swiftDataViewModel.fetchItems()
            
            firstviewSwich.toggle()
            twoviewSwich.toggle()
        }
    }
    
    private func forArrayData() {
        twoviewSwich.toggle()
        for data in idArray {
            swiftDataViewModel.idUndoCreate(id: data, undoManager: swiftDataViewModel.arrayItemUndoManager)
        }
        threeviewSwich.toggle()
    }
    
    private func undoItem() {
        swiftDataViewModel.arrayItemUndoManager.undo()
        
        threeviewSwich.toggle()
        firstviewSwich.toggle()
    }
}

class SwiftDataArrayViewModel: ObservableObject {
    static let shared = SwiftDataArrayViewModel()
    
    let modelContainer: ModelContainer
       
    @ObservationIgnored
    lazy var taskContext: ModelContext = {
        return ModelContext(modelContainer)
    }()
    
    @Published var arrayItems = [UndoRedoData]()
    
    @Published var arrayItemUndoManager = UndoManager()
    
    init() {
        let schema = Schema([UndoRedoData.self])
        let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
        
        do {
            modelContainer = try ModelContainer(for: schema, configurations: [modelConfiguration])
        } catch {
            fatalError(error)
        }
        
        fetchItems()
    }
    
    func fetchItems() {
        let fetchDescriptor = FetchDescriptor<UndoRedoData>()
        do {
            arrayItems = try taskContext.fetch(fetchDescriptor)
        } catch {
            fatalError(error)
        }
    }
    
    func idUndoCreate(id: Int, undoManager: UndoManager?) {
        undoManager?.registerUndo(withTarget: self) { target in
            target.removeID()
        }
    }
    
    func removeID() {
        if let firstUndoRedoData = arrayItems.first {
            print("Before Delete:\(firstUndoRedoData.id)")
            if !firstUndoRedoData.id.isEmpty {
                firstUndoRedoData.id.removeLast()
            }
            print("After  Delete:\(firstUndoRedoData.id)")
        }
        
        do {
            try taskContext.save()
            
        } catch {
            print(error)
        }
        
        fetchItems()
    }
}

In this code, 1. Create an Item in New Item, 2. Execute Data Road and register the data in the array that is the same value as the data created in New Item in SwiftData one by one in UndoManager by for data in idArray. This is done because the data in the array and the data created by New Item in SwiftData can be known in advance.

private func forArrayData() {
        twoviewSwich.toggle()
        for data in idArray {
            swiftDataViewModel.idUndoCreate(id: data, undoManager: swiftDataViewModel.arrayItemUndoManager)
        }

// class SwiftDataArrayViewModel: ObservableObject
func idUndoCreate(id: Int, undoManager: UndoManager?) {
        undoManager?.registerUndo(withTarget: self) { target in
            target.removeID()
        }
}

After registering in UndoManager, when Undo is executed with 3. Undo, instead of being able to perform Undo where one id is deleted each time, all the data of the id in SwiftData is deleted in a one-time Undo.

I would like to be able to delete one id each time Undo is performed and restore them in sequence, but I can only delete them all once. Does this mean that such registration to UndoManager should not be done with for statements, etc.? Or is there another problem in the code?

I want to make sure that one id is deleted for each Undo executed.

Did you try to group your actions using beginUndoGrouping() and endUndoGrouping()? Concretely:

  1. Begin a group by calling beginUndoGrouping()
  2. Add an item.
  3. Register the undo action using registerUndo(withTarget:handler:), as shown in your code.
  4. End the group by calling endUndoGrouping().
  5. Undo.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Undo in SwiftData deletes all data at once.
 
 
Q