I have a UITableView containing about 30 cells. The user is only able to select one. Once a cell is selected it shows a checkmark. But the problem is whenever I scroll down the UITableView it shows cells i never clicked with a checkmark beside them. What is the proper way to fix this?
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return breedNameArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Bcell")
let cell = tableView.dequeueReusableCell(withIdentifier: "Bcell", for: indexPath)
//cell.accessoryType = UITableViewCell.AccessoryType.checkmark
cell.textLabel?.text = breedNameArray[indexPath.item]
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath as IndexPath)
tableView.register(UINib(nibName: "BBcell", bundle: nil), forCellReuseIdentifier: "Cell")
//let cell = tableView.dequeueReusableCell(withIdentifier: "Breeds", for: indexPath)
//tableView.deselectRow(at: tableView.indexPathForSelectedRow!, animated: true)
tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Bcell")
//let cell = tableView.dequeueReusableCell(withIdentifier: "Breeds", for: indexPath)
tableView.cellForRow(at: indexPath)?.accessoryType = .none
}
The reason you are seeing cells getting checkmarks when scrolling is because the call to tableView.dequeueReusableCell(withIdentifier:"Bcell", for: indexPath) in cellForRowAt reuses already allocated UITableViewCell objects that have the identifier "Bcell". The UITableView does not automatically reset the properties of the reused UITableViewCell objects when reused in this way.
Since you are only allowing one UITableViewCell to be selected at a time, a simple solution would be to add a local variable to keep track of the selected IndexPath, and then use the local variable to set the accessoryType accordingly in cellForRowAt. Here is the code to illustrate what I mean:
// The local variable to keep track of the selected IndexPath
var selectedIndexPath = IndexPath()
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return breedNameArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Bcell")
let cell = tableView.dequeueReusableCell(withIdentifier: "Bcell", for: indexPath)
// Use selectedIndexPath to set the accessoryType as .checkmark or .none
if indexPath == selectedIndexPath {
cell.accessoryType = UITableViewCell.AccessoryType.checkmark
} else {
cell.accessoryType = UITableViewCell.AccessoryType.none
}
cell.textLabel?.text = breedNameArray[indexPath.item]
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Set the selectedIndexPath
selectedIndexPath = indexPath
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath as IndexPath)
tableView.register(UINib(nibName: "BBcell", bundle: nil), forCellReuseIdentifier: "Cell")
//let cell = tableView.dequeueReusableCell(withIdentifier: "Breeds", for: indexPath)
//tableView.deselectRow(at: tableView.indexPathForSelectedRow!, animated: true)
tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
// Un-Set the selectedIndexPath
selectedIndexPath = IndexPath()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Bcell")
//let cell = tableView.dequeueReusableCell(withIdentifier: "Breeds", for: indexPath)
tableView.cellForRow(at: indexPath)?.accessoryType = .none
}
Related
I have a tableView with 2 custom xib cells. I want to switch the custom cell when user tapped it also adjust the cell height. How do I achieve it? I tried to use container view but then the height would be the same. I want to know if it is possible to do it with two xib for tableView cell? Thank you.
var isContactView: Bool = true
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.register(UINib(nibName: "ContactTableViewCell", bundle: nil), forCellReuseIdentifier: "contactCell")
tableView.register(UINib(nibName: "DetailTableViewCell", bundle: nil), forCellReuseIdentifier: "detailCell")
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
//if ContactTableViewCell return 40
//if DetailTableViewCell return 150
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//by default the cell view is ContactTableViewCell.
//When user tapped the cell it will change to DetailTableViewCell.
if isContactView {
let cell = tableView.dequeueReusableCell(withIdentifier: "contactCell", for: indexPath) as! ContactTableViewCell
//do something...
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "detailCell", for: indexPath) as! DetailTableViewCell
//do something...
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//not sure how to change the xib in here.
//if cell view is ContactTableViewCell
cell view = tableView.dequeueReusableCell(withIdentifier: "detailCell", for: indexPath) as! DetailTableViewCell
isContactView = false
tableView.reloadData()
//else
cell view = tableView.dequeueReusableCell(withIdentifier: "contactCell", for: indexPath) as! ContactTableViewCell
isContactView = true
tableView.reloadData()
}
There are 2 cases:
If there's just single cell to be viewed, then just put the following code in didSelect:
isContactView = !isContactView
tableView.reloadData()
If there are multiple cells in which the condition is to be applied. Please put a key in your data model.
For example:
testArr is the array of objects that you are displaying in tableview. Then keep a key, say isContactView in this model. And put the login based upon this key.
Like in case of cellForRow:
if testArr[indexpath.row].isContactView {
let cell = tableView.dequeueReusableCell(withIdentifier: "contactCell", for: indexPath) as! ContactTableViewCell
//do something...
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "detailCell", for: indexPath) as! DetailTableViewCell
//do something...
}
And in didselect:
isContactView = !testArr[indexPath.row].isContactView
I have this table view, that populates through Core Data. Everything looks fine, even when scrolling, until I press and hold any row - then that row distorts like in the second image below.
(source: staticflickr.com)
(source: staticflickr.com)
Here's the code to display the tableview
override func viewDidLoad() {
super.viewDidLoad()
animateBackground(colors)
tableView.delegate = self
tableView.dataSource = self
tableView.fetchData(fetchedResultsController as! NSFetchedResultsController<NSFetchRequestResult>)
self.tableView.tableFooterView = UIView(frame: .zero)
}
func numberOfSections(in tableView: UITableView) -> Int {
guard fetchedResultsController.sections?.count != nil else { return 0 }
return fetchedResultsController.sections!.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard fetchedResultsController.sections != nil else { return 0 }
let data = fetchedResultsController.sections![section]
return data.numberOfObjects
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SkillCell", for: indexPath) as! SkillCell
configureCell(cell: cell, indexPath: indexPath as NSIndexPath)
return cell
}
func configureCell(cell: SkillCell, indexPath: NSIndexPath) {
let skill = fetchedResultsController.object(at: indexPath as IndexPath)
cell.skill = skill
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { }
}
From the code above, everything looks normal, yet this distortion occurs. Please help me to see why and how to stop it happening.
As the comment suggests, it's just a visual effect called selectionStyle of your UITableViewCell. Inside your cellForRowAt indexPath method, you can disable it like:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell") as! MyCell
cell.selectionStyle = .none // this removes the effect.
return cell
}
yes you should set tableView selection style to none.
1.Select the tableview in storyboard in the right panel set selection style to none.
or
2.in your tableview cellforrow method set
tableView.selectionstyle = none
When I scroll the screen, the upper markings disappear. I've tried some solutions found here, but none worked ...
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: "cell")
cell.textLabel?.text = players[indexPath.row]
cell.textLabel?.textColor = UIColor.white
cell.backgroundColor = colorForIndex(index: indexPath.row)
cell.textLabel?.font = UIFont(name: "Chalkboard SE", size: 20)
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.tableView.deselectRow(at: indexPath, animated: true)
if let cell = tableView.cellForRow(at: indexPath){
if (cell.accessoryType == .none) {
cell.accessoryType = .checkmark
let player = players[indexPath.row]
playersSelecteds.append(player)
} else {
cell.accessoryType = .none
let player = players[indexPath.row]
if let position = playersSelecteds.index(of: player) {
playersSelecteds.remove(at: position)
}
}
}
}
You should have a model for check-marked cells. because every time you scroll, the cells being reload based on your tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell method.
So, for example add a model like this:
var playersCheckmarked: [Bool] = []
Then add in viewDidLoad some boolean to this array as many as players array have objects. Next in override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) method set playersCheckmarked[indexPath.row] = true or playersCheckmarked[indexPath.row] = false based on user selection.
And of course in override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell method add this line:
cell.accessoryType = playersCheckmarked[indexPath.row] == true .checkmark : .none
I will paste some of my code
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if menu == "your_products"{
let cell = tableView.dequeueReusableCell(withIdentifier: "VendorTableViewCellId", for: indexPath as IndexPath) as! VendorTableViewCell
let vendor = self.vendors![indexPath.row]
cell.setVendor(vendor: vendor)
return cell
}else{
let cell = tableView.dequeueReusableCell(withIdentifier: "EventTableViewCellId", for: indexPath as IndexPath) as! EventTableViewCell
cell.setEvent(event: self.events![indexPath.row])
return cell
}
}
how to access each section and each row through last function didSelectRowAtIndexPath using indexPath.section and indexPath.row in multiple cell?
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.tableView.deselectRow(at: indexPath as IndexPath, animated: false)
print("You tapped cell number \(indexPath.row).")
}
Remove this line self.tableView.deselectRow(at: indexPath as IndexPath, animated: false)
#objc func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
print(indexPath.row)
print(indexPath.section)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You tapped cell number \(indexPath.row).")
}
I think it should work.
If this is not working then please check your delegates are connected or not.
I have a weird problem. While I select a cell with changing the background color of one UIView component, then I scroll down and selected cell going to be out of bounds of the view. I do select new cell -> previous one should get unselected, but it does not. Because when I back I do have 2 selected cells.
var lastSelectedAtIndexPath: IndexPath?
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch tableView {
case myTableView:
if let cell = myTableView.cellForRow(at: indexPath) as? MyTableViewCell {
cell.checkMarkBorder.backgroundColor = UIColor.darkGreyFts
lastSelectedFuelAtIndexPath = indexPath
}
default:
break
}
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
switch tableView {
case myTableView:
if let cell = fuelTableView.cellForRow(at: indexPath) as? MyTableViewCell {
cell.checkMarkBorder.backgroundColor = UIColor.white
}
default:
break
}
}
And iside cellForRowAt I have :
let cell = myTableView.dequeueReusableCell(withIdentifier: "myCell") as! MyTableViewCell
if let lastIndexPath = lastSelectedFuelAtIndexPath {
myTableView.deselectRow(at: lastIndexPath, animated: true)
}
cell.myImage.image = UIImage(named: fuelType)?.withRenderingMode(.alwaysTemplate)
cell.myNameLabel.text = "Test"
Any ideas what is going on?
Thanks in advance!
Don't make changes on cell outside cellForRowAt always change your dataSource and use dataSource in cellForRowAt then in didSelectRowAt and didDeSelectRowAt reload the row.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = myTableView.dequeueReusableCell(withIdentifier: "myCell") as! MyTableViewCell
cell.myImage.image = UIImage(named: fuelType)?.withRenderingMode(.alwaysTemplate)
cell.myNameLabel.text = "Test"
cell.checkMarkBorder.backgroundColor = lastSelectedFuelAtIndexPath == indexPath ? UIColor.darkGreyFts : UIColor.white
return cell
}
Now in didSelectRowAt and didDeSelectRowAt update the lastSelectedFuelAtIndexPath reload the row.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView == myTableView {
lastSelectedFuelAtIndexPath = indexPath
myTableView.reloadRows(at: [indexPath], with: .automatic)
}
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
if tableView == myTableView {
lastSelectedFuelAtIndexPath = nil
myTableView.reloadRows(at: [indexPath], with: .automatic)
}
}