TableViewCell scroll selects wrong cells when scrolling - ios

My tableview highlights the wrong cells when I scroll up and down. In didSelectRowAtIndexPath, I'm trying to add the .Checkmark accessory view as well as change the font of cell.textLabel?.text. Upon scrolling, random rows are selected and the ones I manually select sometimes deselect.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("MyCell", forIndexPath: indexPath) as! MyCell
cell.textLabel?.text = self.array[indexPath.row]
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var cell: UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
if cell.accessoryType == UITableViewCellAccessoryType.Checkmark {
cell.accessoryType = .None
cell.textLabel!.font = UIFont(name: "HelveticaNeue", size: 16)
tableView.deselectRowAtIndexPath(indexPath, animated: true)
} else if cell.accessoryType == UITableViewCellAccessoryType.None {
cell.accessoryType = .Checkmark
cell.textLabel!.font = UIFont(name: "HelveticaNeue-Bold", size: 16)
}
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}

Declare array which will keep track of which rows are selected:
var selectedRows:[Bool] = []
You need to make sure that it is populated with the same number of items as your data array.
Then modify your cellForRowAtIndexPath:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("MyCell", forIndexPath: indexPath) as! MyCell
cell.textLabel?.text = self.array[indexPath.row]
if selectedRows[indexPath.row] {
// row selected
cell.accessoryType = .Checkmark
}
else {
cell.accessoryType = .None
}
return cell
}
And in your didSelectRow function add line:
selectedRows[indexPath.row] = !selectedRows[indexPath.row]
When you modify your cell from didSelectRow the next time cell is dequeued it will have the accessory type which you set. The reason why it happens after scroll is because when you scroll the cells which become visible get dequeued. So, you need to manually set correct accessory type for them

Related

How do I save checkmarks that disappear when scrolling in UITableView?

I'm pretty new to Swift and I'm working on my first app. I'm currently using the UITableView that has an option for checkmarks to appear on the right when users tap on it. It works fine but whenever you scroll down on the list of items, the checkmarks disappear. I've checked a few online sources but I'm unsure of how to apply it to the code I have. Any help would be greatly appreciated!
Also, is there any way that I can store the checkmarks for when the user reopens the app? Every time I restart the app, the list of checks resets.
Here is the code I have so far:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "celltwo")
cell.textLabel?.text = list[indexPath.row]
return(cell)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView.cellForRow(at: indexPath)?.accessoryType == UITableViewCellAccessoryType.checkmark
{
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCellAccessoryType.none
}
else
{
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCellAccessoryType.checkmark
}
tableView.deselectRow(at: indexPath, animated: true)
}
The checkmarks are disappearing on scrolling because table views reuse the cells so the 'cellForRowAt' method gets called whenever you scroll and you haven't provided the logic to show/hide the checkmark in this method. To solve this you can do the following,
Initialise an Array to store the indexes of the selected cells.
var selectedIndexes : [Int] = []
Update your 'didSelectRowAt' method with the logic to add/remove indexes to/from the 'selectedIndexes' array.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView.cellForRow(at: indexPath)?.accessoryType == UITableViewCellAccessoryType.checkmark
{
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCellAccessoryType.none
let indexOfItemToRemove = self.selectedIndexes.index(of: list[indexPath.row])
self.selectedIndexes.remove(at: indexOfItemToRemove)
}
else
{
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCellAccessoryType.checkmark
self.selectedIndexes.append(indexPath.row)
}
}
Update your 'cellForRowAt' method with the logic to show/hide checkmark.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "celltwo")
cell.textLabel?.text = list[indexPath.row]
if self.selectedIndexes.contains(indexPath.row)
{
//cell was selected earlier
cell.accessoryType = UITableViewCellAccessoryType.checkmark
}else
{
// cell was not selected earlier
cell.accessoryType = UITableViewCellAccessoryType.none
}
return cell
}
In order to save the selection for the next time the app is launched you could save the 'selectedIndexes' array to UserDefaults. In order to achieve this do the following :
Update the 'didSelectRowAt' method to include the logic to save the selected index to UserDefaults. At the following code at the end of the method.
let userDefaults = UserDefaults.standard
userDefaults.set(selectedIndexes, forKey: "SelectedIndexes")
Add the following code to the 'viewDidLoad' method.
let userDefaults = UserDefaults.standard
self.selectedIndexes = userDefaults.value(forKey: "SelectedIndexes")
The cell accessoryType disappears because the reusability feature the UITableView use, in order to keep the selection follow the following code:
override func viewDidLoad()
{
super.viewDidLoad()
tableView.allowsMultipleSelection = true
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "celltwo")
cell.textLabel?.text = list[indexPath.row]
if let paths = tableView.indexPathsForSelectedRows
{
if (paths.contains(indexPath))
{
cell.accessoryType = .checkmark
}
else
{
cell.accessoryType = .none
}
}
else
{
cell.accessoryType = .none
}
cell.selectionStyle = .none
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
if tableView.cellForRow(at: indexPath)?.accessoryType == UITableViewCellAccessoryType.checkmark
{
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCellAccessoryType.none
}
else
{
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCellAccessoryType.checkmark
}
//remove deselection
//tableView.deselectRow(at: indexPath, animated: true)
}
Regard saving the selection, this info I guess should be persisted with your datasource in CoreData or UserDefaults.

Disclosure indicator to specific cell in static TableView in Swift

I created a static TableView and I would like to add or remove a disclosure indicator depending if we are consulting our own account or a guest account.
This is what I would like :
let index = IndexPath(row: 4, section: 0)
let cell = tableView.cellForRow(at: index)
if currentUser {
cell.accessoryType = .none
//cell.backgroundColor = UIColor.red
}
I tried to put it in the viewDidLoad function but it didn't work. I tried cellForRowAt indexPath also and same result.
How could I do that?
Just check if you want to show disclosure indicator in cellForRowAt indexPath method.
if (wantsToShow){ // Your condition goes here
cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
}
else{
cell.accessoryType = .none
}
That's it.
Your are working with static cells so cellForRow will not get called. Instead, simply drag connect your cell and set it up, like this
Please use below code in cellForRowTableView code
It will work for you
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
if currentUser {
// Own Account
cell.accessoryType = .none
//cell.backgroundColor = UIColor.red
}else{
//Guest Account
cell.accessoryType =.checkmark
}
}
Swift
Specific cell
if indexPath.row == 1 {
cell.accessoryType = .disclosureIndicator
} else {
cell.accessoryType = .none
}
To add accessory on a specific static cell, I used tableView, cellForRowAt but i couldn't access a reference to UITableViewCell.
Then i found super.tableView(tableView, cellForRowAt: indexPath)
So here is my code:
Assuming you know the specific indexpath you want:
var indexPathSort = IndexPath(item: 0, section: 0)
var indexPathPrice = IndexPath(item: 0, section: 1)
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = super.tableView(tableView, cellForRowAt: indexPath)
if indexPath == indexPathSort || indexPath == indexPathPrice {
cell.accessoryType = .checkmark
}
return cell
}

indexPathForSelectedRow returns nil although I have a cell programmatically selected

I have a UITableView which I load with some application settings. I need the table to be single-select, and since the table holds settings there might be a chance some cell will be programmatically selected based on the setting enabled status.
Currently, I'm experiencing a weird behaviour where if I programmatically select a cell then indexPathForSelectedRow returns nil (when it should return the index for the cell I selected), thus when I tap on a second cell (to change the currenty selected setting) I end up with two cells selected (even when I set allowMultipleSelection to false in my tableView object).
Here's my code:
override func viewDidLoad() {
tableView.allowsMultipleSelection = false
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: UITableViewCell? = tableView.dequeueReusableCellWithIdentifier("myCell")
if let cell = cell {
// tableObject is an array containing settings model
let row = tableObject[indexPath.row]
cell.textLabel!.text = row.settingValue
if row.selected {
cell.setSelected(true, animated: false)
cell.accessoryType = .Checkmark
}
cell.tag = row.id
}
return cell!
}
override func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
// oldIndex is always nil for the cell I called setSelected in cellForRowAtIndexPath
if let oldIndex = tableView.indexPathForSelectedRow {
if let oldCell = tableView.cellForRowAtIndexPath(oldIndex) {
tableView.deselectRowAtIndexPath(oldIndex, animated: true)
oldCell.accessoryType = .None
}
}
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
cell.setSelected(true, animated: true)
cell.accessoryType = .Checkmark
}
return indexPath
}
override func tableView(tableView: UITableView, willDeselectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
cell.accessoryType = .None
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
return indexPath
}
Any idea how I can always have only one cell selected at a time?
Thanks!
Just in case someone else comes across this same behaviour, it seems that selecting the cell through cell.setSelected() it's not the same as invoking tableView.selectRowAtIndexPath() method. Selecting the row with the latest does the job perfectly and solves the issue.
Note that calling tableView.reloadData() resets the tableView.indexPathForSelectedRow to nil.
here how you can accomplish table view single selection through tableView.indexPathForSelectedRow :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.accessoryType = tableView.indexPathForSelectedRow == indexPath ? .checkmark : .none
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
if let visiblePaths = tableView.indexPathsForVisibleRows
{
for visibleIndexPath in visiblePaths
{
let cell = tableView.cellForRow(at: visibleIndexPath)
if visibleIndexPath == indexPath
{
cell?.accessoryType = .checkmark
}
else
{
cell?.accessoryType = .none
}
}
}
}
Create an array like
var arrSelectCell = [NSIndexPath]()
And do the code at didSelectRowAtIndexPath like following:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if arrSelectCell.contains(indexPath) {
if let oldCell = tableView.cellForRowAtIndexPath(indexPath) {
if let index = arrSelectCell.indexOf(indexPath) {
arrSelectCell.removeAtIndex(index)
}
tableView.deselectRowAtIndexPath(indexPath, animated: true)
oldCell.accessoryType = .None
}
}
else
{
arrSelectCell.append(indexPath)
if let selectCell = tableView.cellForRowAtIndexPath(indexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
selectCell.accessoryType = .Checkmark
}
}
and also dont forget to set a code in cellForRowAtIndexPath to check already checked and unchecked cell maintain after reuse cell also. So need to few code you need to write as following.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("serchCell", forIndexPath: indexPath) as! SearchTableViewCell
if arrSelectCell.contains(indexPath)
{
cell.accessoryType = .Checkmark
}
else
{
cell.accessoryType = .None
}
return cell
}

Checkmarks in UITableViewCells showing incorrectly

I have a small scrollable tableView which displays roughly eight rows, when I select a row a checkmark appears, when I select it again it disappears.
The issue is, as I scroll down and the cells are reused, more rows are automatically checked. What is the best practice for tracking which rows require a checkmark so that this does not happen. I have looked everywhere but haven't found Swift solution that works well.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell")! as UITableViewCell
cell.textLabel?.text = "\(searchResults.usernameUserSearchArray[indexPath.row])"
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selectedCell = tableView.cellForRowAtIndexPath(indexPath)
if selectedCell?.accessoryType == UITableViewCellAccessoryType.Checkmark {
selectedCell?.accessoryType = UITableViewCellAccessoryType.None
} else {
selectedCell?.accessoryType = UITableViewCellAccessoryType.Checkmark
}
tableView.reloadData()
}
Wouldn't something like this do it for you? I haven't tested it since I am not on my Mac.
var selectedCells = [NSIndexPath]()
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell")!
cell.accessoryType = selectedCells.contains(indexPath) ? .Checkmark : .None
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selectedCell = tableView.cellForRowAtIndexPath(indexPath)
selectedCells.append(indexPath)
if selectedCell?.accessoryType == .Checkmark {
selectedCell?.accessoryType = .None
selectedCells = selectedCells.filter {$0 != indexPath}
} else {
selectedCell?.accessoryType = .Checkmark
}
}
In didSelectRowAtIndexPath create an NSMutableArray which stores selected index of the cell and in cellForRowAtIndexPath check using if condition from array whether index path is available in array or not. If available than set accessory type checkmark true for index path.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell")! as UITableViewCell
cell.textLabel?.text = arr.objectAtIndex(indexPath.row) as? String
if(arr .objectAtIndex(indexPath.row) as! String == selectedValue.objectAtIndex(indexPath.row) as! String)
{
cell.accessoryType = UITableViewCellAccessoryType.Checkmark
}
else
{
cell.accessoryType = UITableViewCellAccessoryType.None
}
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selectedCell = tableView.cellForRowAtIndexPath(indexPath)
if selectedCell?.accessoryType == UITableViewCellAccessoryType.Checkmark {
selectedCell?.accessoryType = UITableViewCellAccessoryType.None
selectedValue .removeObject(arr .objectAtIndex(indexPath.row))
} else {
selectedCell?.accessoryType = UITableViewCellAccessoryType.Checkmark
selectedValue .addObject(arr .objectAtIndex(indexPath.row))
}
}

UI table view cell check marks repeating swift

My when a table cell is checked and you scroll down a check mark is repeated.
I know this is due to cell reuse, but don't know how to fix it.
function to populate table
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let item = self.myEvents[indexPath.row]
let cell = tableView.dequeueReusableCellWithIdentifier("row", forIndexPath: indexPath) as! UITableViewCell
cell.textLabel!.text = self.myEvents[indexPath.row]
return cell
}
//function to make the table checkable
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
println("indexpath: \(indexPath)")
let cell: UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
let text = cell.textLabel!.text
if cell.accessoryType == UITableViewCellAccessoryType.None {
cell.accessoryType = UITableViewCellAccessoryType.Checkmark
//let text = cell.textLabel!.text
if(checkedEvents[0] == ""){
checkedEvents[0] = text!
}else{
checkedEvents.append(text!)
}
} else {
cell.accessoryType = UITableViewCellAccessoryType.None
var index = 0
for event in checkedEvents{
if event == text{
self.checkedEvents.removeAtIndex(index)
index++
}
}
}
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
First, you need to store the number of the selected row somewhere. How about self.selectedRowNumber?
var selectedRowNumber: Int? = nil
Set this when the user selects a row (short version):
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell: UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
cell.accessoryType = .Checkmark
self.selectedRowNumber = indexPath.row
// You'll also need some code here to loop through all the other visible cells and remove the checkmark from any cells that aren't this one.
}
Now modify your -tableView:cellForRowAtIndexPath: method to clear the accessory if it's not the selected row, or add it if it is:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("row", forIndexPath: indexPath) as! UITableViewCell
cell.textLabel!.text = self.myEvents[indexPath.row]
cell.accessoryType = .None
if let selectedRowNumber = self.selectedRowNumber {
if indexPath.row == selectedRowNumber {
cell.accessoryType = .Checkmark
}
}
return cell
}
This code was written here in the browser, and may need some fixes to compile.
If you want only one selection, put tableView.reloadData() in your didSelectRowAtIndexPath function

Resources