Don't delete some rows from UITableView - swift

I'm trying to implement the functionality to delete some rows from a table view and not others. In this case, everything in section 0 should not be deletable (so not swipe to delete either), but everything in section 1 should be able to. How can I implement this? Currently section 0 rows cannot delete, but when the user swipes, the delete action still appears.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if (indexPath.section == 0){
// dont delete the rows
} else {
if (editingStyle == .delete){
let fetchRequest: NSFetchRequest<Conversions> = Conversions.fetchRequest()
do {
let result = try PersistenceService.context.fetch(fetchRequest)
// Delete from CoreData and remove from the array
if (result.contains(allConversions[indexPath.row])){
PersistenceService.context.delete(allConversions[indexPath.row])
allConversions = allConversions.filter { $0 != allConversions[indexPath.row] }
PersistenceService.saveContext()
self.tableView.reloadData()
}
} catch {
print(error.localizedDescription)
}
}
}

UITableView has a method exactly for this purpose called canEditRowAt. You just need to return false when indexPath.section == 0
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return indexPath.section != 0
}

Related

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.

Deleting the last row and loading multiple rows at the same time

When I delete the last row from a tableview with:
tableView.deleteRows(at: [indexPath], with: .middle)
and change my table's data source to an another array:
if orders.isEmpty {
return ABCDArray.count
} else {
return orders.count
}
It gives me this error:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (20) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
How can I solve that? What is the problem with changing data source?
You need to remove the object from your data array before you call tableView.deleteRows(at: [indexPath], with: .middle). So, your code should look like this:
// Editing of rows is enabled
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
//when delete is tapped
orders.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .middle)
}
}
Was an interesting question so I decided to make it a try.
I made a quick implementation with that, I am sure it's not the prettiest way to do it but if you can be inspired by that maybe:
class ViewController : UITableViewController {
var orders = [1,2,3,4]
var ABCDArray = ["A","B","C","D"]
var currentCellNumber = 0
override func viewDidLoad() {
super.viewDidLoad()
currentCellNumber = orders.count
tableView?.delegate = self
tableView?.dataSource = self
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
if orders.isEmpty {
cell.textLabel?.text = ABCDArray[indexPath.row]
} else {
cell.textLabel?.text = String(orders[indexPath.row])
}
return cell
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return currentCellNumber
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if !orders.isEmpty {
orders.remove(at: indexPath.row)
currentCellNumber -= 1
tableView.deleteRows(at: [indexPath], with: .middle)
if orders.isEmpty {
changeDataSource(newDataSourceArray: ABCDArray)
}
}
}
func changeDataSource(newDataSourceArray array: Array<Any>) {
var newCellIndexPaths = [IndexPath]()
for row in 0...array.count-1 {
print(row)
currentCellNumber = row+1
print(currentCellNumber)
let insertionIndexPath = IndexPath(row: row, section: 0)
print(insertionIndexPath)
newCellIndexPaths.append(insertionIndexPath)
}
tableView.insertRows(at: newCellIndexPaths, with: .automatic)
}
}
If you have some question donut hesitate> Hope it helps

Deselect cell only if other cell in section is tapped

I have a table view with two sections. Each cell in section 1 (the second section) has a tap accessory and a deselect method for when another cell in section 1 is tapped. However, if any cell in section 0 is tapped this also deselects the current selected cell.
My code is:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if(indexPath.section == 1)
{
tableView.cellForRowAtIndexPath(indexPath)?.accessoryType = .Checkmark
}
}
override func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
if(indexPath.section == 1)
{
tableView.cellForRowAtIndexPath(indexPath)?.accessoryType = .None
}
}
I would only like the cells to be deselected if any other cell in section 1 is selected and ignore deselect for any other section.
Any thoughts?
For proper Reference, you need manage the reference in your globalArray itself, as shown below:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
.........
if ( globalArray[indexpath.row][SELECTED_STATE] == SELECTED )
{
tableView.cellForRowAtIndexPath(indexPath)?.accessoryType = .Checkmark
}
else
{
tableView.cellForRowAtIndexPath(indexPath)?.accessoryType = .None
}
.........
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//remove the pre selected state if needed by iterating the array
globalArray[indexpath.row][SELECTED_STATE] = SELECTED
tableView .reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None)
}

UITableView - Multiple selection AND single selection

I have 2 sections in my UITableView.
I want the first section to allow multiple cell selection and the second section to allow only single selection.
I tried some code but didn't work very well.
Code in swift if possible. Thank you.
You can simply try this. This solution works for me perfectly. Give it a try maybe worked for others...
Swift-4
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.section == 0 {
if let cell = tableView.cellForRow(at: indexPath) {
cell.accessoryType = .checkmark
}
}
else {
if let cell = tableView.cellForRow(at: indexPath) {
cell.accessoryType = .checkmark
}
}
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
if indexPath.section == 1 {
if let cell = tableView.cellForRow(at: indexPath as IndexPath) {
cell.accessoryType = .none
}
}
}
Perhaps you could implement the table view's delegate methods:
tableView(_:shouldHighlightRowAtIndexPath:)
and
tableView(_:didSelectRowAtIndexPath:)
...and determine (from indexPath.row and indexPath.section) if the relevant section supports single/multiple selection (this will depend on your data model's custom logic -e.g.: "Section 0 supports multiple selection but section 1 does not"), and if it only supports single selection, check whether there is already a row selected (by accessing tableView.indexPathsForSelectedRows).
If there is a selected row already, you can:
Return false from tableView(_:shouldHighlightRowAtIndexPath:), and
Do nothing (just return) from tableView(_:didSelectRowAtIndexPath:) (I'm not sure if this method is actually called when you return false from shouldHighlight..., so perhaps check it).
This is easily achievable in two lines as follows: (Swift 4)
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if sectionAllowsMultipleSelection {
if let indexPathsInSection = tableView.indexPathsForSelectedRows?.filter ({ $0.section == indexPath.section && $0.row != indexPath.row }) {
for selectedPath in indexPathsInSection {
tableView.deselectRow(at: selectedPath, animated: false)
}
}
}
}
If you want the selected row in section 2 to be the new selected row, this might work for you. Else, go with #NicolasMiari's answer.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.section == 1 {
for i in 0..tableView.numberOfRowsInSection(indexPath.section) - 1 {
let cell: UITableViewCell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: i, inSection: indexPath.section))!
if (i == indexPath.row) {
cell.accessoryType = .Checkmark
cell.selected = false
}
else {
cell.accessoryType = .None
}
}
}
else {
//Do whatever for the first section
}
}
Not very elegant, but hopefully it will give you an idea.

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