Mych Mych - 7 months ago 27
Swift Question

Swift UITableView delete selected row and any rows above it

My TableView contains cells that represent a mileage log. I need to allow users that ability to delete any mistakes. The tableview lists the logs in descending order. Deleting the top row is OK. Deleting any other row I need to issue a warning as an alert and if confirmed then delete the selected row + all rows above it. Is this possible? Is there any example code anywhere?

UPDATE

Based on the two answers I have had so far I have done the following....

import UIKit
import CoreData

class MileageLogsTableViewController: UITableViewController, NSFetchedResultsControllerDelegate {

@IBOutlet var milageLogTableView: UITableView!

override func viewDidLoad() {
super.viewDidLoad()

do {
try fetchedResultsController.performFetch()
} catch {
let fetchError = error as NSError
print("Unable to fetch MileageLog")
print("\(fetchError), \(fetchError.localizedDescription)")
}

// Display an Edit button in the navigation bar for this view controller.
self.navigationItem.leftBarButtonItem = self.editButtonItem()
}

// MARK: - Table view data source

private lazy var fetchedResultsController: NSFetchedResultsController = {
// Initialize Fetch Request
let fetchRequest = NSFetchRequest(entityName: "MileageLog")

// Add Sort Descriptors
let dateSort = NSSortDescriptor(key: "tripDate", ascending: false)
let mileSort = NSSortDescriptor(key: "startMileage", ascending: false)
fetchRequest.sortDescriptors = [dateSort, mileSort]

let delegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedObjectContext = delegate.managedObjectContext

// Initialize Fetched Results Controller
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: "rootCache")

//ADDED AS PER ANSWER FROM SANDEEP
fetchedResultsController.delegate = self

return fetchedResultsController

}()


override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if let sections = fetchedResultsController.sections {
return sections.count
}

return 0
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections = fetchedResultsController.sections {
let sectionInfo = sections[section]
return sectionInfo.numberOfObjects
}

return 0
}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

let cell = tableView.dequeueReusableCellWithIdentifier("MileageLogCell") as! MileageTableViewCell

// Fetch MileageLog
if let mileageLog = fetchedResultsController.objectAtIndexPath(indexPath) as? MileageLog {
//format date as medium style date
let formatter = NSDateFormatter()
formatter.dateStyle = .MediumStyle
let logDateString = formatter.stringFromDate(mileageLog.tripDate!)
//format NSNumber mileage to string
let mileageInt:NSNumber = mileageLog.startMileage!
let mileageString = String(mileageInt)

cell.lb_LogDate.text = logDateString
cell.lb_LogMileage.text = mileageString
cell.lb_LogStartLocation.text = mileageLog.startLocation
cell.lb_LogDestination.text = mileageLog.endLocation
}
return cell
}


// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}

// MARK: Fetched Results Controller Delegate Methods
func controllerWillChangeContent(fetchedResultsController: NSFetchedResultsController) {
tableView.beginUpdates()
}

func controllerDidChangeContent(fetchedResultsController: NSFetchedResultsController) {
tableView.endUpdates()
}

func controller(fetchedResultsController: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch (type) {
case .Insert:
break;
case .Delete:

let context = fetchedResultsController.managedObjectContext
if let indexPath = indexPath {

if indexPath.row == 0 {
//this is the top (first row)
// Deleting without warning
let objectToDelete = fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject
context.deleteObject(objectToDelete)

do {
try context.save()
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)

} catch {
print(error)
}
self.tableView.reloadData();

} else {
//we are deleted a row that is not the top row
// we need to give a warning and if acknowledged then delele all rows from the selected row and all rows above it

let alertController = UIAlertController(title: nil, message: "Are you sure? This will remove this and all logs above it.", preferredStyle: .Alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel) { (action) in

}
alertController.addAction(cancelAction)
let deleteAction = UIAlertAction(title: "Delete", style: .Default) { (action) in

for deleteindex in 0 ... indexPath.row {
let deleteIndexPath = NSIndexPath(forRow: deleteindex, inSection: 0)
let objectToDelete = self.fetchedResultsController.objectAtIndexPath(deleteIndexPath) as! NSManagedObject
context.deleteObject(objectToDelete)

do {
try context.save()
self.tableView.deleteRowsAtIndexPaths([deleteIndexPath], withRowAnimation: .Fade)

} catch {
print(error)
}
}
self.tableView.reloadData();
}
alertController.addAction(deleteAction)

// Dispatch on the main thread
dispatch_async(dispatch_get_main_queue()) {
self.presentViewController(alertController, animated: true, completion:nil)
}

}
}
break;
case .Update:
break;
case .Move:
break;
}
}

}


Now my problem is that the touching Delete does nothing. The treeview is correctly populated. The Edit button is in the navbar. Click Edit and the 'no entry' icon appear on each row... slide a row and the Delete block appears. Click delete and nothing...! What have I missed out?

FINAL WORKING FIX

// Override to support editing the table view.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {

switch editingStyle {

case .Delete:
let context = fetchedResultsController.managedObjectContext
if indexPath.row == 0 {
//this is the top (first row)
// Deleting without warning
let indexPathToDelete = NSIndexPath(forRow: 0, inSection: 0)
let objectToDelete = fetchedResultsController.objectAtIndexPath(indexPathToDelete) as! NSManagedObject
context.deleteObject(objectToDelete)

do {
try context.save()
//self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)

} catch {
print(error)
}
//self.tableView.reloadData();

} else {
//we are deleted a row that is not the top row
// we need to give a warning and if acknowledged then delele all rows from the selected row and all rows above it

let alertController = UIAlertController(title: nil, message: "Are you sure? This will remove this and all logs above it.", preferredStyle: .Alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel) { (action) in

}
alertController.addAction(cancelAction)
let deleteAction = UIAlertAction(title: "Delete", style: .Default) { (action) in

for deleteindex in 0 ... indexPath.row {
let deleteIndexPath = NSIndexPath(forRow: deleteindex, inSection: 0)
let objectToDelete = self.fetchedResultsController.objectAtIndexPath(deleteIndexPath) as! NSManagedObject
context.deleteObject(objectToDelete)

}

do {
try context.save()

} catch {
print(error)
}
}
alertController.addAction(deleteAction)

// Dispatch on the main thread
dispatch_async(dispatch_get_main_queue()) {
self.presentViewController(alertController, animated: true, completion:nil)
}

}
break;

default :
return
}

}

// MARK: Fetched Results Controller Delegate Methods
func controllerWillChangeContent(controller: NSFetchedResultsController) {
tableView.beginUpdates()
}

func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}

func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Insert:
break;
case .Delete:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
case .Update:
break;
case .Move:
break;
}
}

Answer

Apart from the enhancement to use edit actions this is the easy solution.

First of all do not touch the delegate method didChangeObject.
Leave it as it is. It is called after making changes in the managed object context and works like the view in the MVC pattern.

 func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
    switch type {
    case .Insert:
      tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
    case .Delete:
      tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
    case .Update:
      self.configureCell(tableView.cellForRowAtIndexPath(indexPath!)!, atIndexPath: indexPath!)
    case .Move:
      tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
      tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
    }
  }

Insert the code to delete the rows in commitEditingStyle which works like the model in the MVC pattern. The code deletes all rows from the selected row above in the current section.

  override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
    switch editingStyle {
    case .Delete:
      let context = fetchedResultsController.managedObjectContext
      let section = indexPath.section
      let currentRow = indexPath.row
      for index in 0...currentRow {
          let indexPathToDelete = NSIndexPath(forRow: index, inSection: section)
          let objectToDelete = fetchedResultsController.objectAtIndexPath(indexPathToDelete) as! NSManagedObject
          context.deleteObject(objectToDelete)
      }
      do {
        try context.save()
      } catch let error as NSError {
        print(error)
      }

    case .Insert, .None: break
    }
  }