I am trying enable editing of my UITableView by providing an Edit button on the nav bar.
The intention is to allow the user to insert new items and delete existing items.
When clicking the Edit button iOS shows the "Delete" decoration (deleting works fine), but doesnt show "Insert"
I have added navigationItem.rightBarButtonItem = editButtonItem to my UIViewControllers viewDidLoad and have added the following methods to my UITableViewDataSource delegate:
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// Delete the row from the data source
AppDelegate.persistenceContext.delete(self.fetchedResultsController.object(at: indexPath))
AppDelegate.saveContext()
} else if editingStyle == .insert {
let current = self.fetchedResultsController.object(at: indexPath)
// do rest of insertion logic
}
}
What am I missing something?
Oh and out of interest, if this is possible would the new cell be inserted above or below the one on which the action was initiated?
For the record I can add custom swipe actions to the cells, but the users feedback is they prefer an edit button.
Any light would be appreciated, all the Googling has gotten me is using a button to insert a new row, or adding custom swipe actions.
You need to implement the delegate method:
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle
Return .delete, .insert, or .none for a given index path. Normally only one row (typically first or last) would return .insert and the rest would return .delete. Return .none for any row that can't be inserted or deleted.
It's not uncommon that you also need to override the setEditing(_ editing: Bool, animated: Bool) method to add/remove an extra row just for the purposes of showing the green + (.insert) icon.
An alternative is to show the + icon in the nav bar instead of having a row with the insert icon.
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 am showing a UITableView which is driven by RxRealmDataSources.
I need to perform some actions when a row in the table gets deleted.
Is there a way such that whenever a row gets deleted from the table, a function gets called with the indexpath of the deleted row?
Edit -
The UI of a cell of the UITableView in my app depends on 2 things -
A data object that is fetched from the realm db ( info )
The index position of the row
Whenever, a cell gets deleted, I need to update the UI of its next cell.
If the only way the db ever got updated was by the direct action of the user, then I could have used func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) method to get the indexPath of the cell that should be deleted and update the UI of the next cell.
However, the db is synced to cloud and the db is binded to the table view so that I do not have control on when cells gets added or deleted. It is for this reason, I wanted to know if there is a way to know when a cell is removed from UITableView
Due to the reusability of cells in UITableView, cells are not actually deleted until the table itself is deallocated.
I might assume that by 'deleting' cell you mean cell disappearing from the screen. In this case the following function of UITableViewDelegate might help you (called when the cell is not visible any more):
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath)
In a comment you said:
By 'deleting', I mean when the cell is removed from the tableview like when we swipe the cell to the left to remove it.
Since you tagged RxSwift, the solution is to use itemDeleted as in:
tableView.rx.itemDeleted
.subscribe(onNext: { print("delete item at index path \($0) from your model.")})
.disposed(by: bag)
If you aren't looking for an Rx solution, then your question is a dup of:
Add swipe to delete UITableViewCell
I was able to solve this by subclassing the UITableView class and overriding the func deleteRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation) method.
You have to implement 1 delegate method of UITableView.
trailingSwipeActionsConfigurationForRowAt
It's easy to implement. This delegate method will be called twice, one when you swipe and again when you press to delete a row.
`enter code here`
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let config = UISwipeActionsConfiguration(actions: [makeDeleteContextualAction(forRowAt: indexPath)])
config.performsFirstActionWithFullSwipe = true
return config
}
private func makeDeleteContextualAction(forRowAt indexpath:IndexPath) -> UIContextualAction {
let deleteAction = UIContextualAction(style: .destructive, title: LocalizableConstants.constLS_global_delete()) { (action, swipeButtonView, completion) in
let product = self.products[indexpath.row]
if let quantity = product.vo_quantity(), let amount = product.vo_priceFide() {
self.totalProducts -= Int(truncating: quantity)
self.totalAmount -= amount.doubleValue * quantity.doubleValue
}
DispatchQueue.main.async {
self.lbBasketNumber.text = String(self.totalProducts)
self.lbTotalAmount.text = String(self.totalAmount)
}
self.products.remove(at: indexpath.row)
self.tableView.deleteRows(at: [indexpath], with: .fade)
if #available(iOS 13.0, *) {
action.image = ImagesConstants.constIMG_XCA_mini_icon_red_trash()
action.image?.withTintColor(ConstantsColor.const_COLOR_RED())
action.backgroundColor = ConstantsColor.const_COLOR_WHITE()
} else {
action.title = LocalizableConstants.constLS_global_delete()
}
completion(true)
}
return deleteAction
}
like this
How can I have a swipe delete cell button like iOS mail application(image + text )?
I found this method but my image is not scale to size:
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
let deleteButton = UITableViewRowAction(style: .Destructive, title:"\nDelete", handler: { (action, indexPath) in
self.tableView.dataSource?.tableView?(self.tableView,commitEditingStyle: .Delete,
forRowAtIndexPath: indexPath)
return
})
deleteButton.backgroundColor = UIColor(patternImage: UIImage(named: "icon")!)
return [deleteButton]
}
thanks in advance
I am little confused with your explanation. If you can please provide some more explanation regarding button you want. Do you want Standard button which iOS provides itself or you want a customize button?
I am trying to address both aspects below.
If you need standard operation you can do it like this:
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// remove the item from the array if you want
array.remove(at: indexPath.row)
// delete the table view row
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
}
Also, i would like to recommend This.
If u just want to resize button image you can do it like this:
deleteButton.imageView?.contentMode = UIViewContentMode.scaleAspectFill
The table view will do that for you. You just need to have your table view/table view controller live in a navigation stack so there's a navigation bar, and then implement a couple of methods: tableView(_, canEditRowAt:) and tableView(_:,commit:forRowAt:).
You'll probably want to add an edit button to your navigation bar.
I just pushed a demo project called MasterDetailSwift that supports adding and removing cells to a table view. (It also shows the master/detail UI pattern, a good way to handle prepareForSegue using a switch statements, and a few other useful techniques.)
I am trying to learn Swift, but there is a problem in my project that drives me nuts.
I have a working list of data in a ViewController fed by parse.com. I managed to implement a swipe-feature that reveals buttons for both deleting and editing. That is working fine. Now I want the user to be able to reorder the cells. So I successfully implemented a button to put the table into editing-mode. My 2 problems with that are:
When I enter edit-mode I just want to be able to reorder the cells since editing and deleting is done via swipe (via "tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath)". How can I achive that the user doesn't see the 2 buttons for deleting and editing when in editing-mode and touching the delete-circle that is provided automatically?
Is it possible to remove the delete-circle altogether? Using "UITableViewCellEditingStyle.None" also disables the swipe-functionality.
Thanks in advance!
To avoid the round red delete button that appears when you put set UITableView isEditing to true at the left and does nothing when you click it, the minimum that worked for me was this (Swift 4, iOS 11)
// Avoid the round red delete button on the left of the cell:
func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
return false
}
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
return .none
}
I also have these functions, which probably interact:
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return savedTempTable.isEditing
}
// Including this function in the delegate enable left-swipe deleting
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath)
{
if editingStyle == .delete {
savedConversions.remove(at: indexPath.row)
}
}
// Including this function enables reordering
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath,to destinationIndexPath: IndexPath)
{
let elem = savedConversions.remove(at: sourceIndexPath.row)
savedConversions.insert(elem, at: destinationIndexPath.row)
}
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
return .none
}
override func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
return false
}
Although people can delete a row through swipe the delete-button in editing mode should not be removed. People may not know about the swipe gesture and by removing the delete button (which they already expect in editing mode) the app becomes more difficult to use.
If you really want to remove the delete button then you have to implement the delegate method tableView(_:editingStyleForRowAtIndexPath:). There you can return .None while the screen is in editing mode and .Delete while the screen is not.
To enable reordering you have to implement the data source methods tableView(_:canMoveRowAtIndexPath:) and tableView(_:moveRowAtIndexPath:toIndexPath:).
You follow this way to remove the delete Icon while editing:
-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath{
return UITableViewCellAccessoryNone;
}
How can I make a static table view to create an action when one of the cells is clicked in Swift?
I have created a static table like a general menu of the app, I can directly create a push segue when one of the cells are clicked. But at the same time when I click to one of the seques, I want the below function to be run. By draging a cell to the UITableView in storyboard the create action option is not appearing.
var goToProfiles = PFObject(className: "goToProfile")
goToProfiles["user"] = PFUser.currentUser()!.username
goToProfiles["goToUser"] = usernameLbl.text
goToProfiles.save()
If you use sections you will also need to query them.
override func tableView(tableView: UITableView,
didSelectRowAtIndexPath indexPath: NSIndexPath) {
print(indexPath.section)
print(indexPath.row)
if indexPath.section == 1 && indexPath.row == 1 {
// do something
}
}
I found the solution with the code below:
override func tableView(tableView: UITableView,
didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row == 1 {
//here you can enter the action you want to start when cell 1 is clicked
}
}
For swift 3 compatibility:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//Your action here
}