I have a tableview where I am using SwipeCellkit for custom cell deletion. When the delete button is pressed I delete the specified object in realm,
however, I did not reload the tableview but still the tableview row has been deleted. I want to know how it has done (is realm doing implicit tableview reloading?)
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = super.tableView(tableView, cellForRowAt: indexPath)
cell.textLabel?.text = categoryArray?[indexPath.row].name
return cell
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
guard orientation == .right else { return nil }
let deleteAction = SwipeAction(style: .destructive, title: "Delete") { action, indexPath in
// handle action by updating model with deletion
self.updateModel(at: indexPath)
}
// customize the action appearance
deleteAction.image = UIImage(named: "delete-icon")
return [deleteAction]
}
func updateModel(at indexPath: IndexPath) {
if let category = self.categoryArray?[indexPath.row]
{
do{
try self.realm.write {
self.realm.delete(category)
}
}catch{
print("eror deleting object")
}
}
}
This has nothing to do with the Realm.
This is because of the deleteAction that you're returning in editActionsForRowAtIndexPath method.
More precisely, the SwipeActionStyle.destructive is responsible for creating a delete action.
As mentioned in the docs:
The built-in .destructive, and .destructiveAfterFill expansion styles
are configured to automatically perform row deletion when the action
handler is invoked (automatic fulfillment).
Hope this helps
Related
There is a UITableView that uses UITableViewDiffableDataSource. I subclassed UITableViewDiffableDataSource to add canEditRowAt. This correctly shows the swipeable delete action when gesturing the row. However, clicking the delete option does not call tableView(_:commit:forRowAt:). I have read that you have to use tableView(_:trailingSwipeActionsConfigurationForRowAt:indexPath:) because the other function is not supported. I wanted to confirm that was true. If we subclass tableView(_:commit:forRowAt:) as well, we need a clean way to call a function on the original View Controller.
// MyViewController
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// Delete cell
}
}
// Subclass DiffableDataSource used in MyViewController
final class CustomDiffableDatasource: UITableViewDiffableDataSource<MyViewController.Section, MyViewController.Item> {
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
guard let item = itemIdentifier(for: indexPath) else {
return false
}
return item.isEditable
}
}
Here is the custom trailingSwipeActionsConfigurationForRowAtfunc looks like for adding a delete swipe. I saw this in another StackOverflow question that referenced a blog post.
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
guard self.dataSource?.tableView(tableView, canEditRowAt: indexPath) == true else {
return nil
}
let delete = UIContextualAction(style: .destructive, title: "Delete") { [weak self] action, view, success in
self?.remove(at: indexPath)
success(true)
}
let swipeActionConfig = UISwipeActionsConfiguration(actions: [delete])
swipeActionConfig.performsFirstActionWithFullSwipe = false
return swipeActionConfig
}
When I remove an item from list with SwipeTableViewCell, the tableview doesn't get updated.
The swiping works, I can also tap on the delete button behind the cell, but once tapped on it, it only hides the delete button but tableview is not updated.
This is how my code look like:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return carList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell_id", for: indexPath) as! CarCell
(cell as SwipeTableViewCell).delegate = self
let car = carList[indexPath.row]
cell.configure(with: car)
return cell
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
guard orientation == .right else { return nil }
let deleteAction = SwipeAction(style: .destructive, title: "Delete") { action, indexPath in
self.carList.remove(at: indexPath.row)
}
deleteAction.hidesWhenSelected = true
return [deleteAction]
}
It works when I call reloadData of the tableview, but it's not the correct way I guess. The following snippet of code works:
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
guard orientation == .right else { return nil }
let deleteAction = SwipeAction(style: .destructive, title: "Delete") { action, indexPath in
self.carList.remove(at: indexPath.row)
self.tableView.reloadData() // <-- this works! But without animation
}
deleteAction.hidesWhenSelected = true
return [deleteAction]
}
You have to add the code to update the view but instead of reloading the entire table view just delete the row
let deleteAction = SwipeAction(style: .destructive, title: "Delete") { action, indexPath in
self.carList.remove(at: indexPath.row)
self.tableView.deleteRows(at: [indexPath], with: .fade)
}
In my project I use trailingSwipeActionsConfigurationForRowAt func. And I also use selectRow func when I need. If my cell is selected and I use swipe on it and then I cancelled it (swipe from left to right), my cell doesn't selected again. Where can I fix it (use selectRow again)?
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .destructive, title: "") { [weak self] (contextualAction, view, boolValue) in
guard let self = self else { return }
self.deleteRow(at: indexPath)
}
deleteAction.image = UIImage(systemName: "trash")
let swipeActions = UISwipeActionsConfiguration(actions: [deleteAction])
return swipeActions
}
You can use:
func tableView(_ tableView: UITableView, didEndEditingRowAt indexPath: IndexPath?) {
//Get the cell's indexPath (be aware of the optional argument and threat it the way you want)
let cell = tableView.cellForRow(at: indexPath!)
//Use the cell to the changes you need
}
Also you can use an animation if you want to achieve a smooth effect.
you're not calling the completion handler. According to the API documentation it is necessary to call the completion handler to indicate whether the operation was successful.
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .destructive, title: "") { _, _, complete in
// remove object from your array..
self.arrayName.remove(at: indexPath.row)
//reload the table to avoid index out of bounds crash..
self.tableView.deleteRows(at: [indexPath], with: .automatic)
complete(true)
}
deleteAction.image = UIImage(named: "trash")
let configuration = UISwipeActionsConfiguration(actions: [deleteAction])
configuration.performsFirstActionWithFullSwipe = true
return configuration
}
I have had a UITableView working for several version of Swift, but with Swift 5, it started presenting 'delete' action on a left swipe.
One of the rows is 'swipe-able' (the 'Expanded' one) and others are not, so I return a nil in place of the RowAction.
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
if listOfCurrentAwards[indexPath.row] != "Expanded" {
print(">>> Skipping row \(indexPath.row)")
return nil
} else {
let share = UITableViewRowAction(style: .normal, title: "Share") { action, index in
self.prepareOneDogMessageShareSheet(row: indexPath.row)
}
share.backgroundColor = UIColor.blue
return [share]
}
}
I am also getting the same behavior with the older 'editActionsForRowAtIndexPath'.
Anyone else seeing the same thing? Did you find a work around?
For now I am just returning a dummy action that displays a dog related emoji ().
if listOfCurrentAwards[indexPath.row] != "Expanded" {
//TBD - remove the following line if the nill action is supported/fixed.
let dogEmoji = ["🐶","🐩","🦴","🐕","💩","🐾"]
let share = UITableViewRowAction(style: .normal, title: dogEmoji.randomElement()) { action, index in
print(">>> Skipping row \(indexPath.row)")
}
return [share] //nil
} else ...
Update 1
Even refactoring to use trailingSwipeActionsConfigurationForRowAt did not work, I'm getting the same result.
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?
{
if listOfCurrentAwards[indexPath.row] != "Expanded" {
print(">>> Swiping not enabled for row \(indexPath.row)")
return nil
} else {
print(">>> 'Share' swipped on \(indexPath.row)")
let shareAction = UIContextualAction(style: .normal, title: "Share") { (action, view, handler) in
print(">>> 'Share' clicked on \(indexPath.row)")
self.prepareOneDogMessageShareSheet(row: indexPath.row)
}
shareAction.backgroundColor = UIColor.blue
let configuration = UISwipeActionsConfiguration(actions: [shareAction])
configuration.performsFirstActionWithFullSwipe = true //false to not support full swipe
return configuration
}
}
Answer
I had to add the canEditRowAt helper, allow me to move some of the logic from trailingSwipeActionsConfigurationForRowAt.
func tableView(_ tableView: UITableViewbcgdfgdfg, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?vdfgh
{
print(">>> Trying to swipe row \(indexPath.row)")
let shareAction = UIContextualAction(style: .normal, title: "Share") { (action, view, handler) in
print(">>> 'Share' clicked on \(indexPath.row)")
self.prepareOneDogMessageShareSheet(row: indexPath.row)
}
shareAction.backgroundColor = UIColor.blue
let configuration = UISwipeActionsConfiguration(actions: [shareAction])
return configuration
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return listOfCurrentAwards[indexPath.row] == "Expanded"
}
editActionsForRowAt is outmoded if the goal is to control what happens when you swipe. So is UITableViewRowAction.
You should be using tableView(_:trailingSwipeActionsConfigurationForRowAt:).
I want to remove the isEditing red icon totally from the table, i also want to reenable the swipe for row actions feature it seems to be overriding.
At the moment i can either have no isEditing command and use swipe to get to my row actions, but cant have my rearrange icons and feature. Or i have isEditing enabled and cant swipe to get to my actions i have to click the massive red button, but i can have my cell rearrange icons...
Is there a way I can have swipe for row actions AND be able to rearrange my cells? WITHOUT the red editing icon overriding everything!
Screenshots of issue: I have to have this red icon to have the rearrange icons, and it disables user swiping for the row actions in pic 2...
Some of the current related code:
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let delete = UITableViewRowAction(style: .destructive, title: "Delete") { (action, indexPath) in
let userExercise = self.fetchedResultsController.self.managedObjectContext
print("EXERCISE TO BE DELETED IS \(userExercise)")
userExercise.delete(self.fetchedResultsController.object(at: indexPath))
do {
try userExercise.save()
print("DELETE COMPLETED")
self.workoutDesignerTable.reloadData()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
let edit = UITableViewRowAction(style: .normal, title: "Edit") { (action, indexPath) in
let cell = tableView.cellForRow(at: indexPath)
self.updateExercise = self.fetchedResultsController.object(at: indexPath)
self.performSegue(withIdentifier: self.segueEditUserExerciseViewController, sender: cell)
}
edit.backgroundColor = UIColor.lightGray
return [delete, edit]
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let UserExercise = fetchedResultsController.self.managedObjectContext
UserExercise.delete(fetchedResultsController.object(at: indexPath))
do {
try UserExercise.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
return false
}
func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath)-> Bool {
return true
}
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
var movableUserExercisesArray = self.fetchedResultsController.fetchedObjects
let movedObject = movableUserExercisesArray?[sourceIndexPath.row]
movableUserExercisesArray?.remove(at: sourceIndexPath.row)
movableUserExercisesArray?.insert(movedObject!, at: destinationIndexPath.row)
print("THIS WAS MOVED FROM \(sourceIndexPath.row) => TO DESINATION \(destinationIndexPath.row)")
// attempting to set index paths - doesnt work as we fetched new?
movedObject?.arrayPosition = Int64(destinationIndexPath.row)
do {
try self.managedObjectContext.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
EDIT
I have added
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
return .none
}
which now hides the red icon, however i still cant swipe to get to row actions, removing the red button removes any ability to access them when isEditing is on, can the swipe access be restored?