Reputation: 1024
I have a UITableView in Swift for iOS that is supposed to allow only one cell to be selected at a time. I have it set up so that taps toggle between selected and not selected, and a long press sets it to permanently selected, which means it starts out selected by default when the view loads. I was having difficulty with keeping the cells' label colored when the cell went outside the bounds of the visible part of the scrollview and then reentered it, so I used my data model to keep track of what cells were selected and permanently selected. By doing so, I have bypassed Apple's way of making a tableview allow only single cell selection by selecting "Single Selection" in interface builder. Now I am trying to build out my own functionality to change the color of all cells visible on the screen when another cell is tapped. I am currently attempting to do this by looping through each cell when it is tapped and calling a method on that custom cell that will change the text color. The problem is, it isn't working. If I move the cells out of the visible scrolling area and then back in, the cells will be colored properly, but if I select one cell, and then another, the first cell won't change color while it is visible.
Here is my code. I have removed some code that was irrelevant, so if there is something else you would like to see, let me know.
import Foundation
import UIKit
class AddDataViewController : UIViewController {
@IBOutlet weak var locationTableView: UITableView!
let locationTableViewController = LocationTableViewController()
override func viewWillAppear(animated: Bool) {
// Set the data source and delgate for the tables
self.locationTableView.delegate = self.locationTableViewController
self.locationTableView.dataSource = self.locationTableViewController
// Refresh the tables
self.locationTableView.reloadData()
// Add the table view controllers to this view
self.addChildViewController(locationTableViewController)
}
override func viewDidLoad(){
super.viewDidLoad()
// Create a tap gesture recognizer for dismissing the keyboard
let tapRecognizer = UITapGestureRecognizer()
// Set the action of the tap gesture recognizer
tapRecognizer.addTarget(self, action: "dismissKeyboard")
// Update cancels touches in view to allow touches in tableview to be detected
tapRecognizer.cancelsTouchesInView = false
// Add the tap gesture recognizer to the view
self.view.addGestureRecognizer(tapRecognizer)
}
}
class LocationTableViewController : UITableViewController, UITableViewDelegate, UITableViewDataSource {
var locationSelected : String = ""
func colorAllCells() {
for section in 0 ... self.tableView.numberOfSections() - 1 {
for row in 0 ... self.tableView.numberOfRowsInSection(section) - 1 {
if row != 0 {
let indexPath = NSIndexPath(forRow: row, inSection: section)
let cell = self.tableView.cellForRowAtIndexPath(indexPath) as! PlayerLocationCell
cell.indexPath = indexPath.row - 1
cell.setCellColor()
}
}
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row != 0 {
// Color all of the cells
self.colorAllCells()
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = PlayerLocationCell(style: UITableViewCellStyle.Value2, reuseIdentifier: "addDataCell")
cell.selectionStyle = UITableViewCellSelectionStyle.None
if indexPath.row != 0 {
cell.textLabel!.text = pokerLibrary.currentLog.locationList[indexPath.row - 1].name
cell.indexPath = indexPath.row - 1
cell.setCellColor()
}
return cell
}
}
class PlayerLocationCell : UITableViewCell {
let selectedColor = UIColor.greenColor()
let deselectedColor = UIColor.grayColor()
let permanentlySelectedColor = UIColor.blueColor()
var shouldHighlightText : Bool = true
var indexPath : Int = 0
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: "cellPressed")
longPressRecognizer.minimumPressDuration = 1.0
longPressRecognizer.cancelsTouchesInView = false
self.addGestureRecognizer(longPressRecognizer)
let tapRecognizer = UITapGestureRecognizer(target: self, action: "cellTapped")
tapRecognizer.cancelsTouchesInView = false
self.addGestureRecognizer(tapRecognizer)
}
func setCellColor() {
if shouldHighlightText {
if self.isPermanentlySelected() {
self.textLabel!.textColor = self.permanentlySelectedColor
}
else if self.isCurrentlySelected() {
self.textLabel!.textColor = self.selectedColor
}
else {
self.textLabel!.textColor = self.deselectedColor
}
}
}
func cellTapped() {
if shouldHighlightText {
self.setPermanentlySelected(false)
if self.isCurrentlySelected() {
self.setSelectedProperty(false)
self.setCellColor()
}
else {
self.setSelectedProperty(true)
self.setCellColor()
}
}
}
func cellPressed() {
if self.shouldHighlightText {
if !self.isPermanentlySelected() {
self.setPermanentlySelected(true)
self.setCellColor()
}
}
}
func setPermanentlySelected(b : Bool) {
if b {
// Set all other locations to false
for i in 0 ... pokerLibrary.currentLog.locationList.count - 1 {
pokerLibrary.currentLog.locationList[i].selected = false
pokerLibrary.currentLog.locationList[i].permanentlySelected = false
}
}
pokerLibrary.currentLog.locationList[self.indexPath].permanentlySelected = b
}
func isPermanentlySelected() -> Bool {
return pokerLibrary.currentLog.locationList[self.indexPath].permanentlySelected
}
func setSelectedProperty(b : Bool) {
if b {
// Set all other locations to false
for i in 0 ... pokerLibrary.currentLog.locationList.count - 1 {
pokerLibrary.currentLog.locationList[i].selected = false
pokerLibrary.currentLog.locationList[i].permanentlySelected = false
}
}
pokerLibrary.currentLog.locationList[self.indexPath].selected = b
}
func isCurrentlySelected() -> Bool {
return pokerLibrary.currentLog.locationList[self.indexPath].selected
}
}
As you can see, I am currently calling the LocationTableViewController's colorAllCells method from inside the didSelectRowAtIndexPath method. The colorAllCells method loops through all cells and calls each cell's setCellColor method, which will determine what the cell color should be and set the text color. I have verified each of the cell's is having its setCellColor method called from the loop, and it is correctly determining which color to set. The color just won't change. I have also tried calling self.tableView.reloadData() after I have looped through each cell and set the color, but that didn't work either.
I would appreciate any help with this issue. If you need me to clarify anything, let me know. Thanks!
Upvotes: 1
Views: 4208
Reputation: 1024
I finally found a solution. If I remove the reference to self before calling tableView.cellForRowAtIndexPath, and I move the code from the colorAllCells method into the didSelectRowAtIndexPath method, then it works.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row != 0 {
// Color all of the cells
for section in 0 ... self.tableView.numberOfSections() - 1 {
for row in 0 ... self.tableView.numberOfRowsInSection(section) - 1 {
if row != 0 {
let indexPath = NSIndexPath(forRow: row, inSection: section)
if let cell = tableView.cellForRowAtIndexPath(indexPath) as? PlayerLocationCell {
cell.setCellColor()
}
}
}
}
}
}
This solution works. However, I don't understand why it works when I place the code inside the didSelectRowAtIndexPath method, but if I take that code out and put it in its own function and then call that function, it doesn't work. Does anyone have any idea why that is?
Upvotes: 2
Reputation: 7252
Try calling your colorAllCells()
function on the main thread.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row != 0 {
// Color all of the cells
dispatch_async(dispatch_get_main_queue(),{
self.colorAllCells()
})
}
}
This will however block your UI.
Upvotes: 0
Reputation: 2782
You need some way to manage the state of cell selection in your view controller. I would do it with a Set, like this:
class LocationTableViewController: UITableViewController {
var selectedIndexPaths = Set<NSIndexPath>()
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.selectedIndexPaths.insert( indexPath )
if let cell = tableView.cellForRowAtIndexPath( indexPath ) as? PlayerLocationCell {
// Change color of the existing cell
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCellWithIdentifier( "addDataCell" )as? PlayerLocationCell {
if self.selectedIndexPaths.contains( indexPath ) {
// Change color of the cell just dequeued/created
}
return cell
}
fatalError( "Could not dequeue cell of type 'PlayerLocationCell'" )
}
}
Also, you should use dequeueReusableCellWithIdentifier:
to reuse table view cells and not create new ones in your tableView:cellForRowAtIndexPath
method.
Upvotes: 1