UITableView cell insertion failure - ios

I am trying to make a UITableView that can have expandable header views. When you press a button inside of the header view, the following function gets executed:
func expandTheCell(_ sender: UIButton) {
self.tableView.beginUpdates()
if sender.isExpanded == false {
postsArray.append("Hello")
tableView.reloadData()
print("Array Count: \(postsArray.count)")
self.tableView.insertRows(at: [IndexPath.init(row: 0, section: sender.tag)], with: .fade)
} else {
print("test")
}
self.tableView.endUpdates()
}
This are some table view functions:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return postsArray.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return 3
}
When I try to insert the rows, I get the following error:
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'Invalid update: invalid
number of rows in section 1
How come I can't insert the cells? What am I doing wrong?

I think problem is due to having same dataSource array for the sections, in your case postsArray , when you append the item to postsArray on clicking the button , same postsArray is used for other sections, so after you insert the row in section 0 , section 1 complains that number of rows for me before and after insert operation is not same, but section 0 doesnt complain because it has same number of rows and number of items in postsArray
Now this problem can be solved in two ways:
First way is that you can insert the row for other sections as well, then all the sections have equal number of rows as the number of elements in postsArray
Second way is that you have different dataSource arrays for all the sections , like postsArray1 for section 1, postsArray2 for section 2 and same for other sections. Now in this case you dont need to insert rows for other sections , since each section has different dataSource array, changing one wont affect others.
I have made a simple project to demonstrate the above theory:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(buttonTapped(_:)))
self.navigationItem.rightBarButtonItem = addButton
}
var valuesFirstSection = ["value1", "value2", "value3"]
var valuesSecondSection = ["value1Second", "value2Second", "value3Second"]
//if you want to have the same dataSource array then use this
//var sharedValues = ["value1Shared", "value2Shared", "value3Shared"] // shared dataSource array
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return valuesFirstSection.count
}else {
return valuesSecondSection.count
}
// //if you want to have the same dataSource array then
//use this
//return sharedValues.count;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
if indexPath.section == 0 {
cell.textLabel?.text = valuesFirstSection[indexPath.row]
}else {
cell.textLabel?.text = valuesSecondSection[indexPath.row]
}
return cell
//if you want to have the same dataSource array then
//use this
//cell.textLabel?.text = sharedValues[indexPath.row]
//return cell
}
func buttonTapped(_ sender: UIBarButtonItem) {
//if you want to have the same dataSource array then
//you need to insert the rows for other sections as well
// sharedValues.insert("NewValue0", at: 0)
// self.tableView.insertRows(
// at: [IndexPath(row: 0, section: 0),
// IndexPath(row: 0, section: 1)
// ],
// with: .automatic)
valuesFirstSection.insert("NewValue0", at: 0)
self.tableView.insertRows(
at: [IndexPath(row: 0, section: 0)
],
with: .automatic)
}
}
Hope this helps.

Related

dequeueReusableCell on tableView get wrong pointers on Xcode 13

I'm having al lot of troubles when I have updated Xcode from 12 to 13.
**All the table view controllers are not yet working as before.**
Doing dome debug I have noticed that when the command:
tableView.dequeueReusableCell
is used to read the already instantiated cells, it get the wrong pointers.
Example, I have 3 cells, and the first time the command dequeueReusableCell create one pointer for each cell (self)
row: 0, cell: <andrea.TestCell: 0x7fc387f19320
row: 1, cell: <andrea.TestCell: 0x7fc388a07730
row: 2, cell: <andrea.TestCell: 0x7fc388a1c230
when I reload the table (to refresh the data), the pointers are swapped
row: 0, cell: <andrea.TestCell: 0x7fc388a1c230
row: 1, cell: <andrea.TestCell: 0x7fc387f19320
row: 2, cell: <andrea.TestCell: 0x7fc388a07730
So, since that I'm using internal class variables (see below cell_store_var), when the table is refresh all the stored variables inside the class are not yet aligned with their right position.
I did a lot of trials but without success, and I don't know how to update by App on Apple Store,
here the xcode project: table view
(this is only an example to reproduce the issue)
thanks,
Andrea
import UIKit
class ListController_test: UIViewController, UITableViewDataSource, UITableViewDelegate {
var timer_3sec: Timer!
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
timer_3sec = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(self.read_sensors), userInfo: nil, repeats: true)
}
func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TestCell
cell.refresh(index: indexPath.row)
print("Loaded - IndexPathItem: \(indexPath.item), cell: \(cell.self)")
return cell
}
//imposta l'altezza delle celle in modo dinamico in base al tipo di sensore
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
#objc func read_sensors () {
self.tableView.reloadData()
}
}
import UIKit
class TestCell: UITableViewCell {
var cell_store_var : Int = 0
#IBOutlet weak var label: UILabel!
#IBOutlet weak var label2: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func refresh(index:Int) {
cell_store_var = index + cell_store_var + 1
print("index: \(index), cell_store_var: \(cell_store_var)")
let self_address = String(format:"%02X", self.hashValue)
label.text = "index: \(index), cell_store_var: \(cell_store_var)"
label2.text = "pointer: 0x\(self_address)"
}
}
I have solved my issue.
I used tableview.visibleCells to refresh the already allocated cells.
I have not yet used tableview.reloadData() that allocates again all the rows updating the pointers and so removing/resetting all the internal local variables used to store the cell operations. (for example to store the previous sensor value)
With this approach, to refresh the Cells, I use only the method update_cella() used to refresh the data.
func refresh_cells(){
let cells = tableView.visibleCells
for cell in cells {
switch cell
{
case is CellaSwitchXIB:
let cell1 = cell as! CellaSwitchXIB
cell1.update_cella()
break
case is CellaSensoreXIB:
let cell1 = cell as! CellaSensoreXIB
cell1.update_cella()
break
case is CellaButtonXIB:
let cell1 = cell as! CellaButtonXIB
cell1.update_cella()
break
case is CellaBlindXIB:
let cell1 = cell as! CellaBlindXIB
cell1.update_cella()
break
case is CellaPwmXIB:
let cell1 = cell as! CellaPwmXIB
cell1.update_cella()
break
default:
break
}
}
}
Image of tableview

How to create dynamic section in table view swift based on the user input?

I'm trying to create a number of sections based on the input from the user in the text field.
take a look at my snapshot to make it easier.
as default, I created 1 section of label 1, label 2, label 3, label 4.
but if the number of section text field is inputed 3, I want the section that fills with labels to increase into 3.
How do I do that?
My problem is that it does not add up another section when I type it.
I had also put tableView.reloadData() in some possible places but it didn't work.
Here's my code, any suggestion will be appreciated.
Thankyou in advance.
var numberOfSection: String = "0"
override func viewDidLoad() {
super.viewDidLoad()
}
override func numberOfSections(in tableView: UITableView) -> Int {
if numberOfSection == "0" {
return 2
}
else if numberOfSection == "1" {
return 2
}
else {
print(numberOfSection)
return Int(numberOfSection) ?? 2
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
return 1
case 1:
return WSSpec.count
default:
return 0
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.section {
case 0:
let cells = tableView.dequeueReusableCell(withIdentifier: "serverNumbCell", for: indexPath) as! CellOutlet
let txtFieldServerNumber = cells.txtFieldServerNumb
cells.lblSectionNumb.text = "Number of section:"
txtFieldServerNumber?.addTarget(self, action: #selector(sectionTxtFieldDidChange), for: .editingChanged)
return cells
case 1:
let cells = tableView.dequeueReusableCell(withIdentifier: "serverSpecCell", for: indexPath) as! CellOutlet
let specWS = testSpec[indexPath.row]
cells.labels.text = specWS.spec
return cells
default:
return CellOutlet()
}
}
#objc func sectionTxtFieldDidChange(_ sender: UITextField) {
numberOfSection = sender.text ?? "2"
}
Try adding a didSet to the variable then reloading data from there. Like this:
var numberOfSection: String = "0" {
didSet {
tableView.reloadData()
}
}

Swift TableView insert row below button clicked

I am new to Swift and I am using Swift 4.2 . I have a TableView with a label and button . When I press a button I would like to add a new row directly below the row in which the button was clicked . Right now when I click a button the new row gets added to the bottom of the TableView every time. I have been looking at posts on here but haven't been able to get it working this is my code base . I have a method called RowClick I get the indexpath of the row that was clicked but do not know how to use that to get the new row to appear directly below the clicked row .
class ExpandController: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var TableSource: UITableView!
var videos: [String] = ["FaceBook","Twitter","Instagram"]
override func viewDidLoad() {
super.viewDidLoad()
TableSource.delegate = self
TableSource.dataSource = self
TableSource.tableFooterView = UIView(frame: CGRect.zero)
// Do any additional setup after loading the view.
}
#IBAction func RowClick(_ sender: UIButton) {
guard let cell = sender.superview?.superview as? ExpandTVC else {
return
}
let indexPath = TableSource.indexPath(for: cell)
InsertVideoTitle(indexPath: indexPath)
}
func InsertVideoTitle(indexPath: IndexPath?)
{
videos.append("Snapchat")
let indexPath = IndexPath(row: videos.count - 1, section: 0)
TableSource.beginUpdates()
TableSource.insertRows(at: [indexPath], with: .automatic)
TableSource.endUpdates()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return videos.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let videoTitle = videos[indexPath.row]
let cell = TableSource.dequeueReusableCell(withIdentifier: "ExpandTVC") as! ExpandTVC
cell.Title.text = videoTitle
cell.ButtonRow.tag = indexPath.row
cell.ButtonRow.setTitle("Rows",for: .normal)
return cell
}
}
This is how my table looks I clicked the Facebook Rows button and it appended the string SnapChat . The Snapchat label should appear in a row below Facebook instead . Any suggestions would be great !
I think the easiest solution without re-writing this whole thing would be adding 1 to the current row of the IndexPath you captured from the action.
let indexPath = TableSource.indexPath(for: cell)
var newIndexPath = indexPath;
newIndexPath.row += 1;
InsertVideoTitle(indexPath: newIndexPath);
I did this from memory because I am not near an IDE, so take a look at the change and apply that change if needed in any other location.
class ExpandController: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var TableSource: UITableView!
var videos: [String] = ["FaceBook","Twitter","Instagram"]
override func viewDidLoad() {
super.viewDidLoad()
TableSource.delegate = self
TableSource.dataSource = self
TableSource.tableFooterView = UIView(frame: CGRect.zero)
// Do any additional setup after loading the view.
}
#IBAction func RowClick(_ sender: UIButton) {
guard let cell = sender.superview?.superview as? ExpandTVC else {
return
}
let indexPath = TableSource.indexPath(for: cell)
var newIndexPath = indexPath;
newIndexPath.row += 1;
InsertVideoTitle(indexPath: newIndexPath);
}
func InsertVideoTitle(indexPath: IndexPath?)
{
videos.append("Snapchat")
let indexPath = IndexPath(row: videos.count - 1, section: 0)
TableSource.beginUpdates()
TableSource.insertRows(at: [indexPath], with: .automatic)
TableSource.endUpdates()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return videos.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let videoTitle = videos[indexPath.row]
let cell = TableSource.dequeueReusableCell(withIdentifier: "ExpandTVC") as! ExpandTVC
cell.Title.text = videoTitle
cell.ButtonRow.tag = indexPath.row
cell.ButtonRow.setTitle("Rows",for: .normal)
return cell
}
}
Your current code calls append to add the new item at the end of the array. What you want to do is insert a new row at indexPath.row+1. Array has an insert(element,at:) function.
You have to handle the case where the user has tapped the last row and not add 1 to avoid an array bounds error:
func InsertVideoTitle(indexPath: IndexPath)
{
let targetRow = indexPath.row < videos.endIndex ? indexPath.row+1 : indexPath.row
videos.insert("Snapchat" at:targetRow)
let newIndexPath = IndexPath(row: targetRow, section: 0)
TableSource.beginUpdates()
TableSource.insertRows(at: [newIndexPath], with: .automatic)
TableSource.endUpdates()
}

Expand and collapse multilevel sections in uitableview swift4

I want to expand and collpase the multilevel array in uitableview like the following
Cat1
SubCat1
Info 1
Info 2
SubCat2
Info 1
Info 2
SubCat3
Info 1
Info 2
Cat2
SubCat1
Info 1
Info 2
For that purpose I have done the following code.
struct CellData {
var opened = Bool()
var subCatTitle = String()
var subCatList = [String]()
}
struct MainModel {
var opened = Bool()
var categoryTitle = String()
var categoryList = [CellData]()
}
I have made the list
#IBOutlet var expandableThreeStageTableView: UITableView!
var arrayList = [CellData]()
var expandableList = [MainModel]()
func loadData(){
arrayList.append(CellData(opened: false, subCatTitle: "SubCat1", subCatList: ["Info1","Info2","Info3"]))
arrayList.append(CellData(opened: false, subCatTitle: "SubCat2", subCatList: ["Info1","Info2","Info3"]))
arrayList.append(CellData(opened: false, subCatTitle: "SubCat3", subCatList: ["Info1","Info2"]))
arrayList.append(CellData(opened: false, subCatTitle: "SubCat4", subCatList: ["Info1"]))
expandableList.append(MainModel(opened: true, categoryTitle: "Cat1", categoryList: arrayList))
expandableList.append(MainModel(opened: false, categoryTitle: "Cat2", categoryList: arrayList))
expandableList.append(MainModel(opened: false, categoryTitle: "Cat3", categoryList: arrayList))
}
And delegate, datasource methods are given below
extension TextFieldAsSearchVC : UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return expandableList.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section:
Int) -> Int {
if expandableList[section].opened{
if expandableList[section].categoryList[section].opened{
return
expandableList[section].categoryList[section].subCatList.count////which extra count should return here
}else{
print("COUNT ",expandableList[section].categoryList.count)
return expandableList[section].categoryList.count +
1///here +1 is for catname + subcatname
}
}else{
return 1
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath:
IndexPath) -> UITableViewCell {
if indexPath.row == 0{
let cell =
expandableThreeStageTableView.dequeueReusableCell(withIdentifier:
"TextFieldAsSearchVCCell", for: indexPath) as! TextFieldAsSearchVCCell
cell.lblValue.text =
expandableList[indexPath.section].categoryTitle
return cell
}else if indexPath.row <=
expandableList[indexPath.section].categoryList.count{
let cell =
expandableThreeStageTableView.dequeueReusableCell(withIdentifier:
"SectionDataCell", for: indexPath) as! SectionDataCell
cell.rowLabel.text =
expandableList[indexPath.section].categoryList[indexPath.row -
1].subCatTitle
return cell
}
else{
let cell =
expandableThreeStageTableView.dequeueReusableCell(withIdentifier:
"SectionDataCell", for: indexPath) as! SectionDataCell
cell.rowLabel.text =
expandableList[indexPath.section].categoryList[indexPath.row].
subCatList[indexPath.row]//how to access rows in subcategories
return cell
}
}
}
extension TextFieldAsSearchVC : UITableViewDelegate{
func tableView(_ tableView: UITableView, didSelectRowAt indexPath:
IndexPath) {
if indexPath.row == 0{
if expandableList[indexPath.section].opened{
expandableList[indexPath.section].opened = false
//now reload the section
let sections = IndexSet(integer: indexPath.section)
expandableThreeStageTableView.reloadSections(sections,
with: .automatic)
}else{
expandableList[indexPath.section].opened = true
//now reload sections
let sections = IndexSet(integer: indexPath.section)
expandableThreeStageTableView.reloadSections(sections,
with: .automatic)
}
}else {
if
expandableList[indexPath.section].categoryList[indexPath.row].opened{
expandableList[indexPath.section].categoryList[indexPath.row].opened =
false
expandableThreeStageTableView.reloadRows(at:
[IndexPath(index: indexPath.row)], with: .automatic)
}else{
expandableList[indexPath.section].categoryList[indexPath.row].opened =
true
expandableThreeStageTableView.reloadRows(at:
[IndexPath(index: indexPath.row)], with: .automatic)
}
}
}
}
From above code I can expand and collapse the Categories but not Subcategories.. When I tried to click on Subcategories it gives me an error
*** Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'Invalid index path for use
with UITableView. Index paths passed to table view must contain exactly
two indices specifying the section and row. Please use the category on
NSIndexPath in NSIndexPath+UIKitAdditions.h if possible.'
How to deal with such type of logic?
The specific error you are getting occurs in this line:
expandableThreeStageTableView.reloadRows(at:
[IndexPath(index: indexPath.row)], with: .automatic)
An IndexPath needs both, a row and a section; you're only providing a row. So it should be something like this:
expandableThreeStageTableView.reloadRows(at:
[IndexPath(row: indexPath.row, section: indexPath.section)], with: .automatic)
If you really only need to reload the current indexPath, simply call it like this:
expandableThreeStageTableView.reloadRows(at:
[indexPath], with: .automatic)
This would fix the error you are getting, but I don't know if that solves your problem or not.

Insert Row At End Of Section Error

I'm trying to allow the user to insert rows at the end of sections but I'm getting an error. However everything seems correct so there must be something that I'm not seeing. Can someone point me in the right direction. Attached is my code, the error, and a screenshot of the table.
//
// Test.swift
// Table Views
//
// Created by Deion Long on 7/18/15.
// Copyright (c) 2015 Deion Long. All rights reserved.
//
import UIKit
var array = ["Section 1", "Section 2"]
extension UITableView {
func indexPathForView (view : UIView) -> NSIndexPath? {
let location = view.convertPoint(CGPointZero, toView:self)
return indexPathForRowAtPoint(location)
}
}
class Test: UIViewController, UITableViewDelegate, UITableViewDataSource {
var things:NSMutableArray = ["hi", "bye", "kie"]
#IBAction func editBtnClicked(sender: AnyObject) {
//When not in editing mode already set editing to true
if(table.editing == false){
self.editing = true
println("hi")
table.setEditing(true, animated: true)
}else{
self.editing = false
table.setEditing(false, animated: true)
}
}
#IBOutlet weak var table: UITableView!
#IBAction func doneButton(sender: AnyObject) {
table.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
table.allowsSelectionDuringEditing = true
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return array.count // This was put in mainly for my own unit testing
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return array[section]
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var addRow = self.editing ? 1 : 0
println(addRow)
return addRow + things.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("textInputCell3") as! DetailCell
if (indexPath.row >= things.count && self.editing) {
cell.configure(text: "Add Row", placeholder: "Enter")
} else{
cell.configure(text: things.objectAtIndex(indexPath.row) as! String, placeholder: "Enter")
}
return cell
}
override func setEditing(editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
self.table.setEditing(editing, animated: animated)
if(editing){
table.beginUpdates()
for var i = 0; i < self.table.numberOfSections(); i++
{
var lastRow = table.numberOfRowsInSection(i)
var lastIndex = NSIndexPath(forRow: lastRow, inSection: i)
table.insertRowsAtIndexPaths([lastIndex], withRowAnimation: UITableViewRowAnimation.Automatic)
}
table.endUpdates()
}
}
func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle {
if(indexPath.row >= things.count){
return UITableViewCellEditingStyle.Insert
}else{
return UITableViewCellEditingStyle.Delete
}
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if (editingStyle == UITableViewCellEditingStyle.Delete) {
things.removeObject(indexPath.row)
table.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
else if (editingStyle == UITableViewCellEditingStyle.Insert) {
things.insertObject("123", atIndex: self.things.count)
table.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
}
}
ERROR: Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 1. The number of rows contained in an existing section after the update (5) must be equal to the number of rows contained in that section before the update (4), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
The error message says it all. You mustn't insert the row until you've updated the model data to have the extra row. You may be confusing yourself with your sections here; practice with just one section first. Your model does not seem very well designed to distinguish the two sections.
In the setEditing: you are inserting rows inside the beginUpdates and endUpdates function of UITableView
table.beginUpdates()
for var i = 0; i < self.table.numberOfSections(); i++ {
var lastRow = table.numberOfRowsInSection(i)
var lastIndex = NSIndexPath(forRow: lastRow, inSection: i)
table.insertRowsAtIndexPaths([lastIndex], withRowAnimation: UITableViewRowAnimation.Automatic)
}
table.endUpdates()
When you are inserting rows make sure that you are updating your model object things. If you just want to reload the data of the cell use
table.reloadRowsAtIndexPaths([lastIndex], withRowAnimation: UITableViewRowAnimation.Automatic)

Resources