Cyrille
Cyrille

Reputation: 25144

UITableViewCell subview disappears when cell is selected

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).

Upvotes: 177

Views: 57966

Answers (20)

Andriy
Andriy

Reputation: 2796

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

Upvotes: 121

gentlee
gentlee

Reputation: 3727

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

Upvotes: 1

Atef
Atef

Reputation: 2902

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...
    }
    

Upvotes: 4

zekel
zekel

Reputation: 9487

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
        }
    }
}

Upvotes: 0

Yatheesha
Yatheesha

Reputation: 10432

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
    }
}

Upvotes: 225

Pavel Gurov
Pavel Gurov

Reputation: 5605

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

Upvotes: 44

Milan Kamilya
Milan Kamilya

Reputation: 2298

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
    }
}

Upvotes: 4

Josh O'Connor
Josh O'Connor

Reputation: 4962

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.

Upvotes: -1

mofojed
mofojed

Reputation: 1393

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

Upvotes: -1

user3352495
user3352495

Reputation: 391

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
            }
        }
    }
}

Upvotes: 0

Jaya Mayu
Jaya Mayu

Reputation: 17257

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)
    }
}

Upvotes: -1

Magoo
Magoo

Reputation: 2636

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()
}

Upvotes: 0

Mehul Thakkar
Mehul Thakkar

Reputation: 12592

Try Following code:

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

Upvotes: 0

swiftBoy
swiftBoy

Reputation: 35783

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.

Upvotes: 10

PeiweiChen
PeiweiChen

Reputation: 413

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);

        }
    }


}

Upvotes: 1

Nick
Nick

Reputation: 241

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;
    }
}

Upvotes: -1

gumpwang
gumpwang

Reputation: 109

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

Upvotes: 7

Tim Bodeit
Tim Bodeit

Reputation: 9703

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

Upvotes: 8

DylanVann
DylanVann

Reputation: 493

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.

Upvotes: 3

Agat
Agat

Reputation: 4779

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];

Upvotes: 9

Related Questions