How to update the tableview row after swipe action? - ios

I am using swipe actions in my tableview. I want to add and delete small icons on the row after a swipe action completed.
I use an asynchronous thread to do this but, it is not giving a smooth result as I wanted. My code is given below any help will be appriciated.
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let groceryAction = groceryToggleAction(forRowAtIndexPath: indexPath)
let config = UISwipeActionsConfiguration(actions: [groceryAction])
return config
}
func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let consumeAction = consumeToggleAction(forRowAtIndexPath: indexPath)
let config = UISwipeActionsConfiguration(actions: [consumeAction])
return config
}
// MARK: Custom Methods
func groceryToggleAction(forRowAtIndexPath indexPath: IndexPath) -> UIContextualAction {
let food = foods[indexPath.item]
let action = UIContextualAction(style: .normal, title: "actionTitle") { (action, view, completionHandler) in
let food = self.foods[indexPath.item]
food.isAddedToGrocery = !food.isAddedToGrocery
self.persistenceManager.saveContext()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
self.homeTableView.reloadRows(at: [indexPath], with: .none)
}
completionHandler(true)
}
action.image = #imageLiteral(resourceName: "shoppingCart") // İyi bir liste ikonu bul...
action.backgroundColor = food.isAddedToGrocery ? UIColor.Palette.alizarin : UIColor.Palette.turquoise
action.title = food.isAddedToGrocery ? "Remove" : "Add"
return action
}
func consumeToggleAction(forRowAtIndexPath indexPath: IndexPath) -> UIContextualAction {
let food = foods[indexPath.item]
let action = UIContextualAction(style: .normal, title: "actionTitle") { (action, view, completionHandler) in
food.isConsumed = !food.isConsumed
self.persistenceManager.saveContext()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
self.homeTableView.reloadRows(at: [indexPath], with: .none)
}
completionHandler(true)
}
action.image = #imageLiteral(resourceName: "pacman")
action.title = food.isConsumed ? "Remove": "Consumed!"
action.backgroundColor = food.isConsumed ? UIColor.Palette.alizarin : UIColor.Palette.turquoise
return action
}

Finally I figured it out. I simply used below function from tableview delegate.
func tableView(_ tableView: UITableView, didEndEditingRowAt indexPath: IndexPath?) {
print("did end editing")
guard let indexPath = indexPath else {return}
tableView.reloadRows(at: [indexPath], with: .none)
}

You need to integrate the UITableView delete rows, this will give that nice anim instead of the clunky reload view update.
tableView.deleteRows(at: [indexPath], with: UITableViewRowAnimation.automatic)

DispatchQueue.main.async
{
self.tableView.reloadData()
}
should be just enough.
In your code, you force the app to execute at a specific time. If your data is dynamic, such as remote images, they could delay the main loop more than 0.4 seconds and thus cause an even more jerky update and movement.

Related

SwipeCellKit: Why removing an item from list, doesn't update the UITableview?

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)
}

How to add action when Trailing Swipe Action cancelled (Swift 5)

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
}

UITableView editActionsForRowAt not reacting properly to nil in Swift 5

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:).

Delete is not working in case of UITableview

MyTableviewController
var animalNameArray = ["cat","dog","lion"]
override func viewDidLoad() {
super.viewDidLoad()
tableview.delegate = self
tableview.dataSource = self
self.cancelButton.isEnabled = false
}
#IBAction func editButtonAtNavigationBar(_ sender: UIBarButtonItem) {
self.cancelButton.isEnabled = true
self.tableview.isEditing = !self.tableview.isEditing
sender.title = (self.tableview.isEditing) ? "Done" : "Edit"
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
animalNameArray.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
} else if editingStyle == .insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let remove = UITableViewRowAction(style: .default, title: " ") { action, indexPath in
}
remove.backgroundColor = UIColor(patternImage: UIImage(named: "delete")!)
return [remove]
}
//conditional Rearranging the table view cells
func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
return true
}
}
I want to put the image (trash) instead of text, image is showing but the delete action is not happening when trying to delete the row. I don't know what I am doing wrong. How can I delete my tableview row? Can someone please help me? Thanks in advance.
You should remove the cell at the indexPath that is passed in editActionsForRowAt like this:
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let remove = UITableViewRowAction(style: .default, title: " ") { action, indexPath in
tableView.beginUpdates()
tableView.deleteRows(at: [IndexPath(row: index, section: 0)], with: .left)
tableView.endUpdates()
}
remove.backgroundColor = UIColor(patternImage: UIImage(named: "delete")!)
return [remove]
}
It will delete the row but it won't be consistent with the data inside the array. You can also delete item from the array at the specific index and then call tableView.reloadData()
Once you delete row from tableview you need to reload rows
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let remove = UITableViewRowAction(style: .default, title: " ") { action, indexPath in
tableView.deleteRows(at: [IndexPath(row: index, section: 0)], with: .left)
self.tableView.reloadRows(at: [IndexPath], with: .fade)
}
remove.backgroundColor = UIColor(patternImage: UIImage(named: "delete")!)
return [remove]
}

Swift: UITableView - swipte to delete - How to have cell content not moving while swiping

I have implemented swipe to delete on UITableView, it works correctly with default behaviour, i.e. content of cell is moving to the left while swiping like on the image. What I want to achive (customize) is to have this delete button appear over the cell content while swiping i.e. text in the cell isn't moved.
I am using this method
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let delete = UITableViewRowAction(style: .destructive, title: "Delete") { (action, indexPath) in
self.deletionDidStart(of: self.element(for: indexPath)!, at: indexPath)
// use helper function to delete element at given index path
let deleted = self.deleteElement(for: indexPath)
// refresh tableView
self.tableView.beginUpdates()
self.tableView.deleteRows(at: [indexPath], with: .automatic)
if let deletedSection = deleted.section {
self.tableView.deleteSections(IndexSet(integer: indexPath.section), with: .automatic)
self.deletedSection(deletedSection, at: indexPath)
}
self.tableView.endUpdates()
// call deletion event handler
self.deletedElement(deleted.element!, at: indexPath)
}
return [delete]
}
You should override setEditing method of your UITableViewCell,
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
let leadingSpace = 15
let editingOffset = 80
if editing == true {
self.labelLeadingConstraint.constant = CGFloat(leadingSpace + editingOffset)
} else {
self.labelLeadingConstraint.constant = CGFloat(leadingSpace);
}
UIView.animate(withDuration: 0.1) {
self.layoutIfNeeded()
}
}
You need to take IBOutlet of your UILabel's leading Constraint and do it like this way.
This is suggestion only, actual code may be changed depending on your Cell layout.
UPDATE
Use below functions as well.
func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
cell?.setEditing(true, animated: true)
}
func tableView(_ tableView: UITableView, didEndEditingRowAt indexPath: IndexPath?) {
if let indexPath = indexPath {
let cell = tableView.cellForRow(at: indexPath)
cell?.setEditing(false, animated: true)
}
}
Hope this helps to you.

Resources