How to avoid getting highlighted two cells in a tableview? - ios

Hi in my application i am using one tableview and each cell contains one collection view. When i select one cell in tableview that cell should highlight in light gray and previous should change to black this i am doing as like below.
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
collectionView.backgroundColor=[UIColor clearColor];
collectionViewTagValue=collectionView.tag;
nextFocusedIndex=indexPath;
selectedCollectionCellTag=collectionView.tag;
// Get previously selected collectionview tag value(Because we have many collectionvies in table)
NSIndexPath *previousPath=[NSIndexPath indexPathForRow:self->appdelegateObject.guideSelectionTag inSection:0];
// Get Tableview cell based on tag value - that tag value will be a tableview row number
DetailTableViewCell *cell = (DetailTableViewCell*)[self->guideDetailsTable cellForRowAtIndexPath:previousPath];
// Get indexpath of a selected cell in collection view
NSIndexPath *previouslySelectedCell=[NSIndexPath indexPathForRow:self->appdelegateObject.guideSelectionIndex inSection:0];
// Get collectionview cell based on indexpath value
MainCollectionViewCell *previousCell = (MainCollectionViewCell*)[cell.collection cellForItemAtIndexPath:previouslySelectedCell];
// Change celllabel background color to normal not highlight
previousCell.cellLable.backgroundColor=[UIColor colorWithRed:0.073 green:0.073 blue:0.073 alpha:1.0];
MainCollectionViewCell *currentCell = (MainCollectionViewCell*)[collectionView cellForItemAtIndexPath:indexPath];
currentCell.cellLable.backgroundColor=[UIColor lightGrayColor];
}
using above code sometimes both previous and current cells are getting highlighted with light gray.Even if i did this change in dispatch_main_queue also same behaviour i observed. Can any one please suggest best approach for this feature.
Note : For first cell selection i am getting collectionview.tag as -1.

Implemented following method to fix your issue:
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let yourCell = tableView.cellForRow(at: indexPath)
yourCell?.backgroundColor = UIColor.black
}
This code in Swift, you need to convert it to Objective-C.
This method call for previous cell which you have selected, it being deselect.

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
cell?.contentView.backgroundColor = UIColor.red
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
cell?.contentView.backgroundColor = UIColor.lightGray
}

//add condition in tableView delegate in didselect or diddeSelect, check index.section..
//for selected cell
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = (UITableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
// check indexPath.Section here
if (!indexPath.section) == 0{
cell.contentView.backgroundColor = [UIColor yellowColor];
}
}
//for other cells
-(void) tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath{
// check indexPath.Section here
if (!indexPath.section) == 0{
cell.contentView.backgroundColor = [UIColor whiteColor];
}
}
// hope its works for you!

Related

change color of `UITableViewCell` once shown in screen

I want to change the color of the UITableViewCell once it is shown on the screen to reflect something like the read/unread behaviour. For example:
User will be seeing a list which will be firstly of lets say red color then after some time the color should be changed to clearColor. When user scrolls down the new cell should again be red for a limited time and then change to clearColor.
You can try
Swift
func tableView(_ tableView: UITableView,
willDisplay cell: UITableViewCell,
forRowAt indexPath: IndexPath) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
if let cel = tableView.cellForRow(at: indexPath) { // not used cell directly because it may be nil at this moment
cel.backgroundColor = UIColor.red // save this state in model to avoid dequeuing problems
}
}
}
Objective-C
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell
forRowAtIndexPath:(NSIndexPath *)indexPath {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
UITableViewCell*cel = [tableView cellForRowAtIndexPath:indexPath];
cel.backgroundColor = [UIColor YourColor]; // won't ser it cel is nil
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
UITableViewCell*cel = [tableView cellForRowAtIndexPath:indexPath];
cel.backgroundColor = [UIColor clearColor]; // won't ser it cel is nil
});
});
}
You need to save the state of each row/cell in your datasource. Since cells are reused, you shouldn't store this data in the cell itself. I presume that you have some kind of array as your tableview's datasource? So the easiest solution would be to add a property for each item in your datasource to store if the cell for a particular item has been displayed or not.
Is the idea to give each cell a default color, and the next time this cell is scrolled into view the color should be changed to indicate that it has been viewed previously?
There are different approaches for it. One of the approach will be creating an array of Boolean with all false, whose size will be equal to number of rows.
var array = [Bool](repeating: false, count: self.tableView.numberOfRows(inSection: 0))
// assuming only 1 section is available
Inside tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath), you have to do the following:
array[indexPath.row] = true
Inside tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath), you can check the boolean value and set color:
if array[indexPath.row] {
cell.backgroundColor = .red
} else {
cell.backgroundColor = .blue
}

dequeueReusableCellWithIdentifier:forIndexPath: VS dequeueReusableCellWithIdentifier:

I have read this question and think that I understand the difference between the two methods until I find a strange example:
Set table view cell's style be Basic, Identifier be Cell in Storyboard, code as below:
import UIKit
class TableViewController: UITableViewController {
var items: [String]!
override func viewDidLoad() {
super.viewDidLoad()
items = ["first", "second", "third"]
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// either works fine
let cell = tableView.dequeueReusableCellWithIdentifier("Cell")! // let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
cell.textLabel?.text = items[indexPath.row]
return cell
}
}
Very simple, but when I change the tableView:cellForRowAtIndexPath: method to 1, 2, 3, 4 cases respectively:
Case 1:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
cell.textLabel?.text = items[indexPath.row]
return cell
}
Case 2:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
cell = tableView.dequeueReusableCellWithIdentifier("Cell")!
cell.textLabel?.text = items[indexPath.row]
return cell
}
Case 3:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell")!
cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
cell.textLabel?.text = items[indexPath.row]
return cell
}
Case 4:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell")!
cell = tableView.dequeueReusableCellWithIdentifier("Cell")!
cell.textLabel?.text = items[indexPath.row]
return cell
}
Case 1, 2 (doesn't work):
Case 3, 4 (works fine):
How to explain? I think it really helps to understand these two methods from another perspective, any opinion is welcome.
In each case, you are dequeueing two cells for each row. In cases 1 and 2, you call the ("Cell", forIndexPath: indexPath) version first. In this case the table view ends up with two cells for each row, one completely overlapping and obscuring the other. You can see this in the view inspector since you can amend the angle of view to see behind:
(I amended the cellForRowAtIndexPath code like this:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("plainCell", forIndexPath: indexPath)
cell.textLabel!.text = "First cell for row \(indexPath.row)"
cell = tableView.dequeueReusableCellWithIdentifier("plainCell", forIndexPath: indexPath)
cell.textLabel!.text = "Second cell for row \(indexPath.row)"
print("Cell being returned is \(cell)")
return cell
}
to given different text labels to each cell.) In cases 3 and 4, where you call the ("Cell") version first, the table view has only one cell for each row.
Why the different behaviour? If you create a custom subclass of UITableViewCell and use that in your storyboard, you can then override various methods and add print() statements to see what's happening. In particular, awakeFromNib, didMoveToSuperView, and deinit. What transpires is that in cases 1 and 2, the first cell is created (awakeFromNib) and immediately added (didMoveToSuperView) to a superview, presumably the table view or one of its subviews. In cases 3 and 4, the first cell is created but is not added to a superview. Instead some time later, the cell is deallocated (deinit).
(Note that if the second cell is dequeued using the ("Cell", forIndexPath: indexPath) version, it too is added immediately to a superview. However, if the second cell is dequeued using the ("Cell") version, it is only added to a superview after the cellForRowAtIndexPath method has returned.)
So the key difference is that the ("Cell", forIndexPath: indexPath) version results in the cell being added immediately to the table view, before even the cellForRowAtIndexPath has completed. This is hinted at in the question/answer to which you refer, since it indicates that the dequeued cell will be correctly sized.
Once added to the superview, the first cell cannot be deallocated since there is still a strong reference to it from its superview. If the cells are dequeued with the ("Cell") version, they are not added to the superview, there is consequently no strong reference to them once the cell variable is reassigned, and they are consequently deallocated.
Hope all that makes sense.
dequeueReusableCellWithIdentifier: doesn't give you guarantees: cells could be nil, so you have to check if your cell is nil and handle it properly or your app will crash.
dequeueReusableCellWithIdentifier:forIndexPath:, on the other hand, does check this for you (it always return a cell).
For your particular case (Swift), this means you can safely force-unwrap the cell with dequeueReusableCellWithIdentifier:forIndexPath:, while you'll have to use the if let syntax with the second one.
Example codes (in Objective-C, I don't use Swift)
dequeueReusableCellWithIdentifier:forIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" atIndexPath:indexPath];
// Here we know the cell is not nil (....atIndexPath: ensures it)
cell.textLabel.text = items[indexPath.row];
return cell;
}
dequeueReusableCellWithIdentifier:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
// You asked for a cell, but you don't know if it is nil or not
// In Swift, here the cell should be a conditional
// First, check if the cell is nil
if ( cell == nil ) {
// Cell is nil. To avoid crashes, we instantiate an actual cell
// With Swift conditional should be something similar
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"];
}
// Here you're sure the cell is not nil
// If condicional, you probably will write cell?.textLabel?.text = items[indexPath.row];
cell.textLabel.text = items[indexPath.row];
// Finally, you return the cell which you're 100% sure it's not nil
return cell;
}

How would I programmatically change the background color of a cell

So my actual question is a bit more robust. I'm curious whether it's possible to change the background color of a cell programmatically, however it would be based on whether the cell was first, or second, etc.
I'm not sure this is even possible, but what I'm trying to achieve is a gradient effect utilizing the cells as they increase in number.
if (indexPath.row % 2)
{
cell.contentView.backgroundColor = UIColor.redColor()
}
else
{
cell.contentView.backgroundColor = UIColor.blueColor()
}
I've tried something like this to at least have the color alternate to see if I can figure out what to do next, but this has failed. Alternating doesn't seem to be the right option, but it might be the right way to start programming what I want to happen.
A tableView sends this message to its delegate just before it uses cell to draw a row, thereby permitting the delegate to customize the cell object before it is displayed. for more info
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row % 2 == 0 {
cell.backgroundColor = UIColor.redColor()
}
else {
cell.backgroundColor = UIColor.blueColor()
}
return cell
}
Yes, it is possible to change the background color of a cell programmatically, based on whether the cell was first, or second, etc. In cellForRowAtIndexPath delegate method, check if row is odd then put the different background color, if it is even then put the different color. By using below code, you will get alternate colors of rows.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CommentTableCellIdentifier"];
cell.textLabel.text = #"Your text goes here";
if(indexPath.row % 2 == 0)
cell.backgroundColor = [UIColor redColor];
else
cell.backgroundColor = [UIColor greenColor];
return cell;
}
SWIFT:-
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell? {
// Configure the cell...
let cellId: NSString = "Cell"
var cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(cellId) as UITableViewCell
cell.textLabel.text = "Your text goes here"
if(indexPath.row % 2 == 0)
{
cell.backgroundColor = UIColor.redColor()
}
else
{
cell.backgroundColor = UIColor.greenColor()
}
return cell
}
Swift 4.2
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.row % 2 == 0 {
cell.backgroundColor = UIColor.red
//OR
productCell.contentView.backgroundColor = UIColor.red
}
else {
cell.backgroundColor = UIColor.blue
}
return cell
}

UITableView Static cells - Selection color not working

I have a problem regarding my static cells. This is my tableview structure:
http://i.stack.imgur.com/1gq1y.png (I cant post images)
The cell background color is Gray and the contentView background color is ClearColor. The cell is not using a custom UITableViewCell subclass.
I set the Selection to Blue in the storyboad. However, when I run it, the output is Gray
I tried doing it manually in the didSelectRowAtIndexPath with this code:
currentCell.contentView.backgroundColor = self.view.tintColor;
but it turns out like this: http://i.stack.imgur.com/Zfatl.png
The Label in the Cell doesn't change to white and the Accessory background isn't changing. Please help. Thank you so much!
I am doing this selection this way. I have static header cells as 0-index rows in tableview's sections
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Did select row \(indexPath.row) in section \(indexPath.section)")
if indexPath.row > 0 {
let cell = tableView.cellForRow(at: indexPath)
cell?.selectionStyle = .gray
}
}
Add the following code to your TableView: cellForRowAtIndexPath method.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Write code for create your cell and properties regarding that cell
//add this line
cell.selectionStyle = UITableViewCellSelectionStyleDefault;
UIView *bgColorView = [[UIView alloc] init];
bgColorView.backgroundColor = [UIColor blueColor];
[cell setSelectedBackgroundView:bgColorView];
return cell;
}
add cell selection stylecolor as stylenone in cellForRowAtIndexPath method.
cell.selectionStyle = UITableViewCellSelectionStyleNone;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Write code for create your cell and properties regarding that cell
//add this line
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
Note that in iOS 7, using
[cell setSelectionStyle:UITableViewCellSelectionStyleBlue];
will not work as expected, because in iOS 7 this is now gray, even if
you pass the constant above. See:
https://developer.apple.com/library/ios/documentation/uikit/reference/UITableViewCell_Class/Reference/Reference.html#//apple_ref/c/tdef/UITableViewCellSelectionStyle
Vern Jensen's answer:
https://stackoverflow.com/a/19257253/2575115

UITableViewController changing one reusable cell affects other cells

I have a very simple UITableViewController subclass designed to show users the characters in the alphabet in cells. When the user presses on a cell, it is to set its accessory type to a checkmark.
#import "MTGTableViewController.h"
#interface MTGTableViewController ()
#property (nonatomic, strong) NSArray *data;
#end
#implementation MTGTableViewController
- (void)viewDidLoad
{
[super viewDidLoad];
_data = #[#"A", #"B", #"C", #"D", #"E", #"F", #"G", #"H", #"I", #"J", #"K", #"L", #"M", #"N", #"O", #"P", #"Q", #"R", #"S", #"T", #"U", #"V", #"W", #"X", #"Y", #"Z"];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return _data.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"reuseIdentifier" forIndexPath:indexPath];
// Configure the cell...
cell.textLabel.text = _data[indexPath.row];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
#end
The table view works fine. I have my reuseIdentifier property set in the storyboard on my prototype cells, and it all looks good before I start selecting cells.
The problem: When I select any cell, say the "A" cell, other not-yet-visible cells are also given checkmarks when i scroll down to them. Even worse, when I scroll up and down, sometimes the checkmark on cell "A" is removed and given to cell "B".
This is because of the way table views reuse cells. You need to make sure to clear the accessory item after you call dequeueReusableCellWithIdentifier. eg:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"reuseIdentifier" forIndexPath:indexPath];
// Configure the cell...
cell.textLabel.text = _data[indexPath.row];
cell.accessoryType = UITableViewCellAccessoryNone;
return cell;
}
Although--you're going to have a different problem after you make this change. The cell is going to "forget" that it is selected because of this code. So you'll need to actually change that line where the accessoryType is set to check and see if the cell is selected or not.
I had this problem a little while ago as well, you need to add another array that keeps track of the which cell are marked. Just make an array of NSIndexPaths that are the ones marked.
Sort of like:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if ([arrayOfMarked containsObject:indexPath]) {
cell.accessoryType = UITableViewCellAccessoryNone;
} else {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
}
For Swift 3:
A simplified version I discovered was to use swifts ".indexPathsForSelectedRows" just within the "cellForRowAt" function.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableViewCell
//To keep from reusing checkmark in reusableCell
if let selectedIndexPaths = tableView.indexPathsForSelectedRows {
if selectedIndexPaths.contains(indexPath) {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
}
cell.contactsOutlet.text = namesOrganized[indexPath.section].names[indexPath.row]
return cell
}
Then in didSelect and didDeselect:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
cell?.accessoryType = UITableViewCellAccessoryType.checkmark
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
cell?.accessoryType = UITableViewCellAccessoryType.none
}
on didselectRowAtIndex path, append the index path into an array of indexpath
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if selectedIndexPaths.contains(indexPath) {
let indexOfIndexPath = selectedIndexPaths.indexOf(indexPath)!
selectedIndexPaths.removeAtIndex(indexOfIndexPath)
} else {
selectedIndexPaths += [indexPath]
}
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
and then on the cellForRowAtIndexPath use the selectedIndexpaths Array to check if the cell is selected or not
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let mycell = tableView.dequeueReusableCellWithIdentifier("yourCell", forIndexPath: indexPath) as! YourCustomCell
.
.
.
if selectedIndexPaths.contains(indexPath){
// do what you want to do if the cell is selected
} else {
// do what you want to do if the cell is not selected
}

Resources