UITableViewCell subview disappears when cell is selected

IphoneIosUitableview

Iphone Problem Overview


I'm implementing a color-chooser table view where the user can select amongst, say, 10 colors (depends on the product). The user can also select other options (like hard drive capacity, ...).

All color options are inside their own tableview section.

I want to display a little square on the left of the textLabel showing the actual color.

Right now I'm adding a simple square UIView, give it the correct background color, like this :

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:RMProductAttributesCellID];
	if (cell == nil) {
		cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:RMProductAttributesCellID] autorelease];
		cell.indentationWidth = 44 - 8;
		
		UIView *colorThumb = [[[UIView alloc] initWithFrame:CGRectMake(8, 8, 28, 28)] autorelease];
		colorThumb.tag = RMProductAttributesCellColorThumbTag;
		colorThumb.hidden = YES;
		[cell.contentView addSubview:colorThumb];
	}
	
	RMProductAttribute *attr = (RMProductAttribute *)[_product.attributes objectAtIndex:indexPath.section];
	RMProductAttributeValue *value = (RMProductAttributeValue *)[attr.values objectAtIndex:indexPath.row];
	cell.textLabel.text = value.name;
	cell.textLabel.backgroundColor = [UIColor clearColor];
	
	UIView *colorThumb = [cell viewWithTag:RMProductAttributesCellColorThumbTag];
	colorThumb.hidden = !attr.isColor;
	cell.indentationLevel = (attr.isColor ? 1 : 0);
	
	if (attr.isColor) {
		colorThumb.layer.cornerRadius = 6.0;
		colorThumb.backgroundColor = value.color;
	}
	
	[self updateCell:cell atIndexPath:indexPath];
	
	return cell;
}

This displays fine without problems.

My only problem is that when I select a "color" row, during the fade-to-blue selection animation, my custom UIView (colorThumb) is hidden. It gets visible again just after the selection/deselection animation ended, but this produces an ugly artifact.

What should I do to correct this? Don't I insert the subview at the right place?

(There's nothing special in the didSelectRowAtIndexPath, I just change the cell's accessory to a checkbox or nothing, and deselect the current indexPath).

Iphone Solutions


Solution 1 - Iphone

UITableViewCell changes the background color of all sub views when cell is selected or highlighted ,You can Solve this problem by overriding Tableview cell's setSelected:animated and setHighlighted:animated and resetting view background color.

In Objective C :

- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
   UIColor *color = self.yourView.backgroundColor;        
   [super setSelected:selected animated:animated];
    
    if (selected){
        self.yourView.backgroundColor = color;
    }
}

-(void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated{
    UIColor *color = self.yourView.backgroundColor;        
    [super setHighlighted:highlighted animated:animated];
    
    if (highlighted){
        self.yourView.backgroundColor = color;
    }
}

In Swift 3.1 :

override func setSelected(_ selected: Bool, animated: Bool) {
    let color = yourView.backgroundColor         
    super.setSelected(selected, animated: animated)
  
    if selected {
        yourView.backgroundColor = color
    }
}

override func setHighlighted(_ highlighted: Bool, animated: Bool) {
    let color = yourView.backgroundColor
    super.setHighlighted(highlighted, animated: animated)
    
    if highlighted {
        yourView.backgroundColor = color
    }
}

Solution 2 - Iphone

It's because table view cell automatically changes background color of all views inside content view for highlighted state. You may consider subclassing UIView to draw your color or using UIImageView with custom 1x1 px stretched image.

Solution 3 - Iphone

Found a pretty elegant solution instead of messing with the tableViewCell selection/highlighting methods. You can create a subclass of UIView that ignores setting its background color to clear color.

Swift 3/4:

class NeverClearView: UIView {
    override var backgroundColor: UIColor? {
        didSet {
            if backgroundColor != nil && backgroundColor!.cgColor.alpha == 0 {
                backgroundColor = oldValue
            }
        }
    }
}

Swift 2:

class NeverClearView: UIView {
    override var backgroundColor: UIColor? {
        didSet {
            if CGColorGetAlpha(backgroundColor!.CGColor) != 0 {
                backgroundColor = oldValue
            }
        }
    }
}

Obj-C version:

@interface NeverClearView : UIView

@end

@implementation NeverClearView

- (void)setBackgroundColor:(UIColor *)backgroundColor {
    if (CGColorGetAlpha(backgroundColor.CGColor) != 0) {
        [super setBackgroundColor:backgroundColor];
    }
}

@end

Solution 4 - Iphone

For Swift 2.2 this works

cell.selectionStyle = UITableViewCellSelectionStyle.None

and reason is explained by @Andriy

> It's because table view cell automatically changes background color of > all views inside content view for highlighted state.

Solution 5 - Iphone

Another way to manage the problem is to fill the view with core-graphics gradient, like:

CAGradientLayer* gr = [CAGradientLayer layer];
gr.frame = mySubview.frame;
gr.colors = [NSArray arrayWithObjects:
                     (id)[[UIColor colorWithRed:0 green:0 blue:0 alpha:.5] CGColor]
                     ,(id)[[UIColor colorWithRed:0 green:0 blue:0 alpha:.5] CGColor]
                     , nil];
        
gr.locations = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0],[NSNumber numberWithFloat:1],nil];
        
[mySubview.layer insertSublayer:gr atIndex:0];

Solution 6 - Iphone

Inspired by Yatheesha B L's answer I created a UITableViewCell category/extension that allows you to turn on and off this transparency "feature".

Swift

let cell = <Initialize Cell>
cell.keepSubviewBackground = true  // Turn  transparency "feature" off
cell.keepSubviewBackground = false // Leave transparency "feature" on

Objective-C

UITableViewCell* cell = <Initialize Cell>
cell.keepSubviewBackground = YES;  // Turn  transparency "feature" off
cell.keepSubviewBackground = NO;   // Leave transparency "feature" on

KeepBackgroundCell is CocoaPods compatible. You can find it on GitHub

Solution 7 - Iphone

You can cell.selectionStyle = UITableViewCellSelectionStyleNone;, then set the backgroundColor at - (void)tableView:(UITableView *)tableView didHighlightRowAtIndexPath:(NSIndexPath *)indexPath

Solution 8 - Iphone

Inspired by Yatheesha B L's answer.

If you call super.setSelected(selected, animated:animated), it will clear all background color you set. So, we will not call super method.

In Swift :

override func setSelected(selected: Bool, animated: Bool) {    
    if(selected)  {
        contentView.backgroundColor = UIColor.red 
    } else {
        contentView.backgroundColor = UIColor.white
    }
}

override func setHighlighted(highlighted: Bool, animated: Bool) {
    if(highlighted) {
        contentView.backgroundColor = UIColor.red 
    } else {
        contentView.backgroundColor = UIColor.white
    }
}

Solution 9 - Iphone

For may case, This is the Septs to avoid getting the gray color for all items in the cell (in case you are using custom table view cell):

  1. Set the selectionStyle to .none  selectionStyle = .none

  2. Override this method. > func setHighlighted(_ highlighted: Bool, animated: Bool)

  3. Call the super, to get the benefit of super setup. > super.setHighlighted(highlighted, animated: animated)

  4. Do what ever highlighting logic you want.

    override func setHighlighted(_ highlighted: Bool, animated: Bool) {
          super.setHighlighted(highlighted, animated: animated)
          // Your Highlighting Logic goes here...
    }
    

Solution 10 - Iphone

UITableViewCell changes the backgroundColor of all subviews on selection for some reason.

This might help:

DVColorLockView

Use something like that to stop UITableView from changing your view color during selection.

Solution 11 - Iphone

Draw the view instead of setting background colour

import UIKit

class CustomView: UIView {
    
    var fillColor:UIColor!
    
    convenience init(fillColor:UIColor!) {
        self.init()
        self.fillColor = fillColor
    }
    
    override func drawRect(rect: CGRect) {
        if let fillColor = fillColor {
            let context = UIGraphicsGetCurrentContext()
            CGContextSetFillColorWithColor(context, fillColor.CGColor);
            CGContextFillRect (context, self.bounds);
            
        }
    }
    

}

Solution 12 - Iphone

SIMPLEST solution without bugs with animation (as in the top rated answer) and without subclassing and drawing - set layer's border color instead of backgroundColor and set very big border width.

colorThumb.layer.cornerRadius = 6
colorThumb.layer.borderWidth = colorThumb.frame.width
colorThumb.layer.borderColor = value.color

Solution 13 - Iphone

Try Following code:

-(void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated
{     
[super setHighlighted:highlighted animated:animated];
//Set your View's Color here.
}

Solution 14 - Iphone

Don't forget to override setSelected as well as setHighlighted

override func setHighlighted(highlighted: Bool, animated: Bool) {
    
    super.setHighlighted(highlighted, animated: animated)
    someView.backgroundColor = .myColour()
}

override func setSelected(selected: Bool, animated: Bool) {
    
    super.setSelected(selected, animated: animated)
    someView.backgroundColor = .myColour()
}

Solution 15 - Iphone

This is similar to Pavel Gurov's answer, but more flexible in that it allows any color to be permanent.

class PermanentBackgroundColorView: UIView {
    var permanentBackgroundColor: UIColor? {
        didSet {
            backgroundColor = permanentBackgroundColor
        }
    }
    
    override var backgroundColor: UIColor? {
        didSet {
            if backgroundColor != permanentBackgroundColor {
                backgroundColor = permanentBackgroundColor
            }
        }
    }
}

Solution 16 - Iphone

I wanted to keep the default selection behavior except for one cell subview that I wanted to ignore the automatic background color change. But I also needed to be able to change the background color at other times.

The solution I came up with was to subclass UIView so it ignores setting the background color normally and add a separate function to bypass the protection.

Swift 4

class MyLockableColorView: UIView {
    func backgroundColorOverride(_ color: UIColor?) {
            super.backgroundColor = color
    }
    
    override var backgroundColor: UIColor? {
        set {
            return
        }
        get {
            return super.backgroundColor
        }
    }
}

Solution 17 - Iphone

here is my solution,use contentView to show selectionColor,it's work perfectly

#import "BaseCell.h"

@interface BaseCell ()
@property (nonatomic, strong) UIColor *color_normal;
@property (nonatomic, assign) BOOL needShowSelection;
@end


@implementation BaseCell
@synthesize color_customSelection;
@synthesize color_normal;
@synthesize needShowSelection;

- (void)awakeFromNib
{
    [super awakeFromNib];
    [self setup];
}

- (void)setup
{
    //save normal contentView.backgroundColor
    self.color_normal = self.backgroundColor;
    if (self.color_normal == nil) {
        self.color_normal = [UIColor colorWithRGBHex:0xfafafa];
    }
    self.color_customSelection = [UIColor colorWithRGBHex:0xF1F1F1];
    self.accessoryView.backgroundColor = [UIColor clearColor];
    if (self.selectionStyle == UITableViewCellSelectionStyleNone) {
        needShowSelection = NO;
    }
    else {
        //cancel the default selection
        needShowSelection = YES;
        self.selectionStyle = UITableViewCellSelectionStyleNone;
    }
}

/*
 solution is here
 */
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesBegan:touches withEvent:event];
    if (needShowSelection) {
        self.contentView.backgroundColor = self.backgroundColor = color_customSelection;
    }
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesCancelled:touches withEvent:event];
    if (needShowSelection) {
        self.contentView.backgroundColor = self.backgroundColor = color_normal;
    }
}

- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
    [super setSelected:selected animated:animated];
    if (needShowSelection) {
        UIColor *color  = selected ? color_customSelection:color_normal;
        self.contentView.backgroundColor = self.backgroundColor = color;
    }
}

Solution 18 - Iphone

Place this code in your subclass of UITableViewCell

Swift 3 syntax

override func setSelected(_ selected: Bool, animated: Bool) {
    super.setSelected(selected, animated: animated)
    
    if(selected) {
        lockerSmall.backgroundColor = UIColor.init(red: 233/255, green: 106/255, blue: 49/255, alpha: 1.0)
    }
}


override func setHighlighted(_ highlighted: Bool, animated: Bool) {
    super.setHighlighted(highlighted, animated: animated)
    
    if(highlighted) {
        lockerSmall.backgroundColor = UIColor.init(red: 233/255, green: 106/255, blue: 49/255, alpha: 1.0)
    }
}

Solution 19 - Iphone

Adding another solution if you're using storyboards. Create a subclass of UIView that does not allow the backgroundColor to be set after it is initially set.

@interface ConstBackgroundColorView : UIView

@end

@implementation ConstBackgroundColorView

- (void)setBackgroundColor:(UIColor *)backgroundColor {
    if (nil == self.backgroundColor) {
        [super setBackgroundColor:backgroundColor];
    }
}

@end

Solution 20 - Iphone

If the background solution mentioned above isn't fixing your problem, your issue may lie in your datasource for your tableView.

For me, I was creating an instance of a DataSource object (called BoxDataSource) to handle the delegate and dataSource tableView methods, as so:

//In cellForRowAtIndexPath, when setting up cell
let dataSource = BoxDataSource(delegate: self)
cell.tableView.dataSource = dataSource
return cell

This was causing the dataSource to be deallocated whenever the cell was tapped, and thus all the contents disappeared. The reason being, is ARC deallocating/garbage collecting nature.

To fix this, I had to go into the custom cell, add a datasource variable:

//CustomCell.swift
var dataSource: BoxDataSource?

Then, you need to set the dataSource to the cell's dataSource var you just created in cellForRow, so this isnt deallocated with ARC.

cell.statusDataSource = BoxAssigneeStatusDataSource(delegate: self)
cell.detailsTableView.dataSource = cell.statusDataSource
return cell

Hope that helps.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionCyrilleView Question on Stackoverflow
Solution 1 - IphoneYatheeshaView Answer on Stackoverflow
Solution 2 - IphoneAndriyView Answer on Stackoverflow
Solution 3 - IphonePavel GurovView Answer on Stackoverflow
Solution 4 - IphoneswiftBoyView Answer on Stackoverflow
Solution 5 - IphoneAgatView Answer on Stackoverflow
Solution 6 - IphoneTim BodeitView Answer on Stackoverflow
Solution 7 - IphonegumpwangView Answer on Stackoverflow
Solution 8 - IphoneMilan KamilyaView Answer on Stackoverflow
Solution 9 - IphoneAtefView Answer on Stackoverflow
Solution 10 - IphoneDylanVannView Answer on Stackoverflow
Solution 11 - IphonePeiweiChenView Answer on Stackoverflow
Solution 12 - IphoneAlexander DanilovView Answer on Stackoverflow
Solution 13 - IphoneMehul ThakkarView Answer on Stackoverflow
Solution 14 - IphoneMagooView Answer on Stackoverflow
Solution 15 - Iphoneuser3352495View Answer on Stackoverflow
Solution 16 - IphonezekelView Answer on Stackoverflow
Solution 17 - IphoneNickView Answer on Stackoverflow
Solution 18 - IphoneJay MayuView Answer on Stackoverflow
Solution 19 - IphonemofojedView Answer on Stackoverflow
Solution 20 - IphoneJosh O'ConnorView Answer on Stackoverflow