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

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
}

Related

SwipeActions does not work in tableView swift 5 xcode 13.4.1

i am doing trailingSwipe to my tableview but it does not work. does not call the function trailingSwipeActionsConfigurationForRowAt.
try with the leadingSwipeActionsConfigurationForRowAt and if it works for me but not with trailingSwipe
valid that I have delegate and datasource the tableview
code
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let delete = UIContextualAction(style: .normal, title: "delete2") {(action, view, completionHandler) in
print("delete2 \(indexPath.row)")
}
let swipe = UISwipeActionsConfiguration(actions: [delete])
return swipe
}
Uses Apple Mac M1 ( MacBook Pro M1)
Try this, working fine on the Xcode 13+, Apple Mac M1 and Swift 5
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {}
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let delete = UIContextualAction(style: .destructive, title: "Delete") { (action, sourceView, completionHandler) in
// print("index path of delete: \(indexPath)")
completionHandler(true)
}
let edit = UIContextualAction(style: .normal, title: "Edit") { [weak self] (action, sourceView, completionHandler) in
completionHandler(true)
}
delete.title = ""
delete.image = UIImage(named: "deleteProfile")
edit.title = ""
edit.image = UIImage(named: "editProfile")
let swipeAction = UISwipeActionsConfiguration(actions: [delete,edit])
swipeAction.performsFirstActionWithFullSwipe = false // This is the line which disables full swipe
return swipeAction
}
I also faced this issue on my end.
Maybe this issue is with the Apple Mac M1 please try with the Apple intel.
Please check this question
Below code is working fine in my Apple Intel.
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let action = UIContextualAction(style: .destructive, title: "REMOVE") { [self] action, view, completion in
// Your swipe action code!
completion(true)
}
action.backgroundColor = UIColor.red
let swipeAction = UISwipeActionsConfiguration(actions: [action])
swipeAction.performsFirstActionWithFullSwipe = false // Full swipe disable
return swipeAction
}
I found a solution in Apple M1 please try it with the Real device.
I tried the above code on a real device it's working perfectly.

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 is tableview implicitly reloaded when I deleted an object from realm?

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

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

How to update the tableview row after swipe action?

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.

Resources