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

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

Related

Swipe and delete row with two sections in table view and add deleted name to second section

please help, I have an array with names and when I select a cell I add the selected name to the second section of a table view and delete the name from that first section (all fine here) but if I don't want the name in second section for some reason, I want to be able to swipe the cell, remove the name and add it again to the firs section of the table view. When I do that with my code the number of rows fails because I deleted a row. I can't figure it out.
here is my code.
import UIKit
class QuestionsVC: UIViewController {
#IBOutlet weak var namesTableView: UITableView!
var array1 = ["Jill","Clark","Rose","Peter","Louis"]
var array2 = [String]()
override func viewDidLoad() {
super.viewDidLoad()
namesTableView.dataSource = self
namesTableView.delegate = self
}
}
extension QuestionsVC: UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return array1.count
}
return array2.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
cell.textLabel?.text = array1[indexPath.row]
return cell
}
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
cell?.textLabel?.text = array2[indexPath.row]
return cell!
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
array2.append(array1[indexPath.row])
if let index = array1.firstIndex(of: array1[indexPath.row]) {
array1.remove(at: index)
}
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
if indexPath.section == 0 {
return false
}else{
return true
}
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let deleteAction = UITableViewRowAction(style: .destructive, title: "Delete") { (_, indexPath) in
self.array2.remove(at: indexPath.row)
self.array1 += [self.array2[indexPath.row]]
tableView.deleteRows(at: [indexPath], with: .fade)
}
return [deleteAction]
}
}
You have to delete the row in section 1 and insert the row in section 0 simultaneously
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let deleteAction = UITableViewRowAction(style: .destructive, title: "Delete") { (_, indexPath) in
let item = self.array2.remove(at: indexPath.row)
let insertionIndex = self.array1.count
self.array1.append(item)
tableView.beginUpdates()
tableView.deleteRows(at: [indexPath], with: .fade)
tableView.insertRows(at: [IndexPath(row: insertionIndex, section: 0)], with: .automatic)
tableView.endUpdates()
}
return [deleteAction]
}
}
replace the line
tableView.deleteRows(at: [indexPath], with: .fade)
with
tableView.reloadData()

Don't delete some rows from UITableView

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
}

Inserting cell in specific section

I am trying to find a way that allows me to insert a new cell under a specific section on my tableview. There is a button that is pressed called "add song" and once a user presses on that it should insert a new cell that is built by with a prototype cell. That prototype cell will allow a user to click on it and edit certain information on that cell. I have been trying to code a way to insert the cell below the cell that is currently in that section which is section "3". I'm sure it is something simple that I am messing up since I'm not very use to doing tableviews. Here is my code:
import UIKit
class MultipleSongsTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addSong(_ sender: Any) {
insertNewSongCell()
}
func insertNewSongCell() {
let indexPath = IndexPath(row: -1, section: 3)
tableView.beginUpdates()
tableView.insertRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
}
}
extension MultipleSongsTableViewController {
override func numberOfSections(in tableView: UITableView) -> Int {
return 5
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return 1
} else if section == 1 {
return 1
} else if section == 2 {
return 1
} else if section == 3 {
return 1
} else {
return 1
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "trackTitleCell", for: indexPath) as! ReleaseTitleTableViewCell
return cell
} else if indexPath.section == 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: "genreCell", for: indexPath) as! Genre2TableViewCell
return cell
} else if indexPath.section == 2 {
let cell = tableView.dequeueReusableCell(withIdentifier: "TrackListCell", for: indexPath) as! TrackListTableViewCell
return cell
} else if indexPath.section == 3 {
let cell = tableView.dequeueReusableCell(withIdentifier: "TrackListSongCells", for: indexPath) as! TrackListSongsTableViewCell
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "AddSongButtonCell", for: indexPath) as! AddSongTableViewCell
return cell
}
}
}
Also here is a screenshot of how my viewcontroller looks and where I expect the new cell to populate.
I would like the new cell to be inserted after the cell that says "Song Name", I would like the inserted cell to be the same prototype cell that is currently there because the user can click on that cell and fill out information and change the current "Song Name" label to what ever they want.
First of all you need a data source array for the section for example
var songs = [String]()
Then you have to modify numberOfRowsInSection to return the number of songs for section 3. This method can be simplified anyway
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 3: return songs.count
default: return 1
}
}
Now you can add a new song to the array and insert the row
func insertNewSongCell() {
let insertionIndex = songs.count
songs.append("New Song")
let indexPath = IndexPath(row: insertionIndex, section: 3)
tableView.insertRows(at: [indexPath], with: .automatic)
}
beginUpdates and endUpdates have no effect in this case, you can omit the lines.
Calling UITableView.insertRows(at:with:) will insert cells into your UITableView. You can insert exactly 1 cell by passing an indexPaths argument containing 1 IndexPath:
self.tableView.insertRows(at: [IndexPath(row: 1, section: 3)], with: .automatic)
You may need to also update your UITableViewDataSource to return expected values, e.g. from its tableView(_:numberOfRowsInSection:), to account for the additional row(s). Otherwise, your app will throw an unhandled exception and crash.

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)