Last active
May 31, 2021 17:59
-
-
Save kayoslab/088aac932870bdedcdd254652384f8ca to your computer and use it in GitHub Desktop.
Reorder UITableViewCells and modify the underlying CoreData Model
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class TableViewController: UITableViewController { | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
tableView.delegate = self | |
tableView.dataSource = self | |
navigationItem.rightBarButtonItem = editButtonItem | |
} | |
// MARK: - Fetched results controller | |
var managedObjectContext = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext | |
var fetchedResultsController: NSFetchedResultsController<Object>? { | |
if let instance = instanceFetchedResultsController { | |
return instance | |
} | |
let fetchRequest: NSFetchRequest<Object> = Object.fetchRequest() | |
// Set the batch size to a suitable number. | |
fetchRequest.fetchBatchSize = fetchBatchSize | |
// Edit the sort key as appropriate. | |
let dispatchSortDescriptor = NSSortDescriptor(key: "sort", ascending: true) | |
fetchRequest.sortDescriptors = [dispatchSortDescriptor] | |
if let managedObjectContext = managedObjectContext { | |
// Edit the section name key path and cache name if appropriate. | |
// nil for section name key path means "no sections". | |
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: "destination", cacheName: nil) | |
aFetchedResultsController.delegate = self | |
instanceFetchedResultsController = aFetchedResultsController | |
do { | |
try instanceFetchedResultsController?.performFetch() | |
} catch { | |
// Replace this implementation with code to handle the error appropriately. | |
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. | |
let nserror = error as NSError | |
fatalError("Unresolved error \(nserror), \(nserror.userInfo)") | |
} | |
return instanceFetchedResultsController | |
} | |
return nil | |
} | |
fileprivate var instanceFetchedResultsController: NSFetchedResultsController<Object>? | |
} | |
// MARK: - UITableViewDelegate | |
extension TableViewController { | |
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { | |
performSegue(withIdentifier: "cellShowDetail", sender: tableView.cellForRow(at: indexPath)) | |
} | |
override func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool { | |
return false | |
} | |
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle { | |
return .none | |
} | |
override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { | |
return true | |
} | |
override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { | |
guard let context = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.newBackgroundContext() else { return } | |
/** | |
Switches two existing rows sort key | |
- Parameter sourceIndexPath: First Row | |
- Parameter destinationIndexPath: Second Row | |
*/ | |
func switchRows(surceIndexPath: IndexPath, destinationIndexPath: IndexPath) { | |
guard let fetchedSourceObject = fetchedResultsController?.object(at: sourceIndexPath) else { return } | |
guard let fetchedDestinationObject = fetchedResultsController?.object(at: destinationIndexPath) else { return } | |
guard let storedSourceObject = context.object(with: fetchedSourceObject.objectID) as? Object else { return } | |
guard let storedDestinationObject = context.object(with: fetchedDestinationObject.objectID) as? Object else { return } | |
guard let sourceSort = fetchedSourceObject.sortId else { return } | |
guard let destinationSort = fetchedDestinationObject.sortId else { return } | |
storedSourceObject.sortId = destinationSort | |
storedDestinationObject.sortId = sourceSort | |
} | |
/** | |
Stores the destination's sort Key into the origin-object | |
- Parameter origin: An Int defining the row of the current row | |
- Parameter destination: An Int defining the row from which the data is loaded | |
*/ | |
func incrementSortIndex(forOriginRow origin: Int, destinationRow destination: Int) { | |
let mutableSourceIndex = IndexPath(row: origin, section: destinationIndexPath.section) | |
let mutableDestinationIndex = IndexPath(row: destination, section: destinationIndexPath.section) | |
guard let fetchedSourceObject = fetchedResultsController?.object(at: mutableSourceIndex) else { return } | |
guard let fetchedDestinationObject = fetchedResultsController?.object(at: mutableDestinationIndex) else { return } | |
guard let storedSourceObject = context.object(with: fetchedSourceObject.objectID) as? Object else { return } | |
guard let destinationSort = fetchedDestinationObject.sortId else { return } | |
storedSourceObject.sortId = destinationSort | |
} | |
/** | |
Save the current BackgroundContext | |
*/ | |
func saveContext() { | |
do { | |
if context.hasChanges { | |
try context.save() | |
} | |
} catch { | |
print(error.localizedDescription) | |
} | |
} | |
if sourceIndexPath == destinationIndexPath { | |
// Nothing to do here, drag-and-drop and both rows are the same. | |
return | |
} else if abs(sourceIndexPath.row - destinationIndexPath.row) == 1 { | |
// If the rows were just switched | |
switchRows(surceIndexPath: sourceIndexPath, destinationIndexPath: destinationIndexPath) | |
return | |
} else if sourceIndexPath.row < destinationIndexPath.row { | |
// Move rows upwards | |
guard let fetchedSourceObject = fetchedResultsController?.object(at: sourceIndexPath) else { return } | |
guard let fetchedDestinationObject = fetchedResultsController?.object(at: destinationIndexPath) else { return } | |
// iterate over the unmoved rows, which are pushed downwards | |
for row in sourceIndexPath.row + 1 ..< destinationIndexPath.row { | |
incrementSortIndex(forOriginRow: row, destinationRow: row - 1) | |
} | |
// drag Source-Object upwards | |
guard let storedSourceObject = context.object(with: fetchedSourceObject.objectID) as? Object else { return } | |
guard let destinationSort = fetchedDestinationObject.sortId else { return } | |
storedSourceObject.sortId = destinationSort | |
} else if sourceIndexPath.row > destinationIndexPath.row { | |
// Move rows downwards | |
guard let fetchedSourceObject = fetchedResultsController?.object(at: sourceIndexPath) else { return } | |
guard let fetchedDestinationObject = fetchedResultsController?.object(at: destinationIndexPath) else { return } | |
// iterate over the unmoved rows, which are pushed upwards | |
for row in destinationIndexPath.row ..< sourceIndexPath.row { | |
incrementSortIndex(forOriginRow: row, destinationRow: row + 1) | |
} | |
// Source-Object is moved downwards | |
guard let storedSourceObject = context.object(with: fetchedSourceObject.objectID) as? Object else { return } | |
guard let destinationSort = fetchedDestinationObject.sortId else { return } | |
storedSourceObject.sortId = destinationSort | |
} | |
// Save the current Context | |
saveContext() | |
} | |
override func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath { | |
if sourceIndexPath.section != proposedDestinationIndexPath.section { | |
var row = 0 | |
if sourceIndexPath.section < proposedDestinationIndexPath.section { | |
row = self.tableView(tableView, numberOfRowsInSection: sourceIndexPath.section) - 1 | |
} | |
return IndexPath(row: row, section: sourceIndexPath.section) | |
} | |
return proposedDestinationIndexPath | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment