Tableviews, cell editing and dragging - swift

I have a swift app with a tableview where the cells are dynamic. What I want to achieve is that the user can side swipe a cell and it brings up two tiles, one for editing and one for deleting (an example of this would be in the messages app where you side swipe for the delete option)
I have got the two tiles to show by using:
func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
let editRowAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: " Edit ", handler:{action, indexpath in
});
moreRowAction.backgroundColor = UIColor.orangeColor()
let deleteRowAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Delete", handler:{action, indexpath in
return [editRowAction, deleteRowAction]
}
The first issue I have is how can I programatically close the side swipe when the edit option is selected so that the tiles are hidden and the user can see the textfield in the cell?
Second issue, when a cell is in edit mode I want to be able to move the cell in the tableview: following tutorials I have implemented the below:
func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
func tableView(tableView: UITableView, moveRowAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath) {
let sourceRow = sourceIndexPath.row;
let destRow = destinationIndexPath.row;
let object = ArrayList.objectAtIndex(sourceRow)
ArrayList.removeObjectAtIndex(sourceRow)
ArrayList.insertObject(object, atIndex: destRow)
}
and set the following when I want to put cell into moving mode
TableView.setEditing(true, animated: true)
Which sort of does what I want, however when I put it into editing mode I get the delete icon on the left side of the cell (red circle with white dash) which I don't want, ideally I'd like my own icon so user can select and drag cell around but I feel this might be pushing it slightly.
Thanks

To get rid of the remove button you should be able to set the editing style to none (or something else)
func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle {
return tableView.editing ? UITableViewCellEditingStyle.None : UITableViewCellEditingStyle.Delete
}
To hide to buttons after a swipe you can either set tableView.editing or reload the cell.
tableView.editing = false
alt
tableView.reloadRowsAtIndexPaths(indexPaths: [NSIndexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
//All the involved delegate methods:
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
let editRowAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: " Edit ", handler:{action, indexpath in
self.tableView.editing = false
})
let deleteRowAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Delete", handler:{action, indexpath in
self.tableView.editing = false
})
return [editRowAction, deleteRowAction]
}
func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle {
return tableView.editing ? UITableViewCellEditingStyle.None : UITableViewCellEditingStyle.Delete
}
func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
func tableView(tableView: UITableView, moveRowAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath) {
}

Related

Why is the leading swipe action also duplicated as a trailing action?

I have implemented a leading swipe action ('Delete') on my tableView which for a reason I can't figure out is also appearing as a trailing swipe action. See code below:
func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) ->
UISwipeActionsConfiguration? {
let delete1 = deleteAction(at: indexPath)
return UISwipeActionsConfiguration(actions: [delete1])
}
func deleteAction(at indexPath: IndexPath) -> UIContextualAction {
let action = UIContextualAction(style: .destructive, title: "Delete") { (action, view, completion) in
self.delete(at: indexPath)
}
return action
}
I used to have a trailing swipe action, but I deleted this function out completely. When I change 'leadingSwipeActionsConfigurationForRowAt' to 'trailingSwipeActions...' then only the trailing swipe action appears. Be grateful if anyone could tell me what I've missed. Thanks.
Use this code to prevent trailingSwipeAction()
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle
{
return .none
}
or
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
return UISwipeActionsConfiguration(actions: [])
}
Because that is the default behaviour, when swipes are enabled. You can do something like this to disable swipes on the trailing side, if you want to implement the destructive delete action on the left only.
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
return UISwipeActionsConfiguration(actions: [])
}
By passing an empty set of actions, the trailing swipe will disappear due to having 0 set of possible actions.

didSelectRowAt indexPath does not when tapped (but does trigger when cell is swiped in either direction)

This is a weird (but should be simple) one. I am simply trying to tap a custom UITableViewCell and trigger a segue to go to another ViewController. didSelectRowAt indexPath does not trigger when the cell is tapped, but it oddly enough does when the cell is swiped from right to left or left to right.
My didSelectRowAt indexPath:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You selected cell number: \(indexPath.row)!")
self.performSegue(withIdentifier: "mySegue", sender: self)
}
(as suggested here)
My ViewController:
import UIKit
class myViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIPickerViewDelegate {
var myData = NSDictionary()
override func viewDidLoad() {
super.viewDidLoad()
fetchMyData()
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.rowHeight = 100
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func fetchMyData() {
// ... fetches my data
}
////////////////////////////////////////////////
//Table view data source
//set number of sections in table
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
//set number of rows in table
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myData.count
}
//delegate information to cells in table rows
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("running tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell...")
// Dequeue cell
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! myCustomCell
cell.cellAmount!.text = "Hello World"
cell.cellTextField2!.text = "Some info"
cell.cellTextField3!.text = "Some other info"
cell.backgroundCardView.backgroundColor = UIColor.red
cell.backgroundCardView.layer.cornerRadius = 8.0
cell.backgroundCardView.layer.masksToBounds = false
cell.backgroundCardView.layer.shadowColor = UIColor.black.withAlphaComponent(0.9).cgColor
cell.backgroundCardView.layer.shadowOffset = CGSize(width: 0, height: 0)
cell.backgroundCardView.layer.shadowOpacity = 0.9
print("finished tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell...")
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You selected cell number: \(indexPath.row)!")
self.performSegue(withIdentifier: "mySegue", sender: self)
}
// func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
// let delete = UITableViewRowAction(style: .destructive, title: "Delete") { (action, indexPath) in
// // delete item at indexPath
// }
// let share = UITableViewRowAction(style: .normal, title: "Disable") { (action, indexPath) in
// // share item at indexPath
// }
//
// share.backgroundColor = UIColor.blue
//
// return [delete, share]
// }
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
return []
}
// Reload the table data when a change is made
// func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
// self.tableView.reloadData()
// }
/*
// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
*/
/*
// Override to support rearranging the table view.
override func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
}
*/
/*
// Override to support conditional rearranging of the table view.
override func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return false if you do not want the item to be re-orderable.
return true
}
*/
//Table view data source (end)
////////////////////////////////////////////////
}
I have already tried the following as well:
-didSelectRowAtIndexPath: not being called
How to detect Cell selection in UITableView - Swift
Push segue from UITableViewCell to ViewController in Swift
How to tap cell's buttons in UITableViewCell without actionning the cell's segue
is it possible to segue from a UITableViewCell on a UIView to another view
UITableView didSelectRowAt is not called iOS 10, but works for 9.3.5
The UITableViewCell does have UIView and multiple UITextFields, so I did make sure that User Interaction Enabled is unchecked for the UIView and UITextFields.
EDIT: I have also looked in the debugger navigator and can see that the CPU usage percentage increases when I tap a cell (). Maybe there is a way to step deeper and see why that tap doesn't then cause the didSelectRowAt indexPath to be triggered..?
Does anyone know what could possibly be causing didSelectRowAt indexPath to not be triggered when the cell is tapped?
I faced the same issue and found an answer that was applicable for me here. I had this code for keyboard dismiss in viewWillAppear:
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self.view, action: #selector(UIView.endEditing(_:))))
After commenting this line tapping started to work! But now I will have to find better way for keyboard dismiss. :) Kindly check your exstensions, probably you used similar solution for keyboard dismiss.

editActionsForRowAtIndexPath not called, both commit editingStyle and canEditRowAt present

I am trying to add a second swipe button to my table view rows, but editActionsForRowAtIndexPath is not being called when I swipe a row. This is despite my class declaring both
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
and
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
}
Both of these are called when I swipe because I also have a regular delete button.
My editActionsForRowAtIndexPath function is written as follows:
func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewRowAction? {
print("triggered!")
let more = UITableViewRowAction(style: .default, title: "More") { action, index in
print("more button tapped")
}
more.backgroundColor = UIColor.blue
return more
}
Thanks in advance!
You are not using the updated Swift 3 API for this particular method. The delegate method should be written as:
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
print("triggered!")
let more = UITableViewRowAction(style: .default, title: "More") { action, index in
print("more button tapped")
}
more.backgroundColor = UIColor.blue
return [more]
}

Delete Button Not Appearing in UITableViewCell

In my program, I am trying to implement the swipe to delete feature on UITableViewCells but the delete button is not appearing even though the cell does move as it should when swiping. Does anyone know why?
Here is my code:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var cell = tableView.cellForRowAtIndexPath(indexPath) as resultsCell
otherName = cell.usernameLbl.text!
otherProfileName = cell.profileNameLbl.text!
self.performSegueWithIdentifier("goToConversationVC", sender: self)
}
override func viewWillAppear(animated: Bool) {
self.navigationItem.hidesBackButton = true
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return resultsUsernameArray.count
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 120
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:resultsCell = tableView.dequeueReusableCellWithIdentifier("Cell") as resultsCell
cell.usernameLbl.text = self.resultsUsernameArray[indexPath.row]
cell.profileNameLbl.text = self.resultsProfileNameArray[indexPath.row]
resultsImageFiles[indexPath.row].getDataInBackgroundWithBlock {
(imageData: NSData!, error:NSError!) -> Void in
if error == nil {
let image = UIImage(data: imageData)
cell.profileImg.image = image
}
}
return cell
}
#IBAction func logoutBtn_click(sender: AnyObject) {
PFUser.logOut()
self.navigationController?.popToRootViewControllerAnimated(true)
}
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
// called when a row is moved
func tableView(tableView: UITableView,
moveRowAtIndexPath sourceIndexPath: NSIndexPath,
toIndexPath destinationIndexPath: NSIndexPath) {
// remove the dragged row's model
let val = friends.removeAtIndex(sourceIndexPath.row)
// insert it into the new position
friends.insert(val, atIndex: destinationIndexPath.row)
}
func tableView(tableView: UITableView,
commitEditingStyle editingStyle: UITableViewCellEditingStyle,
forRowAtIndexPath indexPath: NSIndexPath) {
switch editingStyle {
case .Delete:
// remove the deleted item from the model
friends.removeAtIndex(indexPath.row)
// remove the deleted item from the `UITableView`
resultsTable.editing = resultsTable.editing
resultsTable.editing = true
self.resultsTable.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
default:
return
}
}
func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle {
return .Delete
}
Your UITableView is too big hence the delete button is out of the screen

Swift - Reorder UITableView cells

I do know that it's not too hard to do it in objective C , the problem is I'm learning Swift by skipping Objective C.
https://developer.apple.com/library/ios/documentation/userexperience/conceptual/tableview_iphone/ManageReorderRow/ManageReorderRow.html
However is there anything equivalent to the link above in Swift?
I have tried this...here is the code
In my example code there is button that starts the editing ---
Action Method of the button -->
#IBAction func editTableView (sender:UIBarButtonItem)
{
if listTableView.editing{
//listTableView.editing = false;
listTableView.setEditing(false, animated: false);
barButton.style = UIBarButtonItemStyle.Plain;
barButton.title = "Edit";
//listTableView.reloadData();
}
else{
//listTableView.editing = true;
listTableView.setEditing(true, animated: true);
barButton.title = "Done";
barButton.style = UIBarButtonItemStyle.Done;
//listTableView.reloadData();
}
}
And the related UITableView delegate methods -->
// The editing style for a row is the kind of button displayed to the left of the cell when in editing mode.
func tableView(tableView: UITableView!, editingStyleForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCellEditingStyle
{
if (false == self.editing && !indexPath){
return UITableViewCellEditingStyle.None;
}
if (self.editing && indexPath.row == countryList.count){
return UITableViewCellEditingStyle.Insert;
}
else{
return UITableViewCellEditingStyle.Delete;
}
//return UITableViewCellEditingStyle.Delete;
}
// Update the data model according to edit actions delete or insert.
func tableView(tableView: UITableView!, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath!)
{
if editingStyle == UITableViewCellEditingStyle.Delete{
countryList.removeAtIndex(indexPath.row);
self.editTableView(barButton);
listTableView.reloadData();
}
else if editingStyle == UITableViewCellEditingStyle.Insert{
countryList.append("New Country");
}
}
// Determine whether a given row is eligible for reordering or not.
func tableView(tableView: UITableView!, canMoveRowAtIndexPath indexPath: NSIndexPath!) -> Bool
{
return true;
}
// Process the row move. This means updating the data model to correct the item indices.
func tableView(tableView: UITableView!, moveRowAtIndexPath sourceIndexPath: NSIndexPath!, toIndexPath destinationIndexPath: NSIndexPath!)
{
let item : String = countryList[sourceIndexPath.row];
countryList.removeAtIndex(sourceIndexPath.row);
countryList.insert(item, atIndex: destinationIndexPath.row)
}
You can also download full code Here
All the same rules apply as in Objective-C. You set the table view data source and delegate just like you would in Objective-C.
func tableView(tableView: UITableView!, canMoveRowAtIndexPath indexPath: NSIndexPath!) -> Bool {
return true // Yes, the table view can be reordered
}
func tableView(tableView: UITableView!, moveRowAtIndexPath fromIndexPath: NSIndexPath!, toIndexPath: NSIndexPath!) {
// update the item in my data source by first removing at the from index, then inserting at the to index.
let item = items[fromIndexPath.row]
items.removeAtIndex(fromIndexPath.row)
items.insert(item, atIndex: toIndexPath.row)
}
If you need finer grain control, you can also implement
func tableView(tableView: UITableView!, targetIndexPathForMoveFromRowAtIndexPath sourceIndexPath: NSIndexPath!, toProposedIndexPath proposedDestinationIndexPath: NSIndexPath!) -> NSIndexPath! {
…
}
Now there is a library for this reorder function: LPRTableView.
Converted Above Answer methods in Swift 3.0
// Determine whether a given row is eligible for reordering or not.
func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
return true
}
// Process the row move. This means updating the data model to correct the item indices.
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let item : Dictionary<String, Any> = arrInterval[sourceIndexPath.row]
arrInterval.remove(at: sourceIndexPath.row)
arrInterval.insert(item, at: destinationIndexPath.row)
}