New iOS 11 UITableView Swipe action not getting called. The delegate and datasource are working fine for the table.
I am not able to swipe and see the menu items.
Below is my code for the same.
func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let contextItem = UIContextualAction(style: .normal, title: "Leading & .normal") { (contextualAction, view, boolValue) in
print("Leading Action style .normal")
}
let swipeActions = UISwipeActionsConfiguration(actions: [contextItem])
return swipeActions
}
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let contextItem = UIContextualAction(style: .destructive, title: "Trailing & .destructive") { (contextualAction, view, boolValue) in
print("Trailing Action style .destructive")
}
let swipeActions = UISwipeActionsConfiguration(actions: [contextItem])
return swipeActions
}
I tried to call below and its working fine.
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
}
}
Any hint in right direction is highly appreciated.
You need to pass true to UIContextualAction in the closure boolValue(true). Otherwise the handler won't allow the action.
func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let contextItem = UIContextualAction(style: .normal, title: "Leading & .normal") { (contextualAction, view, boolValue) in
boolValue(true) // pass true if you want the handler to allow the action
print("Leading Action style .normal")
}
let swipeActions = UISwipeActionsConfiguration(actions: [contextItem])
return swipeActions
}
For those who uses UITableViewDiffableDataSource you need to subclass it and override its canEditRowAt method.
public override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
true
}
Code looks good, but you are missing probably missing the following the "UITableViewDelegate" in the Class declaration and also the "TableView.delegate = self" in the viewdidload() section. So it should look like:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
}
}
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
}
I am trying to make swipe-able table view cell for iOS 11 and above and trying to use the trailingSwipeActionsConfigurationForRowAt but it works on iPad and doesn't work on iPhone. It is not called in iPhone.
Surprisingly if I try to swipe the cell more then 10 or 20 times it sometimes works for once.
Here is my controller's table view extension
import UIKit
extension CustomViewController: UITableViewDelegate, UITableViewDataSource {
func initTableView() {
tableView.register(cell: SingleLineListItemViewCell.self)
tableView.delegate = self
tableView.dataSource = self
tableView.estimatedRowHeight = 64
tableView.rowHeight = UITableView.automaticDimension
tableView.backgroundColor = .green
tableView.isEditing = false
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let model = SingleLineListItemViewModel(title: "coko")
let cell = tableView.dequeueReusableCell(withIdentifier: SingleLineListItemViewCell.reuseIdentifier) as? SingleLineListItemViewCell
cell?.model = model
cell?.isFirst = indexPath.row == 0
cell?.isLast = indexPath.row == 2
return cell!
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return nil
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 0
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 64.0
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
print("didSelectRowAt....")
router?.routeToTransfer()
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
#available(iOS 11.0, *)
func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
return nil
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
// Action here
// In case of delete, you can simply do:
if editingStyle == .delete {
//Remove item at relative position from datasource array
//Reload tableview at the respective indexpath
}
}
#available(iOS 11.0, *)
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .destructive, title: "Delete") { _, _, _ in
//YOUR_CODE_HERE
}
deleteAction.backgroundColor = .red
let configuration = UISwipeActionsConfiguration(actions: [deleteAction])
configuration.performsFirstActionWithFullSwipe = false
return configuration
}
}
Any help would be much appreciated.
The only obvious thing wrong with this is that 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. What it actually does I have no idea...
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .destructive, title: "Delete") { _, _, completionHandler in
//YOUR_CODE_HERE
completionHandler(true)
}
deleteAction.backgroundColor = .red
let configuration = UISwipeActionsConfiguration(actions: [deleteAction])
configuration.performsFirstActionWithFullSwipe = false
return configuration
}
If this doesn't make any difference, do you have any other gesture recognisers that could be conflicting with the swipe action?
you can try this one
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String? {
return "Delete"
}
// this method handles row deletion
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// remove the item from the data model
// delete the table view row
tableView.deleteRows(at: [indexPath], with: .fade)
} else if editingStyle == .insert {
// Not used in our example, but if you were adding a new row, this is where you would do it.
}
}
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let action = UIContextualAction(
style: .normal,
title: "Delete",
handler: { (action, view, completion) in
//do what you want here
completion(true)
})
action.backgroundColor = .red
let configuration = UISwipeActionsConfiguration(actions: [action])
configuration.performsFirstActionWithFullSwipe = false
return configuration
}
Here is are actions defined as well (point 1 and 2);
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .destructive, title: "") { _, _, completionHandler in
// 1. remove object from your array
scannedItems.remove(at: indexPath.row)
// 2. reload the table, otherwise you get an index out of bounds crash
self.tableView.reloadData()
completionHandler(true)
}
deleteAction.backgroundColor = .systemOrange
deleteAction.image = UIImage(named: "trash")
let configuration = UISwipeActionsConfiguration(actions: [deleteAction])
configuration.performsFirstActionWithFullSwipe = true
return configuration
}
i'm trying to change the delete button of a cell.
I have 2 functions :
override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let titleBtn = UITableViewRowAction(style: UITableViewRowActionStyle.default, title: "Supprimer") { (action , indexPath ) -> Void in
self.isEditing = false
//ackAction.backgroundColor = UIColor.orange
}
return [titleBtn]
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
But, when I run my app, the text of the button is changed but the delete doesn't work (i can't delete data from my array and the row of my tableview). Before to add this functions all worked perfectly.
A detail: in the canEditRowAt function, I tried to return false too...
Thanks by advance
if you want to change the text of the delete button, conform this method in the UITableViewDelegate:
func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String?
{
return "Your new title"
}
To delete the item from the array conform this method
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath)
{
if (editingStyle == UITableViewCellEditingStyle.delete)
{
yourDataSourceArray.remove(at: indexPath.row)
yourTableView.reloadData()
}
}
You can either use default delete action and change its title like this.
func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String?
{
return "MyAction"
}
or you can create your own action buttons like this.
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let deleteAction = UITableViewRowAction(style: .default, title: "Delete") {action in
//handle delete
}
let editAction = UITableViewRowAction(style: .normal, title: "Edit") {action in
//handle edit
}
return [deleteAction, editAction]
}
So i've searched the site for an answer to this question and there are some decent results but nothing recent since Xcode 7 is no longer in beta and swift 2.0 is now the standard.
I've used the following code in order to make it so that a 'swipe left' feature will cause something to happen to a UITableViewCell -
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath){
if editingStyle == UITableViewCellEditingStyle.Delete {
// ...
}
}
I understand that this is something that Apple now supplied in their API and use of external classes is not needed.
I also understand you can customize the actions that come up from this swipe using this native code:
func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]? {
let more = UITableViewRowAction(style: .Normal, title: "More") { action, index in
print("more button tapped")
}
Is there any modern native code which would define a 'right swipe' on a UITableViewCell?
func tableView(_ tableView: UITableView,
leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?
{
let closeAction = UIContextualAction(style: .normal, title: "Close", handler: { (ac:UIContextualAction, view:UIView, success:(Bool) -> Void) in
print("OK, marked as Closed")
success(true)
})
closeAction.image = UIImage(named: "tick")
closeAction.backgroundColor = .purple
return UISwipeActionsConfiguration(actions: [closeAction])
}
func tableView(_ tableView: UITableView,
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?
{
let modifyAction = UIContextualAction(style: .normal, title: "Update", handler: { (ac:UIContextualAction, view:UIView, success:(Bool) -> Void) in
print("Update action ...")
success(true)
})
modifyAction.image = UIImage(named: "hammer")
modifyAction.backgroundColor = .blue
return UISwipeActionsConfiguration(actions: [modifyAction])
}
The feature is available on iOS 11.0+ and Mac Catalyst 13.0+. For options on the left use the leading and on the right use the trailing methods.
Just implement the UITableViewDelegate methods and return your UISwipeActionsConfiguration object. You could optionally include an image or just the text as the title of the button.
func tableView(_ tableView: UITableView,
leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?
func tableView(_ tableView: UITableView,
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?
Make sure to enable editing in your data source delegate otherwise the swipe options are disabled.
// True for enabling the editing mode
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
No, there Is no such native API as of now.
In my iOS app I have a UITableViewController whose rows contain data and data picker. I want to change te function of the buttons Edit and Done. I would like the Edit button to allow the user to write/insert data (instead of deleting rows), while I would like the Done button to save to save the data (and not just exiting the edit mode). I've added to my code the following:
// The following two functions remove the red minus sign
override func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle {
return UITableViewCellEditingStyle.None
}
override func tableView(tableView: UITableView, shouldIndentWhileEditingRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return false
}
in order to avoid appearing the circle red minus, but how can I override the functions according to Edit or Done value of the button? I heard about delegates but I'm new to iOS and I don't know what they are...if someone can explain me I would be grateful.
Please try this code block :-)
import UIKit
// You can initialise an instance of this class manually or configure it on storyboard
class TableViewController: UITableViewController {
// MARK:- This part is for demo purpose
override func viewDidLoad() {
super.viewDidLoad()
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
cell.backgroundColor = UIColor.yellowColor()
cell.textLabel?.text = "\(indexPath.row)"
return cell
}
// MARK:- From here is the main part to answer your question
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
override func tableView(tableView: UITableView, shouldIndentWhileEditingRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return false
}
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
let editAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Edit") { (action, indexPath) -> Void in
print("Write/Insert data here")
}
let doneAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Done") { (action, indexPath) -> Void in
print("Save data here")
}
doneAction.backgroundColor = UIColor.blueColor()
return [doneAction, editAction]
}
}
You will see the result like this