App crashing when scrolling UITableView - ios

I am creating table cells in a table view they load correctly and you can scroll down them fine but if you scroll back up the app crashes with the error fatal error: Index out of range
This is the code producing the table cells
Im quite new to coding in swift so please be clear on your answers
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return(tableRows.count)
}
var howmanyindex = 0
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ReviewControllerTableViewCell
cell.label1.text = profilerComments[tableRows[howmanyindex]]
cell.label2.text = String(profilerRatings[tableRows[howmanyindex]])
howmanyindex += 1
return(cell)
}

The problem seems to be with howmanyindex. Since you are using reusable cells, cellForRowAt is going to get called several times for a cell at a certain position if a cell goes out of the visible part of the screen.
Just use indexPath.row to index your data source array.
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ReviewControllerTableViewCell
cell.label1.text = profilerComments[tableRows[indexPath.row]]
cell.label2.text = String(profilerRatings[tableRows[indexPath.row]])
return cell
}

When tableView scrolls your howmanyindex += 1is increasing for every visible cells hence index going beyond your tableRows array, its better you use indexpath.row of tableRows.

No need to keep tracking the index of the table manually howmanyindex because it may be different from your data(or returning count) so when you fetch the data from your array it may come nil from the array and you got crashed.
So you can get the current cell index from indexPath.row
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ReviewControllerTableViewCell
cell.label1.text = profilerComments[tableRows[indexPath.row]]
cell.label2.text = String(profilerRatings[tableRows[indexPath.row]])
return(cell)
}

Use
cell.label1.text = profilerComments[tableRows[indexpath.row]]
in place of
cell.label1.text = profilerComments[tableRows[howmanyindex]]

Related

How reutilize the cells

My table have the functionality to delete a row, but when add a new row is writting two labels in the same place, a label above of the other label
a label belong to the deleted row and the other label belong to the new label when add a new row
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = UITableViewCell()
tableView.register(newCell.self, forCellReuseIdentifier:"newCell");
cell = tableView.dequeueReusableCell(withIdentifier: "newCell", for: indexPath) as! newCell
cell.textLabel.text= value
return cell
}
}
First of all, register your cell outside the cellForRowAt indexPath function, preferably where you are adding your tableView.
For this instance, register it in viewDidLoad
So
tableView.register(newCell.self, forCellReuseIdentifier:"newCell")
goes into viewDidLoad
for the function, try this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "newCell", for: indexPath) as! newCell
cell.textLabel.text= value
return cell
}
Hope this helps. Also, provide more context to what is going wrong and what is the intended behaviour.

Table view did select method to handle check box image

I have an table view with three cell which contains the label and one image (check box).Now when ever i select any cell.That particular cell image (check box) alone needs to get tick.png. And remaining two cell image should be untick.png.
But now if i select first cell then the first cel image get as tick.png.Then if i select second and third cell.That cell image also getting tick.png
But i need only one image alone needs to tick.png.Which ever table view cell i am selecting that particular cell image alone needs to be tick.png.And remaining two cell image should be untick.png.
My code :
var Data: [String] = ["First","Second","Third"]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.Data.count > 0{
return self.Data.count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ViewCell
cell.Lbl.text = self.aData[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! suggestionCell
cell.suggestionImg.image = UIImage(named: "tick")
}
If I understand you correctly you only want a single check mark at any given time. If this is true then you would simply setup a property in your view controller like this:
var checkedRow: Int
and set the row index in tableView(_:didSelectRowAt:). By setting it to -1 you would disable all check marks. Then in tableView(_:, cellForRowAt:) you would conditionally enable the check mark for the cell if indexPath.row is equal to checkedRow:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
checkedRow = indexPath.row
tableView.reloadData()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ViewCell
if indexPath.row == checkedRow {
cell.suggestionImg.image = UIImage(named: "tick.png")
cell. suggestionLbl.text = "<ticked text>"
} else {
cell.suggestionImg.image = UIImage(named "untick.png")
cell. suggestionLbl.text = "<unticked text>"
}
return cell
}
As an add-on to Tom's answer, I suggest storing IndexPath instead of Int adding also a
var lastCheckedRow:IndexPath = IndexPath(row: 0, section: 0)
This allows you to only reload the newly checked row and the previously checked row instead of the whole table view plus it will support multiple sections too. It does not matter much at your current stage where there is only 3 rows but for larger table views this will be more efficient. Also it removes the blinking effect of UITableView.reloadData().
The code is something like:
//0 based on assumption that first cell is checked by default
var checkedRow:IndexPath = IndexPath(row: 0, section: 0)
var lastCheckedRow:IndexPath = IndexPath(row: 0, section: 0)
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//Update checkedRow for reload but keep track of current tick
lastCheckedRow = checkedRow
checkedRow = indexPath
//Remove previous tick
tableView.reloadRows(at: [lastCheckedRow], with: .automatic)
//Update new tick
tableView.reloadRows(at: [checkedRow], with: .automatic)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ViewCell
if indexPath.row == checkedRow {
cell.suggestionImg.image = UIImage(named: "tick.png")
} else {
cell.suggestionImg.image = UIImage(named "untick.png")
}
return cell
}
You can also play around to create an ideal visual effect when ticking different cell by changing the with:UITableViewRowAnimation parameter which I use .automatic for the example.
allowsMultipleSelection: Is only easiest thing that will help you.
Add following line in your viewDidLoad after setting up the tableView
override func viewDidLoad() {
// ... setup you need
tableView.allowsMultipleSelection = false
}
Hope this helps!

Table view - Index out of range in cellForRowAt

I call tableView.reloadData() inside of didSet{} of the items variable.
I got a crash in the cellForRowAt function only once, never before, never after with no code changes.
override func tableView(_ tableView: UITableView, number ofRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return items.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.tag = indexPath.row
let item = items[indexPath.row] //Thread 1: Fatal error: Index out of range
cell.textLabel?.text = item.title
The possible solution will be you need to handle to solve the crash.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if(indexPath.row > items.count-1){
return UITableViewCell()
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.tag = indexPath.row
let item = items[indexPath.row] //Thread 1: Fatal error: Index out of range
cell.textLabel?.text = item.title
return cell
}
}
Maybe this will help.
I can see it happening if the table is scrolling quickly and you update the data source: it could hit a race condition where it tries to call cellForRowAt one more time before it realizes you changed the data source. As a precaution, I'd suggest always adding a check that your index is less than the array's count. Maybe it's paranoid, but better than a crash.
e.g:
Array has 100 items.
Swipe hard sending you towards bottom of table.
While scrolling, the array is updated to only have 10 items.
Table scroll asks for cell at row 99 because it didn't get the message yet.
Crash when you ask for item 99 in an array with only 10 items.
This crash appears when your index is more than array's count.
This crash may appear when scrolling the table view and table view get's updated. You are trying to fetch the index more than array count.
You can check the condition of indexpath.row less than array count.
Try using some thing like this
var fileNameArray = [Audio](){
didSet{
if fileNameArray.count > 0{
recordingListTableView.reloadData()
}
}
}
Is It required to do this ?
crash Is index out of range
Are you Re-Updating Array After this didSet Statement ? if Yes, Do not use this DidSet case here , use if Array is not going to be updated again ,
If you still want to use , Also make use of WillSet Case After didSet()
override func tableView(_ tableView: UITableView, number ofRowsInSection section: Int) -> Int {
guard let items.count > 0 else {return 0}
return items.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.tag = indexPath.row
let item = items[indexPath.row]
cell.textLabel?.text = item.title
}
I think items array does not have any value so it does crash. So, try this above code.

Programmatically Create Static TableView / Cells

I have a UITableView, which as an example contains dynamic cells I create based on the content of an array.
I can populate these using the count of the array and indexPath to render a cell per item. I am happy with this and it works well.
I would like to try now and create static cells programmatically.
Immediately however I am stumped, how do I create this? I'm currently overriding numberOfRowsInSection and cellForRowAt indexPath as follows:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellID", for: indexPath) as! ProfileCell
cell.rowContent.text = items[indexPath.row]
return cell
}
I suspect my first mistake is dequeueReusableCell and would really appreciate any help.
If I understood your question, you want to add a static cell in a tableView that contains dynamic cells.
If that is the case, you could hardcode this, increasing the return value here:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count+1
}
In this case, you want to add just one static cell.
In cellForRowAtIndexPath, you should define where you want to add this static cell. In the example below, it would be the first cell:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
//should change StaticCell to the static cell class you want to use.
let cell = tableView.dequeueReusableCell(withIdentifier: "staticCell", for: indexPath) as! StaticCell
//hardcore attributes here, like cell.rowContent.text = "A text"
return cell
}
let row = indexPath.row-1
let cell = tableView.dequeueReusableCell(withIdentifier: "cellID", for: indexPath) as! ProfileCell
cell.rowContent.text = items[row]
return cell
}
It's basically about shifting the items[row] according to the number of static cells you want to use.
I'm not sure if that will work, but would be my first guess (according to my experience about that). Try this and tell me if it worked :)

for loop only shows first item in array when trying to display in tableView

I'm trying to display tableview cells based on items in an array but for some strange reason it will only display the first item in the array. When I use a print statement it shows the array it being iterated through correctly.
Here is the array:
var restaurants = ["Truckyard", "EasySlider", "Revolver", "Armoury"]
Here is the cellForRowAtIndexPath:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = restaurantTableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as! restaurantCell
for rest in restaurants {
cell.restaurantImageView.image = UIImage(named: rest)
cell.restaurantNameLabel.text = rest
}
return cell
}
cellForIndexPath is called once for each row. Try this instead:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = restaurantTableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as! restaurantCell
cell.restaurantImageView.image = UIImage(named: restaurants[indexPath.row])
cell.restaurantNameLabel.text = restaurants[indexPath.row]
return cell
}
No need for a loop. cellForRowAt is providing you with an indexPath. Check the indexPath.row property. If you only have one section, the row is the index of the item in your array you want to access. You are basically iterating over your array for each row and this will inevitably set your last item as the title/image

Resources