UIView backgroundColor disappears when UITableViewCell is selected

IosUitableviewUiviewInterface Builder

Ios Problem Overview


I have a simple tableViewCell build in interface builder. It contains a UIView which contains an image. Now, when I select the cell, the default blue selection background is shown, but the backgroundColor of my UIView is gone.

My UITableViewCell's implementation file doesn't do anything special. It just init's & returns self and all I do in setSelected is call super.

How do I get my UIView backgroundColor to show when the tableView is selected?

Ios Solutions


Solution 1 - Ios

The problem here is that the [super] implementation of

- (void) setSelected:(BOOL) selected animated:(BOOL) animated;

sets all the background colors in the UITableViewCell to rgba(0,0,0,0). Why? Perhaps to make us all sweat?

It is not that entire views disappear (as evidenced by the fact that if you change the views layer border properties, those are retained)

Here is the sequence of function calls that results from touching a cell

  1. setHighlighted
  2. touchesEnded
  3. layoutSubviews
  4. willSelectRowAtIndexPath (delegate side)
  5. setSelected (!!! this is where all your view background colors are told to disappear)
  6. didSelectRowAtIndexPath (delegate side)
  7. setSelected (again) (Interestingly background colors not cleared on this call. What strangeness is going on inside that super method?)
  8. layoutSubviews (again)

So your options are to

  1. Override - (void) setSelected:(BOOL) selected animated:(BOOL) animated; without calling [super setSelected:selected animated:animated]. This will give you the most technically correct implementation because a) the code is wrapped up inside the UITableViewCell subclass and b) because it is only called when needed (well twice when needed, but maybe there is a way around that). The down side is you'll have to re-implement all the necessary functions (as opposed to unnecessary color clearing functions) of setSelected. Now don't ask me how to properly override setSelected just yet. Your guess is as good as mine for now (be patient, I'll edit this answer once I figure it out).
  2. Re-assert the background colors in didSelectRowAtIndexPath. This is not so great because it puts what should be instance code outside the instance. It has the upside that it is only called when it is needed, as opposed to ...
  3. Re-assert the background colors in layoutSubviews. This is not great at all because layoutSubviews is called like A MILLION times! It is called every time the table refreshes, every time it scrolls, every time you grandmother gets a perm... like seriously, a million times. That means there is a lot of unnecessary background re-assertions and a lot of extra processing overhead. On the bright side it puts the code inside the UITableViewCell subclass, which is nice.

Unfortunately re-asserting the background colors in setHighlighted does nothing because setHighlighted is called before all the background colors get set to [r:0 b:0 g:0 a:0] by the first call to setSelected.

//TODO: Give a great description of how to override setSelected (stay tuned)

Solution 2 - Ios

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

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

Solution 3 - Ios

When your UITableViewCell is selected, there are two states you should pay attention to: Highlighted and Selected.

So, for scenarios that you have a custom cell class which is subclass of UITableViewCell, you can easily override these two methods to avoid this situation(Swift):

class MyCell: UITableViewCell {
    
    @IBOutlet var myView: UIView!
    
    override func setHighlighted(highlighted: Bool, animated: Bool) {
        let myViewBackgroundColor = myView.backgroundColor
        super.setHighlighted(highlighted, animated: animated)
        myView.backgroundColor = myViewBackgroundColor
    }

    override func setSelected(selected: Bool, animated: Bool) {
        let myViewBackgroundColor = myView.backgroundColor
        super.setSelected(selected, animated: animated)
        myView.backgroundColor = myViewBackgroundColor
    }
    
}

Solution 4 - Ios

Previously I have done as @P5ycH0 said (1x1 image stretched), but following @Brooks I figured that overriding -(void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated in my custom UITableViewCell implementation and resetting the the background colors after calling [super setHighlighted:highlighted animated:animated]; keeps my background colors when the cell is selected/highlighted

-(void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated {
    [super setHighlighted:highlighted animated:animated];
    myView.backgroundColor = myColor;
}

Solution 5 - Ios

This issue may (finally) be resolved in iOS 13. Found this sweet paragraph in the iOS 13 beta 3 release notes.

> The UITableViewCell class no longer changes the backgroundColor or isOpaque properties of the contentView and any of its subviews when cells become highlighted or selected. If you are setting an opaque backgroundColor on any subviews of the cell inside (and including) the contentView, the appearance when the cell becomes highlighted or selected might be affected. The simplest way to resolve any issues with your subviews is to ensure their backgroundColor is set to nil or clear, and their opaque property is false. However, if needed you can override the setHighlighted(:animated:) and setSelected(:animated:) methods to manually change these properties on your subviews when moving to or from the highlighted and selected states. (13955336)

https://developer.apple.com/documentation/ios_ipados_release_notes/ios_ipados_13_beta_3_release_notes

Solution 6 - Ios

Brooks has a great explanation for why this happens, but I think I have a better solution.

In your subview, override setBackgroundColor: to what ever color you want. The setter will still be called, but only your color specified will be enforced.

- (void)setBackgroundColor:(UIColor *)backgroundColor {
    [super setBackgroundColor:[UIColor whiteColor]];
}

Solution 7 - Ios

Ok, loosing the background color of a UIView class is normal behavior when its in a selected tableviewcell. I couldn't figure out how to prevent that. Now I've just replaced the UIView with an UIImageView containing a stretched 1x1 white pixel. Ugly imo, but it works.

Solution 8 - Ios

You need to override the next two methods in your custom cell:

- (void) setSelected:(BOOL)selected animated:(BOOL)animated;
- (void) setHighlighted:(BOOL)highlighted animated:(BOOL)animated;

Note that:

  • you should call [super setSelected:animated:] and [super setHighlighted:animated:] in the beginning of your custom implementation or correspond methods;
  • you should set the UITableViewCellSelectionStyleNone selectionStyle for your custom cell, to disable any default UITableViewCell styling;

Here the example of the implementation:

- (void) setHighlighted:(BOOL)highlighted animated:(BOOL)animated
{
	[super setHighlighted:highlighted animated:animated];
	[self setHighlightedSelected:highlighted animated:animated];
}

- (void) setSelected:(BOOL)selected animated:(BOOL)animated
{
	[super setSelected:selected animated:animated];
	[self setHighlightedSelected:selected animated:animated];
}

- (void) setHighlightedSelected:(BOOL)selected animated:(BOOL)animated
{
	void(^selection_block)(void) =
	^
	{
		self.contentView.backgroundColor = selected ? SELECTED_BACKGROUND_COLOR : NORMAL_BACKGROUND_COLOR;
	};
	
	if(animated)
	{
		[UIView animateWithDuration:SELECTION_ANIMATION_DURATION
							  delay:0.0
							options:UIViewAnimationOptionBeginFromCurrentState
						 animations:selection_block
						 completion:NULL];
	}
	else
		selection_block();
}

The contentView is the property of UITableViewCell that is appeared in iOS 7. Note that you can use your own cell's child view or views instead of it.

Solution 9 - Ios

Add this to your UITableViewCell

override func setHighlighted(highlighted: Bool, animated: Bool) {
    super.setHighlighted(false, animated: animated)
    if highlighted {
        self.backgroundColor = UIColor.blueColor()
    }else{
        UIView.animateWithDuration(0.2, animations: {
            self.backgroundColor = UIColor.clearColor()
        })
    }
}

Solution 10 - Ios

Related to @Brooks's answer, this is what I did to make it work in Swift and iOS8/iOS9.

  • Override the setSelected and setHighlighted

  • Don't call super

  • Clear out the contentView.backgroundColor, because it does not have to span over the whole width of the cell (i.e. accessories).

  • Use the backgroundColor of the cell itself, and set it accordingly.

     class AwesomeTableViewCell: UITableViewCell {
         
         private struct Constants {
             
             static var highlightedColor = UIColor.greenColor()
             static var selectedColor = UIColor.redColor()
             
             static let animationTime = NSTimeInterval(0.2)
         }
    
         override func awakeFromNib() {
             super.awakeFromNib()
             
             contentView.backgroundColor = UIColor.clearColor()
             backgroundColor = AppContext.sharedInstance.theme.colors.background
         }
    
         override func setHighlighted(highlighted: Bool, animated: Bool) {
             if animated {
                 UIView.animateWithDuration(Constants.animationTime, animations: { () -> Void in
                     self.setHighlighted(highlighted)
                 })
             } else {
                 self.setHighlighted(highlighted)
             }
         }
         
         override func setSelected(selected: Bool, animated: Bool) {
             
             if animated {
                 UIView.animateWithDuration(Constants.animationTime, animations: { () -> Void in
                     self.setSelected(selected)
                 })
             } else {
                 self.setSelected(selected)
             }
         }
         
         private func setHighlighted(highlighted: Bool) {
    
             backgroundColor = highlighted ? Constants.highlightedColor : UIColor.whiteColor()
         }
         
         private func setSelected(selected: Bool) {
             
             backgroundColor = selected ? Constants.selectedColor : UIColor.whiteColor()
         }
     }
    

Solution 11 - Ios

Summary

This solution let's you lock some of a cell's background colors, while the remainder are controlled by system behaviour.


Based on mientus' answer, I have created a solution which allows you to specify which views should keep their background color.

This still allows other cell subviews to have their background removed on highlighting/selection, and is the only solution which works in our case (two views needing a permanent background).

I used a protocol-oriented approach, with a BackgroundLockable protocol containing the list of views to lock, and running a closure while keeping the colors:

protocol BackgroundLockable {
    var lockedBackgroundViews: [UIView] { get }
    func performActionWithLockedViews(_ action: @escaping () -> Void)
}

extension BackgroundLockable {
    func performActionWithLockedViews(_ action: @escaping () -> Void) {
        let lockedViewToColorMap = lockedBackgroundViews.reduce([:]) { (partialResult, view) -> [UIView: UIColor?] in
            var mutableResult = partialResult
            mutableResult[view] = view.backgroundColor
            return mutableResult
        }

        action()

        lockedViewToColorMap.forEach { (view: UIView, color: UIColor?) in
            view.backgroundColor = color
        }
    }
}

Then I have a subclass of UITableViewCell, which overrides highlighting and selection to run the protocol's closure around calling the default (super) behaviour:

class LockableBackgroundTableViewCell: UITableViewCell, BackgroundLockable {

    var lockedBackgroundViews: [UIView] {
        return []
    }

    override func setHighlighted(_ highlighted: Bool, animated: Bool) {
        performActionWithLockedViews {
            super.setHighlighted(highlighted, animated: animated)
        }
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        performActionWithLockedViews {
            super.setSelected(selected, animated: animated)
       }
    }
}

Now I just have to subclass LockableBackgroundTableViewCell or use the BackgroundLockable protocol in a cell class to easily add locking behaviour to some cells!

class SomeCell: LockableBackgroundTableViewCell {

    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var icon: UIImageView!
    @IBOutlet weak var button: UIButton!

    override var lockedBackgroundViews: [UIView] {
        return [label, icon]
    }
}

Solution 12 - Ios

Swift 4

In your UITableViewCell class:

override func setSelected(_ selected: Bool, animated: Bool) {
    myView.backgroundColor = UIColor.blue
}

override func setHighlighted(_ highlighted: Bool, animated: Bool) {
    myView.backgroundColor = UIColor.blue
}

Solution 13 - Ios

From that you said you built a tableViewCell using IB, I'd like to check whether you are adding your view as a subview of contentView of UITableViewCell, not view. The content view is the default superview for content displayed by the cell.

From the reference:

> The content view of a UITableViewCell object is the default superview for content displayed by the cell. If you want to customize cells by simply adding additional views, you should add them to the content view so they will be positioned appropriately as the cell transitions into and out of editing mode.

Solution 14 - Ios

You can change the behavior of the tableViewCell by overriding the function setHighlighted in UITableViewCell class (you will need to inherit from it). Example of my code where I change the background image of my cell :

// animate between regular and highlighted state
- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated; {
	[super setHighlighted:highlighted animated:animated];

	//Set the correct background Image
	UIImageView* backgroundPicture = (UIImageView*)[self viewWithTag:HACK_BACKGROUND_VIEW_TAG];
	if (highlighted) {
		backgroundPicture.image = [UIImage imageNamed:@"FondSelected.png"]; 
	}
	else {
		backgroundPicture.image = [UIImage imageNamed:@"Fond.png"]; 
	}
}

You can also change the selection mode to gray, blue or none in the interface builder.

Solution 15 - Ios

In iOS 7, what worked for me is to override setSelected:animated: in the UITableViewCell subclass, but contrary to @Brooks' tip, I called [super setSelected:selected animated:animated].

- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
    [super setSelected:selected animated:animated];
    
    // Reassert the background color of the color view so that it shows
    // even when the cell is highlighted for selection.
    self.colorView.backgroundColor = [UIColor blueColor];
}

This lets me keep the system's default selection animation when the user taps on the cell, and also to deselect it in the table view delegate's didSelectRowAtIndexPath:.

Solution 16 - Ios

Just spent some time on this weird issue. I did not want to set UITableViewCellSelectionStyleNone style to preserve nice animation when my row was selected. But none of suggested ideas worked for me - I was trying to override setSelected and setHighlighted and set my subview backgroundColor there - it was keeping resetting by iOS and still blinking (new color -> old color). For me the fix was quite simple. When my row is selected another view controller is pushed, user chooses some option on that screen and delegate is called where I change the color based on user selection. In this delegate I just do [cell setSelected:NO animated:NO] for my cell. (I have static UITableViewController and have outlets to cells). You could probably deselect cell in didSelect method but in my case I'm using segues.

Solution 17 - Ios

Here's my take on it. I have a subclass that all my cell inherit from, so that's the way I do it to avoid the background change in my UIImageViews:

    override func setHighlighted(highlighted: Bool, animated: Bool) {
    var backgroundColors = [UIView: UIColor]()
    
    for view in contentView.subviews as [UIView] {
        if let imageView = view as? UIImageView {
            backgroundColors[imageView] = imageView.backgroundColor
        }
    }
    
    super.setHighlighted(highlighted, animated: animated)
    
    for view in contentView.subviews as [UIView] {
        if let imageView = view as? UIImageView {
            imageView.backgroundColor = backgroundColors[imageView]
        }
    }
}

override func setSelected(selected: Bool, animated: Bool) {
    var backgroundColors = [UIView: UIColor]()
    
    for view in contentView.subviews as [UIView] {
        if let imageView = view as? UIImageView {
            backgroundColors[imageView] = imageView.backgroundColor
        }
    }
    
    super.setSelected(selected, animated: animated)
    
    for view in contentView.subviews as [UIView] {
        if let imageView = view as? UIImageView {
            imageView.backgroundColor = backgroundColors[imageView]
        }
    }
}

This automaticlly fix the issue for all UIImageView.

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
QuestionTycho PandelaarView Question on Stackoverflow
Solution 1 - IosBrooksView Answer on Stackoverflow
Solution 2 - IosMichal ZaborowskiView Answer on Stackoverflow
Solution 3 - IoskubilayView Answer on Stackoverflow
Solution 4 - IosZeCodeaView Answer on Stackoverflow
Solution 5 - IosPatrickDotStarView Answer on Stackoverflow
Solution 6 - IoslavoyView Answer on Stackoverflow
Solution 7 - IosTycho PandelaarView Answer on Stackoverflow
Solution 8 - IosIgor VasilevView Answer on Stackoverflow
Solution 9 - IosTanel TeemuskView Answer on Stackoverflow
Solution 10 - IosKevin RView Answer on Stackoverflow
Solution 11 - IosYasirView Answer on Stackoverflow
Solution 12 - IosChannelView Answer on Stackoverflow
Solution 13 - IosMHCView Answer on Stackoverflow
Solution 14 - IosCedricSoubrieView Answer on Stackoverflow
Solution 15 - IosMLQView Answer on Stackoverflow
Solution 16 - IosdimaclopinView Answer on Stackoverflow
Solution 17 - IosallaireView Answer on Stackoverflow