Disable delete option for specific UITableViewCell in a UITableView - ios

for UITableViews I have added three difference cells
How to Enable delete option for specific cell
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
switch dataSource[indexpath.section].menu {
case "Attachment":
if editingStyle == AttachmentTableViewCell.EditingStyle.delete {
attachmentList.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: UITableView.RowAnimation.automatic)
}
break
default:
break
}
}
In this case it's shows Delete option for other cell too.
How to stop showing the Delete option on swipe to other UITableViewCell.

Implement tableView(_:editingStyleForRowAt:) and return none for the index paths which should not show the delete option.

This worked for me.
Overriding this func from UITableViewController
or implementing it UITableViewDelegate
override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?
The indexpaths were you want an action return a UISwipeActionsConfiguration else return nil.
like this
override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
// return nil on specific rows
guard indexPath.row != 1 else {
return nil
}
let contexualAction = UIContextualAction(style: .normal, title: "Action") { _, _, _ in
// Do Action
}
let swipeAction = UISwipeActionsConfiguration(actions: [contexualAction])
return swipeAction
}

Related

UITableViewDiffableDataSource is not calling tableView(_:commit:forRowAt:)

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
}

Reordering cells in UITableView takes out functionality of cells in Swift?

I am using JSON to parse data from Spotify and add songs into a UITableView. The songs play fine, and I added functionality for deleting cells, but when adding functionality for reording cells, I can''t play songs and I can't swipe to delete them either. Any ideas would be appreciated.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.tableView.isEditing = true
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
This adds the album image and song name to the TableView.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
let mainImageView = cell?.viewWithTag(2) as! UIImageView
mainImageView.image = posts[indexPath.row].mainImage
let mainLabel = cell?.viewWithTag(1) as! UILabel
mainLabel.text = posts[indexPath.row].name
return cell!
}
This adds the swipe to delete functionality.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
posts.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.
}
}
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
return .none
}
override func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
return false
}
This adds the reordering functionality.
override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let movedObject = self.posts[sourceIndexPath.row]
posts.remove(at: sourceIndexPath.row)
posts.insert(movedObject, at: destinationIndexPath.row)
debugPrint("\(sourceIndexPath.row) => \(destinationIndexPath.row)")
self.tableView.reloadData()
}
You don't want to set
self.tableView.isEditing = true
in viewDidLoad. This takes you from the "normal" mode where you can select a cell, or other elements in a cell. Setting "self.tableview.isEditing" is the equivalent of hitting an edit button on the top right-hand corner of many tableViews.

How to add swipe to remove table cells from UITableViewController in Swift?

There aren't very many tutorials for this for Swift 5.1 and I am pretty novice. I am wondering how to make the tableView delete override func work for my code. It gives me an error on objects because they are an unresolved identifier. Also what would go in the insert?
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
let mainImageView = cell?.viewWithTag(2) as! UIImageView
mainImageView.image = posts[indexPath.row].mainImage
let mainLabel = cell?.viewWithTag(1) as! UILabel
mainLabel.text = posts[indexPath.row].name
return cell!
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
objects.remove(at: indexPath.row) // Here I get an error because objects is an unresolved identifier.
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. I am also confused as to what goes here.
}
}
All of the information you will need can be found here.
This method allows the delegate to customize the editing style of the cell located atindexPath. If the delegate does not implement this method and the UITableViewCell object is editable (that is, it has its isEditing property set to true), the cell has the UITableViewCell.EditingStyle.delete style set for it.
This is what you are missing:
https://developer.apple.com/documentation/uikit/uitableviewdelegate/1614869-tableview
override func tableView(_ tableView: UITableView,
editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
return .delete
}
Here you are using an object to remove data for the index path which is wrong you are using "posts" named array to show values on the table view. So, this "posts" array contains objects which are visible on the table view.
So, here when you want to remove object then this means you have to remove objects from the "posts" array. Below the attached function with some changes, you can check this it will work for you.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
posts.remove(at: indexPath.row) // Here I get an error because objects is an unresolved identifier.
tableView.deleteRows(at: [indexPath], with: .fade)
} else if editingStyle == .insert {
posts.append("Here insert what you want") //inside append you can append/insert values to the array
tableView.reloadData()
}
}

trailingSwipeActionsConfigurationForRowAt not work in stability

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
}

UITableView leaves pattern of blank cells after deleting one or more rows

TableView leaving blank cells
Repeating blank cells
When deleting rows from the bottom
After deleting a row or multiple rows in my TableView, the TableView Cells seems to shift or refresh in an odd way that creates multiple blank rows. Seems to start with rows that are off-screen.
I have tried using beginUpdates, endUpdates, and performBatchUpdates with no change in behavior. I have also confirmed that the data source array is being updated properly and so is the number of rows in the tableview.
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return paymentsArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! UserPaymentCell
let payment = paymentsArray[indexPath.row]
cell.payment = payment
cell.selectionStyle = .none
cell.preservesSuperviewLayoutMargins = false
cell.separatorInset = UIEdgeInsets(top: 0, left: 75, bottom: 0, right: 0)
cell.layoutMargins = UIEdgeInsets.zero
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
let payment = paymentsArray[indexPath.row]
if payment.payerUID == Auth.auth().currentUser?.uid {
return true
} else {
return false
}
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
let payment = paymentsArray[indexPath.row]
switch editingStyle {
case .delete:
deleteBillAndRefreshTotals(bill: payment, indexPath: indexPath)
default:
return
}
}
func deleteBillAndRefreshTotals(bill: Bill, indexPath: IndexPath) {
print("DELETING CELL")
paymentsArray.remove(at: indexPath.row)
paymentsTableView.deleteRows(at: [indexPath], with: .automatic)
print(paymentsTableView.numberOfRows(inSection: 0))
}
Expected results - for row to be deleted and all cells above or below the deleted cell to shift together.
override func prepareForReuse() {
super.prepareForReuse() // <--
self.nameLabel.text = nil
self.backgroundColor = .white
}
Within my custom cell implementation, the above function was being called without calling super.prepareForReuse first. Therefore causing the issues above.
after perform delete operations call reloaddata method so after that tableview will refresh.
func deleteBillAndRefreshTotals(bill: Bill, indexPath: IndexPath) {
print("DELETING CELL")
paymentsArray.remove(at: indexPath.row)
paymentsTableView.reloaddata()
print(paymentsTableView.numberOfRows(inSection: 0))
}
You can try this code :
paymentsTableView.beginUpdates()
paymentsTableView.deleteRows(at: [indexPath], with: .automatic)
paymentsTableView.endUpdates()

Resources