Swift Tableview reusable cell issue - swift

I am having an issue with what I think is reusable cells in a tableview cell, unfortunately I can't work out how to force the state of the cell. I am fairly sure this is the issue because when I reload the tableview everything is displayed correctly. It's only when I scroll that I start to see issues, if I once again reload the display corrects itself.
This is the correct display :
and the incorrect display after scrolling about :
my cellForRowAt code :
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "historyGoalCell", for: indexPath)
let name = items[indexPath.section][indexPath.row].name
let date = dateManager.dateAsString(for: items[indexPath.section][indexPath.row].date!)
if tempDate != date {
// show header
cell.textLabel?.text = date
tempDate = date
} else {
// don't show header
cell.textLabel?.text = ""
}
cell.detailTextLabel?.text = "\(date),\(name ?? "")"
return cell
}
Thanks for any help, I have been stuck with this for a couple of days, very new to TableViews - thanks

tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
can be invoked in any order. It isn't called consistently from 1 to N, so logic with tempDate works not as planned. Better do some pre-work and make an array with indexes where you place headers. For example
struct Pair : Hashable {
var i : Int
var j : Int
}
//Somewhere one time before the first reloadData
var hasIndex : Set<Pair> = []
var tempDate: Date = Date.distantPast
for i in 0..<sections {
for j in 0..<rows[i] {
let name = items[i][j].name
let date = dateManager.dateAsString(for: items[i][j].date!)
if tempDate != date {
hasIndex.insert(Pair(i: i, j: j))
// OR items[i][j].showHeader = true
tempDate = date
} else {
// OR items[i][j].showHeader = false
}
}
}
...
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "historyGoalCell", for: indexPath)
let name = items[indexPath.section][indexPath.row].name
let date = dateManager.dateAsString(for: items[indexPath.section][indexPath.row].date!)
if hasIndex.contains(Pair(i: indexPath.section, j: indexPath.row)) {
// OR if items[indexPath.section][indexPath.row].showHeader {
cell.textLabel?.text = date
tempDate = date
} else {
cell.textLabel?.text = ""
}
cell.detailTextLabel?.text = "\(date),\(name ?? "")"
return cell
}

Related

Fatal error: Index out of range when filling cells

I am getting the this error: Index out of range when trying to fill cells out of a table, any idea why this happens?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: VCInstanciasCell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! VCInstanciasCell
cell.siteLabel.text = String?([arrayItinerario[indexPath.section]][indexPath.row].ubicacion!) ?? ""
cell.txtLabel.text = String?([arrayItinerario[indexPath.section]][indexPath.row].maquinaTipo!) ?? ""
cell.orderLabel.text = String?([arrayItinerario[indexPath.section]][indexPath.row].fma!) ?? ""
cell.codeLabel.text = String?([arrayItinerario[indexPath.section]][indexPath.row].iso!) ?? ""
cell.newLabel.text = String?([arrayItinerario[indexPath.section]][indexPath.row].instanciaID!) ?? ""
cell.setLeftUtilityButtons(self.leftButtons(), withButtonWidth: 50.0)
return cell
}
the error is at cell.siteLabel.text = String?([arrayItinerario[indexPath.section]][indexPath.row].ubicacion!) ?? "" in indexPath.Row
It is quite hard to answer this question without seeing how arrayItinerario is defined, and what triggers the cellForRow (when is the timing you're calling reloadData)
But first of all, I'd try to simplify your code, it will make it easier to understand where the crash comes from, as you're using force unwrapped and also cannot know in which of the row/section you get the index out of range.
I added some checks + guards + prints, it will explain my answer + make your testing easier:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: VCInstanciasCell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! VCInstanciasCell
if let value = value(for: indexPath) {
cell.siteLabel.text = ""
if let ubicacion = value.ubicacion {
cell.siteLabel.text = String(ubicacion)
}
// and so on
} else {
print("couldn't get value for indexPath:\(indexPath)")
}
return cell
}
func value(for indexPath: IndexPath) -> SomeObject? {
let section = indexPath.section
let row = indexPath.row
guard section < arrayItinerario.count else {
print("section \(section) is out of range for list with count: \(arrayItinerario.count)")
return nil
}
let list = arrayItinerario[section]
guard row < list.count else {
print("row \(row) is out of range for list with count: \(list.count)")
return nil
}
return list[row]
}

Fetching from Core Data and display in UITableView with custom cell

I have a UITableView with a custom cell:
title, label, text
These are 3 elements of UITextView, now I have an entity in my core data:
I want to put title, in title, the date in label and introText in text:
I did this:
do{
var request = NSFetchRequest<NSFetchRequestResult>(entityName: "Actualites")
let count = try context.count(for: request)
if (count == 0) {
REST().SaveArticles(limit: 5, limitstart: 0, catid: 6, key: "xxx", context: context)
} else {
var actualites = [Actualites]()
actualites = try context.fetch(request) as! [Actualites]
let cell:ActuTblCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! ActuTblCell
for actualites in actualites {
print(actualites.title)
}
}
}catch{
print(error)
}
I can get the titles in the loop, but how to display the data ? Do I have to create 3 arrays ?
EDIT:
I think I have to do a loop here ?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ActuTblCell
let formatter = DateFormatter()
formatter.dateFormat = "dd/MM/yyyy HH:mm"
cell.titleActuCell.text = actualites[indexPath.row].title
cell.dateActuCell.text = formatter.string(from: actualites[indexPath.row].created! as Date)
cell.descriptionActuCell.text = actualites[indexPath.row].introText
return cell
}
Thread 1: Fatal error: Index out of range
In your view controller keep an array of Actualites: var actualites = [Actualites](). Then your else block would look like this:
else {
actualites = try context.fetch(request) as! [Actualites]
tableview.reloadData()
}
You should implement the UITableViewDataSource protocol to return the number of cells based on the actualites array, and in the tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) you get the actuality for every cell by this statement: let actuality = actualites[indexPath.row] and display in the appropriate cell.

Checkmark's associated with wrong row in TableView when using SearchBar

I am creating an application where when a User searches for an item in the TableView they can click on it and a checkmark appears next to it. However, say when I select the first item I have searched for and click it then delete my search the checkmark stays on the first row but for a completely different object, I searched for, to begin with (see images below).
When Searching
When not Searching
var searchingArray = [Symptoms]()
var filteredArray = [Symptoms]()
var selectedSymptoms = [Symptoms]()
var clicked = [String]()
var searchingUnderWay = false
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = searchingSymptomsTableView.dequeueReusableCell(withIdentifier: "ExtraSymptoms", for: indexPath) as? ExtraSymptomCell {
let searchingArrays: Symptoms!
if searchingUnderWay {
searchingArrays = self.filteredArray[indexPath.row]
} else {
searchingArrays = self.searchingArray[indexPath.row]
}
cell.updateUI(symptomNames: searchingArrays)
return cell
} else {
return UITableViewCell()
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedRow: Symptoms!
let symptomName: String!
let cell : UITableViewCell = tableView.cellForRow(at: indexPath)!
if searchingUnderWay {
selectedRow = filteredArray[indexPath.row]
symptomName = filteredArray[indexPath.row].name as String
if clicked.contains(symptomName) {
cell.accessoryType = .none
let indexNumber = clicked.index(of: symptomName)
clicked.remove(at: indexNumber!)
if let element = selectedSymptoms.index(where: { $0.name == selectedRow.name }) {
selectedSymptoms.remove(at: element)
}
} else {
clicked.append(symptomName)
cell.accessoryType = .checkmark
searchingSymptomsTableView.reloadData()
selectedSymptoms.append(selectedRow)
}
} else {
selectedRow = searchingArray[indexPath.row]
symptomName = searchingArray[indexPath.row].name as String
if clicked.contains(symptomName) {
cell.accessoryType = .none
let indexNumber = clicked.index(of: symptomName)
clicked.remove(at: indexNumber!)
if let element = selectedSymptoms.index(where: { $0.name == selectedRow.name }) {
selectedSymptoms.remove(at: element)
}
} else {
clicked.append(symptomName)
cell.accessoryType = .checkmark
searchingSymptomsTableView.reloadData()
selectedSymptoms.append(selectedRow)
}
print(clicked)
print(selectedSymptoms)
}
}
I wish for the item I searched using the searchbar to still be checked when you delete the search.
Many thanks
Welcome to TableViewController logic. It seems really strange, but it works correct)
You need to override prepareForReuse() method in your ExtraSymptomCell. And clear all the values your cell contains including accessoryType
override func prepareForReuse() {
super.prepareForReuse()
accessoryType = .none
}
In your tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath):
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = searchingSymptomsTableView.dequeueReusableCell(withIdentifier: "ExtraSymptoms", for: indexPath) as? ExtraSymptomCell {
let symptomName: String!
let searchingArrays: Symptoms!
if searchingUnderWay {
searchingArrays = self.filteredArray[indexPath.row]
symptomName = filteredArray[indexPath.row].name as String
} else {
searchingArrays = self.searchingArray[indexPath.row]
symptomName = filteredArray[indexPath.row] as String
}
cell.updateUI(symptomNames: searchingArrays)
if clicked.contains(symptomName) {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
return cell
} else {
return UITableViewCell()
}
}
Since UITableViewCell are reused, the checkmark will appear in a cell when you reload Table Data.
In cellForRowAt set the accessoryType to .none, to remove a previously checked cell checkmark:
let cell : UITableViewCell = tableView.cellForRow(at: indexPath)!
cell.accessoryType = .none
This will remove the check from the previous search.

Displaying two reusable cells in tableview - Swift 3

I have two custom reusable table view cells in my table view. The first cell, I would like it to be present at all times. The second cell and beyond, are returning a count that is being passed from mysql database.
// return the amount of cell numbers
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
// cell config
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row < 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! InfoCell
//set the data here
return cell
} else {
let Postcell = tableView.dequeueReusableCell(withIdentifier: "PostCell", for: indexPath) as! PostCell
let post = posts[indexPath.row]
let image = images[indexPath.row]
let username = post["user_username"] as? String
let text = post["post_text"] as? String
// assigning shortcuts to ui obj
Postcell.usernameLbl.text = username
Postcell.textLbl.text = text
Postcell.pictureImg.image = image
return Postcell
}
} // end of function
My first cell is there and so are the post.count, but for some reason the posts.count is missing one post and I believe this is because of the first cell. Can anybody help me with this? thanks in advance.
You need to adjust the value returned from numberOfRowsInSection to account for the extra row. And you would need to adjust the index used to access values from your posts array to deal with the extra row.
But a much better solution is to use two sections. The first section should be your extra row and the second section would be your posts.
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return 1
} else {
return posts.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! InfoCell
//set the data here
return cell
} else {
let Postcell = tableView.dequeueReusableCell(withIdentifier: "PostCell", for: indexPath) as! PostCell
let post = posts[indexPath.row]
let image = images[indexPath.row]
let username = post["user_username"] as? String
let text = post["post_text"] as? String
// assigning shortcuts to ui obj
Postcell.usernameLbl.text = username
Postcell.textLbl.text = text
Postcell.pictureImg.image = image
return Postcell
}
}

Cannot invoke initializer for type 'Double' with an argument list of type '([Int])'

I'm converting the timestamp to normal type but I'm getting this error --- Cannot invoke initializer for type 'Double' with an argument list of type '([Int])', How to fix it?
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "jobCell", for: indexPath)
//print jobTime = 1504753200
let unixTimestamp = Double(jobTime) //error here
let date = Date(timeIntervalSince1970: unixTimestamp)
cell.textLabel?.text = "\(jobTime[indexPath.row])-\(jobEndTime[indexPath.row])"
return cell
}
I'm retrieving the JSON data and put into table view cell
var jobTime = [Int]()
var jobEndTime = [Int]()
viewDidLoad
guard let jobs = json["jobs"] as? [[String:Any]] else {return }
for job in jobs {
if let id = job["jobTime"] as? Int{
self.jobTime.append(id)
}
if let id = job["jobEndTime"] as? Int{
elf.jobEndTime.append(id)
}
}
self.tableView.reloadData()
}
Try this-
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "jobCell", for: indexPath)
//print jobTime = 1504753200
let unixTimestamp = Double(jobTime[indexPath.row]) //Change here
let date = Date(timeIntervalSince1970: unixTimestamp)
cell.textLabel?.text = "\(jobTime[indexPath.row])-\(jobEndTime[indexPath.row])"
return cell
}