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.
Related
Right now I can add checkmarks to the table of choice selected using your approach vadian. I also added the Boolean("selected") in my data class. What I dont get is how to save the boolean selected for each row with UserDefaults or than how to load this in cell for row at table. Do I need to even touch didload section?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as? TableCell else {
return UITableViewCell()
}
let product = products[indexPath.row]
cell.accessoryType = product.selected ? .checkmark : .none
return cell }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as? TableCell else {
return UITableViewCell()
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
var selected = products[indexPath.row].selected
products[indexPath.row].selected = !selected
tableView.reloadRows(at: [indexPath], with: .none)
if selected != true {print("selected", indexPath.row, selected )}
else {print("selected", indexPath.row, selected )}
selected = UserDefaults.standard.bool(forKey: "sound")
}
Add a property var approved : Bool to your data model
Update the property in the model when the selection changes and reload the row.
In cellForRow set the checkmark depending on the property
...
let cell = tableview.dequeueReusableCell(withIdentifier: "myfeedTVC", for: indexPath) as! MyFeedTVC
let user = userDataArray[indexPath.row]
cell.accessoryType = user.approved ? .checkmark : .none
...
UITableView checkmark happening is deselect when I was scrolling
Code
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return dizi.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "cell")
cell.textLabel?.text=dizi[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
}
}
first
second
how can i fix it ? please help me.
Thats because in UITableView Cell gets reused when you scroll and its your responsibility to ensure it restores its UI state when reused
So create a array to hold the selected indexPath
var selectedIndex : [IndexPath]! = [IndexPath]()
In cellForRowAtIndexPath
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "cell")
if if selectedIndex.contains(indexPath) {
cell.accessoryType = UITableViewCellAccessoryType.checkmark
}
else {
cell.accessoryType = UITableViewCellAccessoryType.none
}
cell.textLabel?.text=dizi[indexPath.row]
return cell
}
Finally update the selectedIndexArray
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
if tableView.cellForRow(at: indexPath)?.accessoryType == UITableViewCellAccessoryType.checkmark
{
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCellAccessoryType.none
self.selectedIndex = selectedIndex.filter({
return $0 != indexPath
})
}
else
{
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCellAccessoryType.checkmark
self.selectedIndex.append(indexPath)
}
}
Because you also need to set UITableViewCellAccessoryType in cellForRowAt. because if use scroll up/down table then cell will be reuse so.
How to do it ? take one array same size of rows and repeat with value 0 and when you do check any row then update value 1 with indexPath.row and if you uncheck any row then update value 0 with indexPath.row
and in cellForRowAt method before return cell put condition like
if checkUncheckArr[indexPath.row] == 0 {
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCellAccessoryType.non
}
else {
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCellAccessoryType.checkmark
}
I am new to coding, I have a mutable array that contains nearly 20 names and I loaded that in a table view. I want to show checkmark for a particular name row in table view based on ID but am unable to do that. Please help to fix that issue.
Here I give the code what I am tried.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == productListTableView {
let cell:BillTableViewCell = tableView.dequeueReusableCell(withIdentifier: "BillTableViewCell") as! BillTableViewCell
let productName:String = (productNameArray[indexPath.row] as? String)!
cell.productNameLbl.text = productName
print("addIdsMutArray :",addIdsMutArray)
print(addIdsMutArray.count)
print("oldSelectedIDStr :",oldSelectedIDStr)
for i in (0..<addIdsMutArray.count)
{ var arrayCheck = NSArray()
arrayCheck = addIdsMutArray[i] as! NSArray
print(self.addIdsMutArray)
if arrayCheck.contains(oldSelectedIDStr)
{
print("Found")
cell.accessoryType = .checkmark
tableView.selectRow(at: indexPath, animated: false, scrollPosition: UITableViewScrollPosition.bottom)
} else {
print("Not Found")
cell.accessoryType = .none
}
}return cell}
The problem is the loop, here the solution:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == productListTableView {
let cell:BillTableViewCell = tableView.dequeueReusableCell(withIdentifier: "BillTableViewCell") as! BillTableViewCell
let productName:String = (productNameArray[indexPath.row] as? String)!
cell.productNameLbl.text = productName
print("addIdsMutArray :",addIdsMutArray)
print(addIdsMutArray.count)
print("oldSelectedIDStr :",oldSelectedIDStr)
//set not check for default
cell.accessoryType = .none
for i in (0..<addIdsMutArray.count)
{ var arrayCheck = NSArray()
arrayCheck = addIdsMutArray[i] as! NSArray
print(self.addIdsMutArray)
if arrayCheck.contains(oldSelectedIDStr)
{
print("Found")
cell.accessoryType = .checkmark
tableView.selectRow(at: indexPath, animated: false, scrollPosition: UITableViewScrollPosition.bottom)
break;
}
}
return cell
}
Consider this generic code that shows checkmark in every alternate cell. Edit the if condition to match yours.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "cell")
if indexPath.row % 2 == 0{
cell.accessoryType = .checkmark
}else{
cell.accessoryType = .none
}
cell.textLabel?.text = dummyData[indexPath.row]
return cell
}
This is how it looks:
The only difference is the lack of your call to selectRow(at: indexPath, animated: false, scrollPosition: UITableViewScrollPosition.bottom).
I'm not sure why this is done, but understand that at the end of the loop only the last matched cell will be 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
}
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