Add and delete cell from table view in swift - ios

I have a table view in the cell i'm having two text fields in which user can enter the data. Initially i'm showing 5 cells. There is a button on which when user click it add one more cell in the table view. Now when i hit a button it add a cell when textfields are empty. But when i add data in all 5 cell textfields and than hit add button app crashes by showing this error, Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to insert row 10 into section 0, but there are only 6 rows in section 0 after the update'
The code is try for adding and deleting cell is this,
extension FlashCardViewController: UITableViewDelegate,UITableViewDataSource, UITextFieldDelegate{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numberOfCell
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = flashCardTableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! FlashCardTableViewCell
//cell.termTxt.delegate = self
//allCellsText[indexPath.row] = cell.termTxt.text!
// cell.definitionTxt.delegate = self
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 115
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete{
numberOfCell -= 1
allCellsText.remove(at: indexPath.row)
flashCardTableView.beginUpdates()
flashCardTableView.deleteRows(at: [indexPath], with: .automatic)
flashCardTableView.endUpdates()
}
}
func textFieldDidEndEditing(_ textField: UITextField) {
allCellsText.append(textField.text!)
print(allCellsText)
}
}
The code for add button is this,
#IBAction func addCardBtnTapped(_ sender: Any) {
numberOfCell += 1
let indexPath = IndexPath(row: allCellsText.count+1, section: 0)
flashCardTableView.beginUpdates()
flashCardTableView.insertRows(at: [indexPath], with: .automatic)
flashCardTableView.endUpdates()
view.endEditing(true)
}
When i delete any cell it gives me error of index out of range. How can i achieve this goal? The view controllers looks like this,

The problem is in the way you create an indexPath for inserting a new row, fix it according to this:
#IBAction func addCardBtnTapped(_ sender: Any) {
numberOfCell += 1
// create indexPath from numberOfCell, not from allCellsText.count
let indexPath = IndexPath(row: numberOfCell - 1, section: 0)
flashCardTableView.beginUpdates()
flashCardTableView.insertRows(at: [indexPath], with: .automatic)
flashCardTableView.endUpdates()
view.endEditing(true)
}
The problem is in creating the IndexPath using IndexPath(row: allCellsText.count+1, section: 0). The insertions and deletions on tableView HAVE to be consistent with the dataSource - if you add a new row, the numberOfRowsInSection HAVE to increase by one, too. Now in your case you increment numberOfCell by one, as you are supposed to do, but then you try to add the new row at an indexPath determined by allCellsText.count+1. The problem is that the allCellsText.count is not consistent with numberOfCell variable (notice that you append a new string everytime textFieldDidEndEditing gets called).
EDIT
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return allCellsTermText.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = flashCardTableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! FlashCardTableViewCell
// configure it with the backing data
cell.termTxt.text = allCellsTermText[indexPath.row]
cell.definitionTxt.text = allCellsDefinitionText[indexPath.row]
// now instead of this you will have to find a way how you will be
// able to determine the row which needs to be changed and change the model
// cell.termTxt.delegate = self
// cell.definitionTxt.delegate = self
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 115
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
allCellsTermText.remove(at: indexPath.row)
allCellsDefinitionText.remove(at: indexPath.row)
flashCardTableView.deleteRows(at: [indexPath], with: .automatic)
}
}
func textFieldDidEndEditing(_ textField: UITextField) {
// you should not append here, this will add a new row, you have to UPDATE the proper text
// allCellsText.append(textField.text!)
}
#IBAction func addCardBtnTapped(_ sender: Any) {
// create a new row by appending new empty strings
allCellsTermText.append("")
allCellsDefinitionText.append("")
let indexPath = IndexPath(row: allCellsTermText.count - 1, section: 0)
flashCardTableView.insertRows(at: [indexPath], with: .automatic)
view.endEditing(true)
}

If you want to use button to delete tableviewCell, any table view that allows rows to be deleted
Image TableViewCell delete with button
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ATableViewCell
cell.deleteButton.addTarget(self, action: #selector(nHapusTap(_:)), for: .touchUpInside)
return cell
}
#objc func nHapusTap(_ sender: UIButton) {
let hitPoint = sender.convert(CGPoint.zero, to: tableView)
if let indexPath = tableView.indexPathForRow(at: hitPoint) {
self.dataArray.remove(at: indexPath.row)
tableView.beginUpdates()
tableView.deleteRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
}
}

You're doing it incorrect. You've added self as delegate to the termtxt and definitiontxt here.
cell.termTxt.delegate = self
cell.definitionTxt.delegate = self
As many times as you end editing in these inputboxes, your delegate method gonna get hit where you're just appending the text in a array.
func textFieldDidEndEditing(_ textField: UITextField) {
allCellsText.append(textField.text!)
print(allCellsText)
}
When you call the add row button, the allCellsText size is 10 because endEditing has been called from two types of inputboxes. (10 is incorrect, you can't add 10th row after 5th row).
let indexPath = IndexPath(row: allCellsText.count+1, section: 0)
Solution:
Either add some checks in your delegate method before appending anything in allCellsText array OR update your logic in addCardBtnTapped function, remove the dependency from allCellsText arrow.
use something like:
let indexPath = IndexPath(row: numberOfCell+1, section: 0)

Related

Expand the only tapped cell and collapse other cells

I am doing expand/collapse when tapped on tableview cell, but I have to close all other cells except the tapped one. Tried this solution Expand only the cell that has been tapped this solution is not working for me.
below code which I have written for expand/collapse
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return datasource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView .dequeueReusableCell(withIdentifier: String(describing: ExpandingTableViewCell.self), for: indexPath) as! ExpandingTableViewCell
cell.set(content: datasource[indexPath.row])
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let content = datasource[indexPath.row]
content.expanded = !content.expanded
tableView.reloadRows(at: [indexPath], with: .automatic)
}
You need to collapse all cells and change the current clicked one state , then reload all the table
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let current = datasource[indexPath.row].expanded
datasource.forEach { $0.expanded = false }
let content = datasource[indexPath.row]
content.expanded = !current
tableView.reloadData()
}

Reordering cells in UITableView takes out functionality of cells in Swift?

I am using JSON to parse data from Spotify and add songs into a UITableView. The songs play fine, and I added functionality for deleting cells, but when adding functionality for reording cells, I can''t play songs and I can't swipe to delete them either. Any ideas would be appreciated.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.tableView.isEditing = true
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
This adds the album image and song name to the TableView.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
let mainImageView = cell?.viewWithTag(2) as! UIImageView
mainImageView.image = posts[indexPath.row].mainImage
let mainLabel = cell?.viewWithTag(1) as! UILabel
mainLabel.text = posts[indexPath.row].name
return cell!
}
This adds the swipe to delete functionality.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
posts.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
} else if editingStyle == .insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
return .none
}
override func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
return false
}
This adds the reordering functionality.
override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let movedObject = self.posts[sourceIndexPath.row]
posts.remove(at: sourceIndexPath.row)
posts.insert(movedObject, at: destinationIndexPath.row)
debugPrint("\(sourceIndexPath.row) => \(destinationIndexPath.row)")
self.tableView.reloadData()
}
You don't want to set
self.tableView.isEditing = true
in viewDidLoad. This takes you from the "normal" mode where you can select a cell, or other elements in a cell. Setting "self.tableview.isEditing" is the equivalent of hitting an edit button on the top right-hand corner of many tableViews.

UITableView leaves pattern of blank cells after deleting one or more rows

TableView leaving blank cells
Repeating blank cells
When deleting rows from the bottom
After deleting a row or multiple rows in my TableView, the TableView Cells seems to shift or refresh in an odd way that creates multiple blank rows. Seems to start with rows that are off-screen.
I have tried using beginUpdates, endUpdates, and performBatchUpdates with no change in behavior. I have also confirmed that the data source array is being updated properly and so is the number of rows in the tableview.
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return paymentsArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! UserPaymentCell
let payment = paymentsArray[indexPath.row]
cell.payment = payment
cell.selectionStyle = .none
cell.preservesSuperviewLayoutMargins = false
cell.separatorInset = UIEdgeInsets(top: 0, left: 75, bottom: 0, right: 0)
cell.layoutMargins = UIEdgeInsets.zero
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
let payment = paymentsArray[indexPath.row]
if payment.payerUID == Auth.auth().currentUser?.uid {
return true
} else {
return false
}
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
let payment = paymentsArray[indexPath.row]
switch editingStyle {
case .delete:
deleteBillAndRefreshTotals(bill: payment, indexPath: indexPath)
default:
return
}
}
func deleteBillAndRefreshTotals(bill: Bill, indexPath: IndexPath) {
print("DELETING CELL")
paymentsArray.remove(at: indexPath.row)
paymentsTableView.deleteRows(at: [indexPath], with: .automatic)
print(paymentsTableView.numberOfRows(inSection: 0))
}
Expected results - for row to be deleted and all cells above or below the deleted cell to shift together.
override func prepareForReuse() {
super.prepareForReuse() // <--
self.nameLabel.text = nil
self.backgroundColor = .white
}
Within my custom cell implementation, the above function was being called without calling super.prepareForReuse first. Therefore causing the issues above.
after perform delete operations call reloaddata method so after that tableview will refresh.
func deleteBillAndRefreshTotals(bill: Bill, indexPath: IndexPath) {
print("DELETING CELL")
paymentsArray.remove(at: indexPath.row)
paymentsTableView.reloaddata()
print(paymentsTableView.numberOfRows(inSection: 0))
}
You can try this code :
paymentsTableView.beginUpdates()
paymentsTableView.deleteRows(at: [indexPath], with: .automatic)
paymentsTableView.endUpdates()

Insert row tableview with a button at last section

I want to make a tableview with a button in the section. I want the button to add one more row to a tableview like this
Here is the source code:
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection sectionInd: Int) -> Int {
if sectionInd == 0 {
return others.count
} else {
return 1
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "ShareCell", for: indexPath as IndexPath) as! SelectOthersTableViewCell
cell.firstName.text = others[indexPath.row].firstname
cell.lastName.text = others[indexPath.row].lastname
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "addCell", for: indexPath as IndexPath) as! addTableCell
cell.addCells.tag = indexPath.row
cell.addCells.addTarget(self, action: #selector(OthersViewController.addButtonClicked(sender:)), for: UIControlEvents.touchUpInside)
return cell
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
var height:CGFloat = CGFloat()
if indexPath.section == 0 {
height = 145
} else {
height = 50
}
return height
}
#objc func addButtonClicked(sender:UIButton) {
data.append("Guest 1")
let buttonPosition = sender.convert(CGPoint.zero, to: self.tableView)
let indexPath = self.tableView.indexPathForRow(at: buttonPosition)
print("indexPath \(indexPath!)")
selectedIndexes[indexPath!] = !(selectedIndexes[indexPath!] ?? false)
tableView.reloadRows(at: [indexPath!], with: .automatic)
tableView.beginUpdates()
tableView.insertRows(at: [IndexPath(row: data.count-1, section: 0)], with: .automatic)
tableView.endUpdates()
}
i need help please. How to add new row by tap button on icon (+)?
On click of "Add" button, You should not reload the the entire table view because it increases the processing time. Instead of that you can use of
beginUpdates and endUpdates for inserting new cell when button clicked.
Basic Approaches:
(1). On click of "Add", update your data-source for table-view.
dataSource.append(NewRecord)
(2). Insert the new cell:
tableView.beginUpdates()
tableView.insertRows(at: [IndexPath(row: dataSource.count-1, section: 0)], with: .automatic)
tableView.endUpdates()
Reviewing your Code:
func addButtonClicked(sender:UIButton) {
data.append("Guest 1")
.....
}
Your datasource is others on which the tableview is created and configured.
But on click of add button (addButtonClicked function), you are not updating the others data-source. Please verify it, except that your code seems good.
fun onPlusButtonClicked(){
sections.append(whatever you want)
items[2].append(["1", "2", "3", "4"]) // whatever you want to add here
tableview.reloadData() // you can call this on a background thread as well, if its not working
}
// Ex of how to use with tableview
var sections = Your array
var items = your array
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section]
}
func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items[section].count
}

Can't edit/delete top row in uitableview - Swift

Not sure why but the top row of my uitableview is not editable, all other rows function as normal and delete as expected. It's like caneditrowat indexPath: Indexpath isn't working for that one row. See images attached.
My code in tableView(_:commit:forRowAt:) looks like all the tutorials I can find, can't seem to find any other examples with this problem.
//MARK: Properties
var favouriteExercises = [FavouriteExercise]()
override func viewDidLoad() {
super.viewDidLoad()
//Load exercises from local DB
if let savedFavouriteExercises = loadFavouriteExercises()
{
//loading exercises in from the favourites
favouriteExercises += savedFavouriteExercises
}
// Use the edit button item provided by the table view controller.
navigationItem.rightBarButtonItem = editButtonItem
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//count number of rows in table
return favouriteExercises.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
let cellIdentifier = "FavouriteTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? FavouriteTableViewCell else {
fatalError("The dequeued cell is not an instance of FavouriteTableViewCell.")
}
// Fetches the appropriate exercise for the data source layout.
let exercise = favouriteExercises[indexPath.row]
//setup the layout for the cell in the table view
cell.nameLabel.text = exercise.name
let url = URL(string: (exercise.iconUrl))!
cell.photoImageView.sd_setImage(with: url)
//cell.photoImageView.image = #imageLiteral(resourceName: "defaultPhoto")
cell.backgroundColor = UIColor.darkGray
return cell
}
// Override to support conditional editing of the table view.
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
// Override to support editing the table view.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// Delete the row from the data source
favouriteExercises.remove(at: indexPath.row)
saveFavouriteExercisess()
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
Thanks for the help
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle
{
return UITableViewCellEditingStyle.delete
}
Answer adapted from #KumarReddy's solution

Resources