为什么在选择UITableViewCell时所有背景都消失了?

41

我的当前项目在 UITableViewCell 的行为方面让我感到困惑。我有一个相当简单的 UITableViewCell 的子类。它通过 [self.contentView addSubview:...] 添加了一些额外的元素到基本视图,并设置元素的背景颜色使其看起来像黑色和灰色的矩形框。

由于整个表格的背景具有混凝土般的纹理图像,每个单元格的背景需要是透明的,即使被选中,但在这种情况下,它应该变暗一点。我设置了自定义半透明选中背景以实现此效果:

UIView *background = [[[UIView alloc] initWithFrame:self.bounds] autorelease];
background.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.6];
background.opaque = NO;

[self setSelectedBackgroundView:background];

虽然这样可以得到正确的背景效果,但当我选择单元格时会出现一个奇怪的副作用:所有其他背景都被关闭了。以下是屏幕截图。底部单元格看起来应该是这样的,而且没有被选中。顶部单元格被选中,但它应该显示黑色和灰色的矩形区域,但它们已经消失了!

Screenshot of the simulator. The top cell is selected, the bottom is not.

谁知道这里发生了什么,更重要的是:我该如何纠正这个问题?


我知道一个解决方案是摆脱所有的子视图,并在drawRect:方法中手动绘制所有内容,但由于当前依赖于这种机制的类,这不是一个选项。 - epologee
请查看此帖子。https://dev59.com/uWw15IYBdhLWcg3wO5OX - ms83
好建议,@ms83,但最终结果完全相同,只是现在背景上有一张图片。其他所有背景仍然“消失”。它们不会像那样设置为该背景,因为多个透明视图应该堆叠并仍然可见,即使以非预期的方式。 - epologee
这听起来像是缓存问题。也许可以创建两个CellIdentifiers,一个用于选定的单元格,另一个用于非选定的单元格。根据需要初始化任何类型的单元格并出列。 - Wolfgang Schreurs
嗨@Wolfgang,我不太明白你的意思。您在图像中看到的只是同一单元格的选定和常规状态。选择是由于我按住鼠标按钮(模拟器)同时截取屏幕截图所致。在设备上也会发生同样的事情。 - epologee
8个回答

55

发生的情况是,TableViewCell内部的每个子视图都会接收到setSelected和setHighlighted方法。 setSelected方法将删除背景颜色,但如果您为所选状态设置它,则会进行更正。

例如,如果这些是作为自定义单元格的子视图添加的UILabel,则可以将以下内容添加到TableViewCell实现代码的setSelected方法中:

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

    self.textLabel.backgroundColor = [UIColor blackColor];

}

self.textLabel应该是显示在上面图片中的标签名称。

我不确定您将所选视图添加在哪里,我通常会在setSelected方法中添加它。

或者,您可以子类化UILabel并覆盖setHighlighted方法,像这样:

-(void)setHighlighted:(BOOL)highlighted
{
    [self setBackgroundColor:[UIColor blackColor]];
}

嘿,感谢您回答这个问题。由于它已经无人回答了相当长的时间,您一定获得了某种徽章。谢谢! - epologee
1
别忘了在第二个方法中调用super setHighlighted哦 ;) - Nat
2
嗯,setSelected 在 iOS 8 中似乎不起作用,但 setHighlighted 的重写可以解决问题,谢谢! - 1337holiday
1
为什么是苹果?为什么? - Pedro Paulo Amorim

42

如果你不知道高亮过程是怎么回事,那么单元格的高亮过程可能会显得非常复杂和令人困惑。我曾经非常困惑并进行了一些实验。以下是我的研究笔记,可以帮助某些人(如果有任何补充或反驳,请评论,我将尽力确认并更新):

普通“未选定”状态下:

  • contentView(在 XIB 中的内容,除非你另行编码)正常绘制。
  • selectedBackgroundView 被隐藏。
  • backgroundView 可见(因此,只要 contentView 是透明的,你就可以看到 backgroundView 或者(如果你没有定义 backgroundView)看到 UITableView 本身的背景颜色)。

当选中一个单元格时,立即执行以下操作,没有任何动画:

  • contentView 中的所有视图/子视图都会清除其 backgroundColor(或设置为透明),标签等文本颜色更改为其选定的颜色。
  • selectedBackgroundView 变成可见状态(这个视图始终是单元格的全尺寸(自定义框架将被忽略,如果需要,请使用子视图)。同时请注意,某些原因导致 subViewsbackgroundColor 不会显示,也许它们像 contentView 一样设置为透明)如果你没有定义 selectedBackgroundView ,那么 Cocoa 就会创建/插入蓝色(或灰色)渐变背景,并为你显示这个背景)。
  • backgroundView 不变。

当取消选择单元格时,开始一个动画以去除高亮:

  • selectedBackgroundView 的 alpha 属性从 1.0(完全不透明)动画到 0.0(完全透明)。
  • backgroundView 再次不变(因此动画看起来像是 selectedBackgroundViewbackgroundView 之间的交叉淡化效果)。
  • contentView 只有在动画完成后才会以“未选定”状态重新绘制,并且其子视图的 backgroundColor 再次变得可见(这可能会导致你的动画看起来很糟糕,所以建议你不要在 contentView 中使用 UIView.backgroundColor )。

结论:

如果你需要一个 backgroundColor 在高亮动画中保持不变,请勿使用 UIViewbackgroundColor 属性,而是可以尝试(可能是在 tableview:cellForRowAtIndexPath: 中):

具有背景颜色的 CALayer:

UIColor *bgColor = [UIColor greenColor];
CALayer* layer = [CALayer layer];
layer.frame = viewThatRequiresBGColor.bounds;
layer.backgroundColor = bgColor.CGColor;
[cell.viewThatRequiresBGColor.layer addSublayer:layer];

或者是一个CAGradientLayer:

UIColor *startColor = [UIColor redColor];
UIColor *endColor = [UIColor purpleColor];
CAGradientLayer* gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = viewThatRequiresBGColor.bounds;
gradientLayer.colors = @[(id)startColor.CGColor, (id)endColor.CGColor];
gradientLayer.locations = @[[NSNumber numberWithFloat:0],[NSNumber numberWithFloat:1]];
[cell.viewThatRequiresBGColor.layer addSublayer:gradientLayer];

我还使用了CALayer.border技术来为自定义的UITableView分割线提供样式:

// We have to use the borderColor/Width as opposed to just setting the 
// backgroundColor else the view becomes transparent and disappears during 
// the cell's selected/highlighted animation
UIView *separatorView = [[UIView alloc] initWithFrame:CGRectMake(0, 43, 1024, 1)];
separatorView.layer.borderColor = [UIColor redColor].CGColor;
separatorView.layer.borderWidth = 1.0;
[cell.contentView addSubview:separatorView];

2
但是仅在视图层面上操作而不创建新的视图不就足够了吗?就像这样: viewThatRequiresBGColor.layer = bgColor.CGColor; - roberto.buratti
值得一提的是,在UITableViewStyleGrouped样式下,backgroundView仅适用于UITableViewCell。 - Benjohn
非常感谢。我遇到了类似的问题。这帮助我解决了它。 - Devashis Kant

16
当您开始拖动UITableViewCell时,它会在其子视图上调用setBackgroundColor:方法,并使用0-alpha颜色进行设置。我通过子类化UIView并覆盖setBackgroundColor:方法来解决了这个问题,以忽略使用0-alpha颜色的请求。虽然感觉有些hacky,但比我遇到的任何一个 其他 解决方案都更加简洁。
@implementation NonDisappearingView

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

@end

然后,我在单元格中添加了一个NonDisappearingView并向其添加其他子视图:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *cellIdentifier = @"cell";    
    UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
        UIView *background = [cell viewWithTag:backgroundTag];
        if (background == nil) {
            background = [[NonDisappearingView alloc] initWithFrame:backgroundFrame];
            background.tag = backgroundTag;
            background.backgroundColor = backgroundColor;
            [cell addSubview:background];
        }

        // add other views as subviews of background
        ...
    }
    return cell;
}

或者,您可以将cell.contentView实例化为NonDisappearingView的实例。


1
谢谢你的补充,有趣的解决方案。我不明白为什么苹果决定实现这种子视图背景色的行为。干杯! - epologee

6
我的解决方案是在调用超类方法之前保存backgroundColor,并在调用后恢复它。
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
    UIColor *bgColor = self.textLabel.backgroundColor;
    [super setSelected:selected animated:animated];
    self.textLabel.backgroundColor = bgColor;
}

你还需要对 -setHighlighted:animated: 进行相同的操作。

看了其他答案后,我也是这么做的。看起来效果不错。 - Benjohn

3

我发现了一种比在tableView方法中进行修改更优雅的解决方案。你可以创建一个UIView子类,忽略将其背景色设置为透明颜色的操作。代码:

class NeverClearView: UIView {
    override var backgroundColor: UIColor? {
        didSet {
            if UIColor.clearColor().isEqual(backgroundColor) {
                backgroundColor = oldValue
            }
        }
    }
}

Objective-C的版本会类似,这里主要的点在于理解思路。


2

我创建了一个UITableViewCell的类别/扩展,可以让您打开和关闭这个透明度“特性”。

您可以在GitHub上找到KeepBackgroundCell

通过将以下行添加到您的Podfile来通过CocoaPods安装它:

pod 'KeepBackgroundCell'

Usage:

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

1

阅读了所有现有的答案后,我使用Swift提出了一种优雅的解决方案,只需子类化UITableViewCell即可。

extension UIView {
    func iterateSubViews(block: ((view: UIView) -> Void)) {
        for subview in self.subviews {
            block(view: subview)
            subview.iterateSubViews(block)
        }
    }
}

class CustomTableViewCell: UITableViewCell {
   var keepSubViewsBackgroundColorOnSelection = false

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
    }

    // MARK: Overrides
    override func setSelected(selected: Bool, animated: Bool) {
        if self.keepSubViewsBackgroundColorOnSelection {
            var bgColors = [UIView: UIColor]()
            self.contentView.iterateSubViews() { (view) in

                guard let bgColor = view.backgroundColor else {
                    return
                }

                bgColors[view] = bgColor
            }

            super.setSelected(selected, animated: animated)

            for (view, backgroundColor) in bgColors {
                view.backgroundColor = backgroundColor
            }
        } else {
            super.setSelected(selected, animated: animated)
        }
    }

    override func setHighlighted(highlighted: Bool, animated: Bool) {
        if self.keepSubViewsBackgroundColorOnSelection {
            var bgColors = [UIView: UIColor]()
            self.contentView.iterateSubViews() { (view) in
                guard let bgColor = view.backgroundColor else {
                    return
                }

                bgColors[view] = bgColor
            }

            super.setHighlighted(highlighted, animated: animated)

            for (view, backgroundColor) in bgColors {
                view.backgroundColor = backgroundColor
            }
        } else {
            super.setHighlighted(highlighted, animated: animated)
        }
    }
}

0
我们所需要做的就是重写setSelected方法,并在自定义tableViewCell类中更改tableViewCell的selectedBackgroundView。
我们需要在cellForRowAtIndexPath方法中为tableViewCell添加backgroundview。
lCell.selectedBackgroundView = [[UIView alloc] init];

接下来,我已经重写了如下所示的setSelected方法。

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

// Configure the view for the selected state

UIImageView *lBalloonView = [self viewWithTag:102];
[lBalloonView setBackgroundColor:[[UIColor hs_globalTint] colorWithAlphaComponent:0.2]];

UITextView *lMessageTextView = [self viewWithTag:103];
lMessageTextView.backgroundColor    = [UIColor clearColor];

UILabel *lTimeLabel = [self viewWithTag:104];
lTimeLabel.backgroundColor  = [UIColor clearColor];

}

同样需要注意的一个非常重要的点是更改tableViewCell的选择样式。 它不应该是UITableViewCellSelectionStyleNone。
lTableViewCell.selectionStyle = UITableViewCellSelectionStyleGray;

enter image description here


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接