Swift - Reorder UITableView cells - swift

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)
}

Related

Unable to swipe to delete with tableview using diffable data source in iOS 13

I'm updating a UITableViewController to use the new UITableViewDiffableDataSource, I have everything working except Swipe to delete.
This is an example of how I use swipe to delete
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let lockedAction = UIContextualAction(style: .normal, title: "TEST") { (_, _, completion) in
print("tapped....")
completion(true)
}
return UISwipeActionsConfiguration(actions: [lockedAction])
}
But this doesn't not work in a UITableViewController that has UITableViewDiffableDataSource
There is no swipe, a break point within the method is never called either
I thought this was a beta bug, but I updated to Xcode 11 GM and that same thing is occurring.
Thanks for any advice
It's true that the docs for tableView(_:canEditRowAt:) say:
The method permits the data source to exclude individual rows from being treated as editable. Editable rows display the insertion or deletion control in their cells. If this method is not implemented, all rows are assumed to be editable
However UITableViewDiffableDataSource, does implement that method and it seems to return false by default (though I can't find that documented anywhere).
In fact the sample code from WWDC 2019 sessions 215 and 220 implements a custom UITableViewDiffableDataSource subclass like this:
class DataSource: UITableViewDiffableDataSource<SectionType, ItemType> {
// ...
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
// ...
}
You should subclass UITableViewDiffableDataSource and return true for the rows you want to enable this for in:
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool
If your custom UITableViewDiffableDataSource class inherits from another custom UITableViewDiffableDataSource class, you need to implement the trailingSwipeActionsConfigurationForRowAt method in the parent class with default implementation and then override it in the child class in order to get called:
Parent class:
class ParentDataSource: UITableViewDiffableDataSource<SectionType, ItemType> {
// ...
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
return nil
}
// ...
}
Child class:
class ChildDataSource: ParentDataSource {
// ...
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let lockedAction = UIContextualAction(style: .normal, title: "TEST") { (_, _, completion) in
print("tapped....")
completion(true)
}
return UISwipeActionsConfiguration(actions: [lockedAction])
}
// ...
}
I had the same issue and this is the only solution it worked for me.

Deleting a row not working in UITableViewController

I have a dictionary that I have made called places and I made each cell in the tableViewController show each parts of the dictionary. I know the function to delete the rows in the controller, but when I run the app and do the action of deleting nothing happens.
// This is my entire TableViewController. I have another ViewController that appends the dictionary.
var places = [Dictionary<String,String>()]
var activePlace = -1
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
if UserDefaults.standard.object(forKey: "places") != nil { //checks if the list is not empty
places = UserDefaults.standard.object(forKey: "places") as! [Dictionary<String, String>]
}
if places.count == 1 {
places.remove(at: 0)
places.append(["name":"Ex. Eiffel Tower", "lat": "48.858324", "lon": "2.294764"])
}
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, canEditRowAt
indexPath: IndexPath) -> Bool {
return true
}
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return places.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
DispatchQueue.main.async {
self.tableView.reloadData()
}
cell.textLabel?.text = places[indexPath.row]["name"]
return cell
}
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
activePlace = indexPath.row
return indexPath
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "newPlace" {
activePlace = -1
}
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
tableView.deleteRows(at: [indexPath], with: .bottom)
places.remove(at: indexPath.row)
UserDefaults.standard.setValue(places, forKey: "places")
}
}
I am expecting that when I make the action of swiping to the left that it would delete the row and the contents of the cell from the tableView. Then it would also delete from the dictionary.
It's very complicate to delete table view cell sometimes. Your code is correct but you just need to remove a line. Instead of calling tableview.deleteRows you just delete the item of your dictionary and reload the table view.
Enable the table rows to editable using canEditRowAt function....
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
places.remove(at: indexPath.row)
UserDefaults.standard.setValue(places, forKey: "places")
tableView.reloadData()
}
}
Move deleteRows(at:with:) after remove(at:) in tableView(_: commit: forRowAt:) method, i.e.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
places.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .bottom) //here...
UserDefaults.standard.setValue(places, forKey: "places")
}
}
The main issue is the wrong declaration of the data source array. The pair of parentheses must be behind the brackets
var places = [Dictionary<String,String>]()
In the method tableView(_:commit:forRowAt:the order is wrong. First remove the row from the data source array then delete the row.
Two Don'ts
Do not use setValue:forKey with UserDefaults to save a single value.
Do not declare the data source array outside of the class.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
places.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .bottom)
UserDefaults.standard.set(places, forKey: "places")
}
}
Solved:
DispatchQueue.main.async was creating an endless loop of constantly reloading the data. By removing that the two functions of editing were allowed to run. I was able to perform the deleting action.

How can I move tableview section?

I am developing the application with swift. I stored the objection data named Categories. I've added the tableViewImage here and there is no problem here. I want to move the section of the tableview together with the cell, but it does not. The functions I wrote are below. Please help me.Thanks.
func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_tableview:UITableView, moveRowAtIndexPath sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath)
{
let temp = categories[sourceIndexPath.section]
categories[sourceIndexPath.section] = categories[destinationIndexPath.section];
categories[destinationIndexPath.section] = temp
}
func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
return false
}
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
return .none
}
You can try using this method, assigning the position of the section you want to move and the place where you want it to be at the end:
table.moveSection(0, toSection: 1)
You could also try deleting the section in the IndexSet or appropriate position, subtracting in 1 the number of sections of the table and then reinserting the section and increasing the sections by 1
let index = IndexSet(integer: 0)
self.number_sections -= 1
table.deleteSections(index, with: .fade)
let newIndex = IndexSet(integer: 1)
self.number_sections += 1
table.insertSections(newIndex, with: .fade)

Tableviews, cell editing and dragging

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) {
}

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