I just implemented trailingSwipeActionsConfigurationForRowAt and leadingSwipeActionsConfigurationForRowAt to add swipe actions to my UITableViewCells.
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration
func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration
This triggers (on my UITableViewController) func setEditing(_ editing: Bool, animated: Bool) to be called with editing = true, which in turn also triggers my editing animation and other changes that I want when going into edit mode. (The contextual actions are not related to editing/deleting).
I don't want this, but have yet to find a way to disable this behaviour, even just detecting that setEditing is called via the swipe actions.
Any ideas?
I was able to solve this by triggering the "real" edit mode differently.
On my UITableViewController:
class MyTableViewController: UITableViewController {
var realEditMode: Bool = false
func setRealEditing(_ editing: Bool) {
realEditMode = editing
setEditing(realEditMode, animated: true)
}
// See Note 1 below
#available(iOS 11.0, *)
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration {
guard let item = itemForIndexPath(indexPath) else {
return UISwipeActionsConfiguration(actions: [])
}
if realEditMode {
return UISwipeActionsConfiguration(actions: [
buildActionConfiguration(.delete, fromIndexPath: indexPath)
])
} else {
return UISwipeActionsConfiguration(actions: [
buildActionConfiguration(.read, fromIndexPath: indexPath)
])
}
}
#available(iOS 11.0, *)
func buildActionConfiguration(_ action: MyCellActionEnum, fromIndexPath indexPath: IndexPath) -> UIContextualAction {
// logic to build `UIContextualAction`
}
}
And in my UITableViewCell check if the editing flag is set by manual triggering or by swipe edit triggering:
class MyCell: UITableViewCell {
var myTableViewController: MyTableViewController?
override func setEditing(_ editing: Bool, animated: Bool) {
if editing && !(myTableViewController?.realEditMode ?? true) {
return
}
super.setEditing(editing, animated: animated)
}
}
Then on the edit buttons in the UI I changed setEditing(true/false, animated: true) to setRealEditing(true/false) instead.
Note 1
One problem I found was that when using trailingSwipeActionsConfigurationForRowAt was that the delete button (⛔️) didn't work anymore. Tapping it did not trigger the confirmation swipe.
I found that there had to exist a trailingSwipeActionsConfigurationForRowAt with a UIContextualAction that was initialized with UIContextualAction(style: .destructive) (i.e had destructive style). This is the item that is then used for displaying the delete confirmation.
However I did not want that item to be visible when the regular swipe actions were used, so to only show it one "real edit mode" I used the realEditMode flag.
This worked for me and didn't seem too hacky. If anything more official pops up I'm more than happy to change the accepted answer.
Related
I created a fairly simple tableView for selecting a category for an item model. Everything was working fine yesterday. Today I have been trying to switch the tableView data source over to a UITableViewDiffableDataSource as I want to wrap my head around the API. I have the entire tableView back up and running EXCEPT I can no longer edit my rows!
the setEditing logic works as the newCategoryButton is disabled when I tap the edit button in the navigation bar and is enabled when I tap it again. However, I am never able to swipe to delete and the delete icons do not show up next to my rows in editing mode.
I tried removing setEditing, emptying out commit editingStyle and simply setting canEditRow to return true and still nothing.
Any help would be greatly appreciate. Thank!
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
let section = dataSource.snapshot().sectionIdentifiers[indexPath.section]
if section == .noneSelected {
return false
} else {
return true
}
}
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: true)
if editing == true {
newCategoryButton.isEnabled = false
} else {
newCategoryButton.isEnabled = true
}
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
categories.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
}
}
override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
return true
}
override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let movedCategory = categories.remove(at: sourceIndexPath.row)
categories.insert(movedCategory, at: destinationIndexPath.row)
}
screenShot after edit button is tapped
The trouble is that canEditRowAt is a data source method. You (the view controller) are now not the data source; the diffable data source is. You need to implement this method inside the diffable data source. That's typically done by subclassing the diffable data source class, so that you can override this method. Otherwise, the diffable data source just returns its default value — which is false, which is why you currently can't edit any rows.
I have swipe to delete code here and it my custom TableViewCell I have implemented setSelected method like below ..
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
// tableView.allowsSelectionDuringEditing = false
if tableView.indexPathForSelectedRow != nil, tableView.indexPathForSelectedRow == indexPath {
return UITableViewCell.EditingStyle.none
}
return UITableViewCell.EditingStyle.delete
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
//some code here
}
The logic will do tableview expand collapse based on selection ..but the problem here is if I swipe to delete setSelected also triggers.. not sure how to prevent that any help would be appreciated..
Try this in cellForRow
let cell = tableView.dequeueReusableCell(withIdentifier: "cell_identifier", for: indexPath)
cell.selectionStyle = UITableViewCell.SelectionStyle.none
//or this based on swift version
cell.selectionStyle = .none
return cell
I'm not sure what the appearance you are going for but this will produce the same effect.
If swipe is across the whole screen it will trigger the delete without button press.
If half swipe you can present buttons for user to choose options.
Half swipe (Shows options):
Full swipe (Triggers delete button):
Try adding these two tableView delegate methods for a swipe to delete
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Determine which rows should be editable
return true
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath:
IndexPath) -> [UITableViewRowAction]? {
let button1 = UITableViewRowAction(style: .default, title: "Delete") {
action, indexPath in
print("delete pressed")
// Consider alert to confirm that it was intentionally deleted
}
button1.backgroundColor = UIColor.red
// Create any buttons you want
return [button1]
}
I tried it with and without your tableView method and it seemed to work fine both ways but I don't think your method is necessary if you choose this route
Hope this helps
When my tableView goes into editing mode there's an icon on each side of my cell: the icon on the right is the default icon for rearranging the order of cells, and the icon on the left is the default icon for deleting a cell. I can easily interact with the rearranging icon with no problems, however, tapping on the delete icon gives no response. It only works when I do an awkward right swipe then left swipe on it. Below I've placed what I believe to be all the relevant code to my situation.
Some background information: I have three touch gestures in the view (a single tap, double tap, and long press). I've tried disabling them with conditions for when the editing mode is enabled. I've even tried deleting the touch gestures beforehand, but that didn't help. I thought it might be an auto layout issue that's not registering the area for the delete button, but the rearranging button works so shouldn't this? Both the my cell's leading and trailing constants are +10 to the view. Furthermore, isEditing is controlled by a UIButton. Finally, i have the tableView delegate set to self, and touch interaction when editing is enabled.
Any help would be appreciated. Thank you!
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
if isEditing { return true }
return false
}
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
if isEditing { return .delete }
return .none
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == .delete) {
countBeforeDeletingCell = dataSource.data.count
dataSource.data.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .none)
}
}
override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let movedObject = dataSource.data[sourceIndexPath.row]
dataSource.data.remove(at: sourceIndexPath.row)
dataSource.data.insert(movedObject, at: destinationIndexPath.row)
tableView.reloadData()
}
Hey If the you have use UITableViewController and using isEditing iOS defalut mode you have to override setEditing Mode and Changing Edit button Click.
iOS self.navigationItem.rightBarButtonItem = self.editButtonItem in editButtonItem is default UIViewController method. editButtonItem button Action override.
Swift Example:
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
tableView.reloadData()
}
it works for me on swift 5.7.May be you should config TrailingSwipeActionConfiguration like this:
unc tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .destructive,
title: "Delete") { [weak self] _, _, complete in
//remove row in you data model here
tableView.deleteRows(at: [indexPath], with: .automatic)
complete(true)
}
deleteAction.backgroundColor = .red
return UISwipeActionsConfiguration(actions: [deleteAction])
}
I am not sure i understood it correctly, the documentation is a bit confusing here, but given the following code in the body of a UITableView delegate:
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let action = UITableViewRowAction(style: UITableViewRowAction.Style.normal,
title: "Do Something!") { (_, indexPath) in
self.doSomething()
}
return [action]
}
the call to doSomething() method is performed in simulator on completion of the swipe-to-left action beside on tap on the "Do Something!" button.
I don't want the call to be performed twice. Is there something wrong with my configuration or i have not understood the purpose of UITableViewRowAction?
TLDR: i want swipe action callback to be triggered only when the button that appears is tapped.
Thank you.
To prevent this behavior you need an implementation of new iOS.
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let myAction = UIContextualAction(style: .someStyle, title: "Some tilte") { (action, sourceView, completionHandler) in
print("action has been triggered")
completionHandler(true)
}
let preventSwipeFullAction = UISwipeActionsConfiguration(actions: [myAction ])
preventSwipeFullAction .performsFirstActionWithFullSwipe = false // set false to disable full swipe action
return preventSwipeFullAction
}
TableView Trailing Swipe Action
Remember tableView.isEditing need to be false to allow trailingSwipeActionsConfigurationForRowAt be called.
completionHandler(true) // passing true enable handler call action
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
checkRequirements() { () -> Void in
var room = self.rooms[indexPath.row]
self.roomForPreview = room
self.indexPathForPreview = indexPath
self.performSegueWithIdentifier(CONSTANTS.SegueLobbyToRoomPreview, sender: self.view)
}
}
I perform a segue when a user selects a cell, but when I unwind, I find that the cell still has a grey background.
How can I make the cell grey only on touch? Once the touch is gone, the cell goes back to normal color.
Use deselectRowAtIndexPath:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
...
If you're using a UITableViewController, you can set the clearsSelectionOnViewWillAppear property to true - this will work as well.