I am making an app that requires the user to tap on multiple cells in order to select them. when they tap on a cell, a .Checkmark accessory item will appear. For some reason though whenever I try and get to that VC the app crashes and I get the following error messages on line 8(if !checked[indexPath.row]):
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell: InstrumentTableCell! = tableView.dequeueReusableCellWithIdentifier(identifier) as? InstrumentTableCell
cell.configurateTheCell(recipies[indexPath.row])
if !checked[indexPath.row] {
cell.accessoryType = .None
} else if checked[indexPath.row] {
cell.accessoryType = .Checkmark
}
return cell
}
and this is my working checked method:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
tableView.deselectRowAtIndexPath(indexPath, animated: true)
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
if cell.accessoryType == .Checkmark {
cell.accessoryType = .None
checked[indexPath.row] = false
} else {
cell.accessoryType = .Checkmark
checked[indexPath.row] = true
}
}
}
Your problem is that you only store items in your checked array when tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) is called. However, that method is only called when you actually select a row.
tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) on the other hand is called each and every time you need to render a new table cell.
So when you in cellForRowAtIndexPath asks:
if !checked[indexPath.row]
then you can not be sure that checked actually contains anything. For instance the first time you start rendering your cells, your checked array does not contain any values and therefore it crashes when you ask it for a value on a position that it does not have a value for.
One solution could be to initialize your checked array to contain all false values. I'm guessing you have some model array called recipies so you could do something like:
for (index, _) in recipies.enumerate() {
checked.append(false)
}
Or as #AaronBrager suggests in the comments below (which is way prettier :))
checked = Array(count:recipies.count, repeatedValue:false)
that way you are sure that your checked array is properly initialized with the same number of elements as you have recipies.
Another option could be to let the individual elements in recipies know whether or not they are checked.
Hope this makes sense and helps you.
Related
I have this piece of code:
tableView.deselectRowAtIndexPath(indexPath, animated: true)
if let selected = self.lastSelected {
tableView.cellForRowAtIndexPath(selected)?.accessoryType = .None
}
tableView.cellForRowAtIndexPath(indexPath)?.accessoryType = UITableViewCellAccessoryType.Checkmark
self.lastSelected = indexPath
I allows me to place a checkmark on the cell that I select and removes the previous checkmark. The problem I am having is that if the list is large and the cell is out of view, the checkmark is not removed when a new cell is selected.
I have tried adding tableview.reloaddata() but that did nothing.
Thoughts?
cellForRowAtIndexPath returns nil for non-visible cells.
To get a radio button like effect, you need to do something like this:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
configureCell(cell, atIndexPath: indexPath)
return cell
}
func configureCell(cell: UITableViewCell, atIndexPath indexPath: NSIndexPath) {
if indexPath == selectedIndexPath {
cell.accessoryType = .Checkmark
} else {
cell.accessoryType = .None
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
selectedIndexPath = indexPath
// Note indexPathsForVisibleRows may be nil depending on your app's content.
// zip just joins two arrays together, into a single array of tuples.
zip(tableView.indexPathsForVisibleRows!, tableView.visibleCells).forEach({ indexPath, cell in
configureCell(cell, atIndexPath: indexPath)
})
}
I'm trying to show checkmark on tableview cell, but the checkmark appears only sometimes and it disappears when I scroll.
Below the code:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("vvxxx12", forIndexPath: indexPath)
// Configure the cell...
cell.textLabel?.text = self.dataArray[indexPath.row] as? String //in dataArray values are stored
if dataArray.containsObject(indexPath)
{
cell.accessoryType = .Checkmark
}
else {
cell.accessoryType = .None
}
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
if cell.accessoryType == .Checkmark {
cell.accessoryType = .None
} else {
cell.accessoryType = .Checkmark
}
}
}
Just do following changes in your code to maintain checkmark into tableview while you are scrolling tableview
Result :
Its work fine now, Any problem let me know i will definitely help you to out.Enjoy..
For Swift 3 following worked for me to
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
yourtableView.cellForRow(at: indexPath as IndexPath)?.accessoryType = .checkmark
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
yourtableView.cellForRow(at: indexPath as IndexPath)?.accessoryType = .none
}
cell.textLabel?.text = self.dataArray[indexPath.row] as? String
This suggests that dataArray contains strings.
if dataArray.containsObject(indexPath)
This suggests that dataArray contains index paths. Both of these should not be true. One array for data makes sense, and another one for selected rows or rows to be checked also makes sense, but not the same array for both.
What's probably happening is:
Row selected - cell is then updated to have the checkmark accessory
Table scrolled and cellForRowAtIndexPath is called - at no point will dataArray contain an index path, so the accessory is always cleared.
You need to be updating your model when a row is selected, to store the index path of the selected row, or update a selected flag on your model objects.
I have a TableViewController which implements the TableViewDelegate methods:
public override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! ParticipantesSearchTableViewCell
cell.viewModel?.active = true
}
public override func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! ParticipantesSearchTableViewCell
cell.selectedStateImageView.hidden = true
cell.viewModel?.active = false
}
Please note that I never remove nor set my tableView.editing to true. Also, my numberOfRowsInSection method never changes.
If I set my tableView to tableView.allowsSingleSelection = true I can select and deselect cells just fine and the above methods fire as aspected. However, when I scroll on my tableView and select a new cell the method didDeselectRowAtIndexPath crashes on let cell = tableView.cellForRowAtIndexPath(indexPath) as! ParticipantesSearchTableViewCell
At this point I debugged tableView.indexPathForSelectedRow and it returns nil.
Any idea what I'm implementing wrong here or if this is a bug? Because I try to use the same table with tableView.allowsMultipleSelection = true and I can select, deselect, and scroll just fine.
I have a table I use that shows timezone options. I use a checkmark to show which one is currently selected. When the table is created I checkmark the cell that is saved as the users timezone.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "cellData")
switch indexPath.row {
case 0: cell.textLabel?.text = "Eastern"
case 1: cell.textLabel?.text = "Central"
case 2: cell.textLabel?.text = "Mountain"
case 3: cell.textLabel?.text = "Mountain (No DST)"
case 4: cell.textLabel?.text = "Pacific"
default: cell.textLabel?.text = ""
}
cell.selectionStyle = UITableViewCellSelectionStyle.None
if(cell.textLabel?.text == keychain.get("timezone")) {
cell.accessoryType = UITableViewCellAccessoryType.Checkmark
}
return cell
}
Then I use these functions to change the checkmark when a user chooses a new timezone.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.cellForRowAtIndexPath(indexPath)!.accessoryType = UITableViewCellAccessoryType.Checkmark
}
override func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
tableView.cellForRowAtIndexPath(indexPath)!.accessoryType = UITableViewCellAccessoryType.None
}
However, when I preset the checkmark it is not removed when I choose a new timezone. It will only work if I first select it and then choose a new one. Is there a reason the original cell is not affected on deselect?
The reason your original cell isn't being deselected is that it's not selected in the first place. Enabling the checkmark accessory doesn't select the cell.
It's a bit of a pain to set up, but one way you could get this working is by storing a reference to the cell that needs to be selected, and when the view is going to appear, select it manually. Then, deselections will work.
Add a class variable to remember the cell that should be selected
var initiallySelectedPath: NSIndexPath?
Set the variable in your cellForRowAtIndexPath (personally, I'd do this setting elsewhere in the class due to how the actual cell selection is going to be performed, but this is good enough to demonstrate a solution.
...
if (cell.textLabel?.text == keychain.get("timezone")) {
cell.accessoryType = UITableViewCellAccessoryType.Checkmark
initiallySelectedPath = indexPath
}
...
Then, in your viewWillAppear
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if let indexPath = initiallySelectedPath {
// I force unwrap (sorry!) my tableView, you'll need to change this to however you reference yours
tableView!.selectRowAtIndexPath(indexPath, animated: false, scrollPosition: UITableViewScrollPosition.None)
}
}
Now your original cell should deselect with the first tap on a different cell.
How to show the data thats decks.status == true, and ignore those objects set to false?
data:
var decks: [DeckOfCards]
What I've got now:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as TableViewCell
if (thedeck.decks[indexPath.row].status == true) {
cell.label.text = "\(thedeck.decks[indexPath.row].card.name)"
}
}
You're going about this the wrong way. By the time you get to cellForRowAtIndexPath, you're already stated that a cell should be dequeued for this index path (and therefore at this index in your data array). The right place to be doing this filtering is in your data source.
For example, in addition to your decks array, you could make a computed property (filteredDecks) that gets its value by filtering the decks array.
var decks = [DeckOfCards]
var filteredDecks: [DeckOfCards] {
return decks.filter { $0.status }
}
You can then use this property as the data source for your table view.
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredDecks.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
cell.label.text = "\(filteredDecks[indexPath.row].card.name)"
return cell
}
Now since this solution computes the filteredDecks array on each property access, it may not be the best approach if decks is a large array, or if you're reloading the table view frequently. If this is the case, and it's possible to do so, you should prefer filtering the decks array ahead of time using the same method shown in the computed property above.
You could use the filter function on the decks
let filteredDecks = decks.filter({$0.status})
Filter your array as
self.decks = self.decks.filter {
(d: DeckOfCards) -> Bool in
return d.status == true
}
Now your array will have the filtered values. you dont need to check for status inside cellForRowAtIndexPath function.