I have a UITableView, when the user taps a cell it turns grey to indicate it is complete. However when the cell is scrolled out of view, when it comes back into view it has returned to its default background colour erasing the users interaction.
Is there a way to persist the cell tapped background color on scrolling?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? TodaysRoutineTableViewCell else {
fatalError("Unexpected Index Path")
}
cell.backgroundColor = UIColor.customBackgroundGraphite()
cell.textLabel?.textColor = UIColor.white
configure(cell, at: indexPath)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
if cell.backgroundColor == UIColor.customExerciseDoneCellColor() {
cell.backgroundColor = UIColor.customBackgroundGraphite()
} else if cell.backgroundColor == UIColor.customBackgroundGraphite() {
cell.backgroundColor = UIColor.customExerciseDoneCellColor()
}
}
}
The issue is when you scroll after selecting the cell, the selected colour is not retained isn't it??
Cell for row delegate is called whenever you scroll. So even though you set background in Did select delegate its changed when Cell for row is called.
In order to tackle this situation add a boolean to tableview cell and when you select the cell change the boolean status.
In cell for row add this:
if cell.CheckBoolean == true{
cell.backgroundcolor = UI.color()
}else{
cell.backgroungcolor = UI.color()
}
In did select delegate :
change the value of that boolean and reload the table view
Related
I have a tableview in my storyboard where the prototype cell has a disclosure indicator by default.
When I populate my table I want to remove the indicator only from the last cell AND center a spinner on it.
I'm doing it like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CharacterCell", for: indexPath) as! CharacterCell
if indexPath.row == charactersViewModel.charactersCount - 1 {
cell.accessoryType = .none
cell.accessoryView = .none
// Spinner
let spinner = UIActivityIndicatorView(style: .large)
spinner.color = .white
spinner.center = cell.contentView.center
cell.contentView.addSubview(spinner)
spinner.startAnimating()
}
return cell
}
The problem is that the spinner is offcenter, a little bit to the left, just like if the accessory is still there, but hidden.
I feel maybe I'm missing the lifecycle of a table cell, maybe it's getting the center value of the content view when the accessory is still there, so when it's removed it is offcenter?
I tried on willDisplay as well but the same thing happens.
Any tips on this?
As #Paulw11 mentioned, I used a second subclass and created another cell prototype in my tableview.
Then when the last position at the table is reached, we can use the second prototype on cellForRowAt.
Here how it is:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row >= charactersViewModel.charactersCount - 1 {
reloadRows(indexPath: indexPath)
let cell = tableView.dequeueReusableCell(withIdentifier: "LoadingCharacterCell", for: indexPath) as! LoadingCharacterCell
cell.startSpinner()
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "CharacterCell", for: indexPath) as! CharacterCell
cell.configureCell(charactersViewModel: charactersViewModel, cell: cell, index: indexPath.row)
return cell
}
}
private func reloadRows(indexPath: IndexPath) {
var indexPathList = [IndexPath]()
indexPathList.append(indexPath)
charactersTableView.reloadRows(at: indexPathList, with: .automatic)
}
And with the reloadRows function, the last cell is updated and removed when the table receives more data.
in an App I am writing, i have a form built using storyboard around a UITableView with static cells in a UITableViewController. The table has 6 rows that collects different bits of information. Row 0,3,4,5 have textfield/textview/labels to collect/display some information, row 1 has a UICollectionView with a dozen or so icons. row 2 has a height of 50 but is empty for now.
this setup works well but i am trying to add some functionality and i am stumped.
depending on what I select in the UICollectionView (in row 1), i would like to load one of a number of different cells in row 2
for example, if i click on the 2nd icon (blood pressure), the cell to load in row 2 will be used to input the systolic and diastolic values. if I click on the 3rd icon (temperature), the cell to load in row 2 will be used to input the temperature, etc...
i've designed the cells in separate class files of UITableViewCell and the corresponding xib files. i know how to register them using
tableView.register(UINib(nibName: "myBPTableCell", bundle: nil), forCellReuseIdentifier: "myBPTableCell")
just not sure where to put the logic to update the table on the fly.
if eventTypeCollectionIndex == 4 {
let cell = tableView.dequeueReusableCell(withIdentifier: "myBPTableCell", for: indexPath) as! myBPdTableCell
return cell
}
i was thinking in
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
but since this function has to return a cell, i am not sure how to return the pre-existing cells for anything other than row 2
thanks
Sami
You would want to define different cases in the cell for row at index method. And then when the user taps a button, reload the cell that you would want to change.
You would either use reload rows or self.tableView.reloadData()
So for example:
var didTap = false
func buttonTapped() {
didTap = true
// Reload table
}
And then either use a different nib for the cell:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if didTap {
// show new appearance
let cell = tableView.dequeueReusableCell(withIdentifier: "myBPTableCell", for: indexPath) as! myBPdTableCell
return cell
} else {
// show original
let cell2 = tableView.dequeueReusableCell(withIdentifier: "myBPTableCell2", for: indexPath) as! myBPdTableCell2
return cell2
}
}
Or you could update the properties of the same nib class:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myBPTableCell", for: indexPath) as! myBPdTableCell
if didTap {
// show new appearance
cell.backgroundColor = .white
} else {
// show original
cell.backgroundColor = .black
}
return cell
}
Edit based on comment:
You can also just do the following, where you have one cell for row 2 and then another cell for the other rows:
if indexPath.row == 2 {
let cell = tableView.dequeueReusableCell(withIdentifier: "myBPTableCell", for: indexPath) as! myBPdTableCell
if didTap {
cell.backgroundColor = .white
} else {
cell.backgroundColor = .black
}
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "myOtherCell", for: indexPath) as! myOtherCell
return cell
}
I am currently working on a swift based HRM project. where it requires to show a tableview with slightly customized cell. cells it self containing two buttons, under some business logic one button would be hidden. for example ,
if the current user is the employee himself , he can see a list, the cell containing his name can see two buttons,but other cell would show simply one button.
i have tried the followings:
1. if the userId == employeeId (employeeId came from a model) then ,
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ClaimTableViewCell", for: indexPath) as! ClaimTableViewCell
if(self.claimdata[indexPath.section].employeeId == self.empId) {
cell.CancelButton.isHidden = false
}
also , i have tried
if(self.claimdata[indexPath.section].employeeId != self.empId) {
cell.CancelButton.frame.size.height = 0
}
works fine for the first frame , the problem begins when i begin to scroll. for some unintended cell it also shows two buttons.
Am I missing something?
This issue is due to the cell reusability in UITableView.
Use below code in your cellForRowAtIndexPath method.
cell.CancelButton.isHidden = true
if(self.claimdata[indexPath.section].employeeId == self.empId) {
cell.CancelButton.isHidden = false
}
As tableView cell is reusableCell
dequeueReusableCell withIdentifier
you just need to give else condition so when it reuse the cell again it knowns what to do with CancelButton.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ClaimTableViewCell", for: indexPath) as! ClaimTableViewCell
if(self.claimdata[indexPath.section].employeeId == self.empId) {
cell.CancelButton.isHidden = false
}else{
cell.CancelButton.isHidden = true
}
}
This question already has answers here:
How can I disable the UITableView selection?
(42 answers)
Closed 4 years ago.
I would like to make the cells of a tableView non-selectable but still allow scrolling. When I placed
tableView.isUserInteractionEnabled = false
which is recommended in some answers in viewDidLoad, it prevents selection but also prevents scrolling.
Adding:
cell.selectionStyle = .none
in cellforrowatindexpath as below not have any effect for me.
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
self.tableView.beginUpdates()
self.tableView.endUpdates()
if let cell = tableView.dequeueReusableCell(withIdentifier: "myMessageCell", for: indexPath) as? myMessageCell {
cell.message = messages[indexPath.row]
cell.selectionStyle = .none
}
return tableView.dequeueReusableCell(withIdentifier: "myMessageCell", for: indexPath)
}
Can anyone suggest how to prevent selection without preventing scrolling?
Thanks in advance for any suggestions.
Hope you are looking for this one:
tableView.allowsSelection = false
The problem is you are not returning the same cell that you are dequeuing. Instead you are dequeuing another cell and returning it which has none of the properties that you have set.
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "myMessageCell", for: indexPath) as? myMessageCell {
cell.message = messages[indexPath.row]
cell.selectionStyle = .none
return cell // Return the cell here instead
}
// return tableView.dequeueReusableCell(withIdentifier: "myMessageCell", for: indexPath) // Return another cell which has none of your properties set.
return UITableViewCell() // return default cell in case your cell is not dequeued
}
So I have a TableView with custom cells that I make from fetching data from a server. I have a variable 'selectedIndex' which I use to keep track and add a checkmark accessory to my cell. Weirdly, it only works after I scroll the selected cell (cell with indexPath.row is equal to off the screen and back. Here is the code in my willDisplayCell method:
if selectedIndex == indexPath.row {
if let accessory = cell.viewWithTag(528) as? UITableViewCell {
accessory.frame = (cell.checkmarkView.bounds.offsetBy(dx: 0, dy: 7))
accessory.accessoryType = .checkmark
accessory.isUserInteractionEnabled = false
accessory.backgroundColor = UIColor.clear
accessory.tag = 528
accessory.isHidden = false
print("accessory not nil")
} else {
let accessory = UITableViewCell()
accessory.frame = (cell.checkmarkView.bounds.offsetBy(dx: 0, dy: 7))
accessory.accessoryType = .checkmark
accessory.isUserInteractionEnabled = false
accessory.backgroundColor = UIColor.clear
accessory.tag = 528
accessory.isHidden = false
cell.addSubview(accessory)
print("accessory nil")
}
} else {
let accessory = cell.viewWithTag(528)
accessory?.isHidden = true
}
For example, when the selected index is 0, the checkmark is not displayed at first view, and the logs print ("accessory nil"). When I scroll the cell at index 0 off-screen, and scroll to it again, the checkmark is now displayed and the logs print ("accessory not nil"). For more information, tapping on a cell works just as expected.
Edit:
I know I'm adding another TableViewCell to my cell! I'm only doing that because I need my checkmark accessory to be in a different position (left-aligned, top-aligned) than the default one (right-aligned, vertically centered). So what I did was add a view in my XIB that is in the desired position (right-aligned, top-aligned) and and aligned my programatically-made cell to it. If you could show me another approach it would be appreciated.
Removed cell.addSubview from the first if. Still behaves the same.
Thanks!
You are adding UITableViewCell into your dequeued original cell, this is unnecessary, you need implement this logic in your cellForRowAt datasource method,
updated
If you need a custom position for your accessory view use cell.layoutMargins check my updated code
try this instead
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "YourCellIdentifier", for: indexPath) as! YourCellClass
cell.accessoryType = .none
if selectedIndex == indexPath.row {
cell.accessoryType = .checkmark
}
//if you need a custom position for your accessory view
cell.layoutMargins = UIEdgeInsetsMake(0, 0, 0, 100)
}
UITableView will optimize your cells by reusing them. So in your case it seems better to use tableView.dequeueReusableCell(withIdentifier: "identifier", for: indexPath) instead of calling the Cell's constructor yourself or find the cell back by looking for it's tag.
Besides that the part which determines the state of the cell should be in the UITableView's tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell function instead of the willDisplayCell. This might solve your problem and prevents issues with reusing cells.