swiftcore-datansundomanager

Swift undo feature, previously declared variable is always empty


I'm trying to implement an undo feature when deleting cells in a UITableView. The cell data to be deleted is declared as a variable. The deletion part works fine, however when I attempt to 'undo' the deletion the variable is always empty.

Implementation

func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
    let delete = UITableViewRowAction(style: UITableViewRowAction.Style.destructive, title: "Delete") { (action, indexPath) in

        let selectedExercise = self.exercises[indexPath.row] 
        //selectedExercise is declared here to be used in both 
        //`deleteExercise` and `undoDeleteExercise`

        self.undoManager?.registerUndo(withTarget: self, handler: { (selfTarget) in
            self.undoDeleteExercise(indexPath: indexPath, selectedExercise: selectedExercise)
        })

        self.deleteExercise(indexPath: indexPath, selectedExercise: selectedExercise)

        let message = MDCSnackbarMessage()
        message.text =  "Removing Exercise"
        let action = MDCSnackbarMessageAction()

        action.handler =  {() in
            self.undoManager?.undo()
        }
        action.title = "UNDO"
        message.action = action
        MDCSnackbarManager.show(message)
    }
    return [delete]
}

Deleting This works as expected. The selected exercise is removed from the database, exercises array and the tableView.

func deleteExercise(indexPath: IndexPath, selectedExercise: Exercise){
    guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
    let managedContext = appDelegate.persistentContainer.viewContext

    let _ : NSError! = nil
    do {
        managedContext.delete(selectedExercise as NSManagedObject)
        exercises.remove(at: indexPath.row)
        self.execisesTableView.deleteRows(at: [indexPath], with: .automatic)
        try managedContext.save()
    } catch {
        print("error : \(error)")
    }

    ifNoExercises()
}

Undoing a Deletion The problem here is that selectedExercise is always an empty object, so although the row is added into the database and the tableView it does not contain any information.

func undoDeleteExercise(indexPath: IndexPath, selectedExercise: Exercise){
    guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
    let managedContext = appDelegate.persistentContainer.viewContext

    let _ : NSError! = nil
    do {
        managedContext.insert(selectedExercise as NSManagedObject)

        exercises.insert(selectedExercise, at: indexPath.row)
        execisesTableView.insertRows(at: [indexPath], with: .automatic)
        try managedContext.save()
    } catch {
        print("error : \(error)")
    }

    ifNoExercises()
}

Solution

  • For anyone having the same problem this is how I fixed (this replaces editActionsForRowAt):

    func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        let contextItem = UIContextualAction(style: .destructive, title: "Delete") {  (contextualAction, view, boolValue) in
            guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
            let managedContext = appDelegate.persistentContainer.viewContext
    
            let exerciseEntity = NSEntityDescription.entity(forEntityName: MyVariables.exerciseEntity, in: managedContext)!
    
            let copiedExercise = NSManagedObject(entity: exerciseEntity, insertInto: managedContext)
    
            let selectedExercise = self.exercises[indexPath.row]
    
            copiedExercise.setValue(selectedExercise.distance, forKeyPath: MyVariables.distance)
            copiedExercise.setValue(selectedExercise.end_time, forKeyPath: MyVariables.endTime)
            copiedExercise.setValue(selectedExercise.lat_lngs, forKeyPath: MyVariables.latLngs)
            copiedExercise.setValue(selectedExercise.map_string, forKeyPath: MyVariables.mapString)
            copiedExercise.setValue(selectedExercise.start_time, forKeyPath: MyVariables.startTime)
    
            self.undoManager?.registerUndo(withTarget: self, handler: { (selfTarget) in
                self.undoDeleteExercise(indexPath: indexPath, selectedExercise: copiedExercise as! Exercise)
            })
    
            self.deleteExercise(indexPath: indexPath, selectedExercise: selectedExercise)
    
            let message = MDCSnackbarMessage()
            message.text =  "Removing Exercise"
            let action = MDCSnackbarMessageAction()
    
            action.handler =  {() in
                self.undoManager?.undo()
            }
            action.title = "UNDO"
            message.action = action
            MDCSnackbarManager.show(message)
        }
        let swipeActions = UISwipeActionsConfiguration(actions: [contextItem])
    
        return swipeActions
    }