objective-c - source - uitableviewcell swift 3
¿Por qué desaparecen todos los fondos en la selección de UITableViewCell? (8)
El comportamiento UITableViewCell de mi proyecto actual me desconcierta. Tengo una subclase bastante sencilla de UITableViewCell. Agrega algunos elementos adicionales a la vista base (a través de [self.contentView addSubview:...]
y establece colores de fondo en los elementos para que se vean como cuadros rectangulares negros y grises.
Debido a que el fondo de toda la tabla tiene esta imagen de textura similar al hormigón, el fondo de cada celda debe ser transparente, incluso cuando se selecciona, pero en ese caso debería oscurecerse un poco. Establecí un fondo seleccionado semitransparente personalizado para lograr este efecto:
UIView *background = [[[UIView alloc] initWithFrame:self.bounds] autorelease];
background.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.6];
background.opaque = NO;
[self setSelectedBackgroundView:background];
Y aunque eso da la apariencia correcta para el fondo, ocurre un extraño efecto secundario cuando selecciono la celda; todos los demás fondos de alguna manera se apagan . Aquí hay una captura de pantalla. La celda inferior parece que debería y no está seleccionada. Se selecciona la celda superior, pero debe mostrar las áreas rectangulares negras y grises, ¡pero desaparecieron!
Quién sabe qué está pasando aquí y más importante aún: ¿cómo puedo corregir esto?
Creé una categoría / extensión UITableViewCell que le permite activar y desactivar esta "función" de transparencia.
Puedes encontrar KeepBackgroundCell en GitHub
Instálalo a través de CocoaPods agregando la siguiente línea a tu Podfile:
pod ''KeepBackgroundCell''
Uso:
Rápido
let cell = <Initialize Cell>
cell.keepSubviewBackground = true // Turn transparency "feature" off
cell.keepSubviewBackground = false // Leave transparency "feature" on
C objetivo
UITableViewCell* cell = <Initialize Cell>
cell.keepSubviewBackground = YES; // Turn transparency "feature" off
cell.keepSubviewBackground = NO; // Leave transparency "feature" on
Cuando comienza a arrastrar una UITableViewCell, llama a setBackgroundColor:
en sus subvistas con un color 0-alfa. Trabajé alrededor de esto subclasificando UIView y redefiniendo setBackgroundColor:
para ignorar las solicitudes con colores 0-alfa. Se siente raro, pero es más limpio que any de las other solutions que he encontrado.
@implementation NonDisappearingView
-(void)setBackgroundColor:(UIColor *)backgroundColor {
CGFloat alpha = CGColorGetAlpha(backgroundColor.CGColor);
if (alpha != 0) {
[super setBackgroundColor:backgroundColor];
}
}
@end
Luego, agrego un NonDisappearingView
a mi celda y le agrego otras subvistas:
-(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;
}
Alternativamente, podría hacer que cell.contentView una instancia de NonDisappearingView
.
Después de haber leído todas las respuestas existentes, se le ocurrió una solución elegante usando Swift solo subclasificando 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)
}
}
}
El proceso de resaltado celular puede parecer complejo y confuso si no sabes qué está pasando. Estaba completamente confundido e hice una gran experimentación. Aquí están las notas sobre mis hallazgos que pueden ayudar a alguien (si alguien tiene algo que agregar o refutar, por favor comenten y me esforzaré por confirmar y actualizar)
En el estado normal "no seleccionado"
- El contenidoView (lo que está en su XIB a menos que lo haya codificado de otra manera) se dibuja normalmente
- El SelectedBackgroundView está OCULTO
- La vista de
backgroundView
es visible (de modo que si su contenido es transparente, verá la vista debackgroundView
o (si no ha definido una vista debackgroundView
, verá el color de fondo deUITableView
)
Se selecciona una celda, lo siguiente ocurre inmediatamente sin ninguna animación:
- Todas las vistas / subvistas dentro de contentView tienen su
backgroundColor
borrado (o establecido en transparente), etiqueta, etc. cambio de color de texto a su color seleccionado -
selectedBackgroundView
vuelve visible (esta vista es siempre del tamaño completo de la celda (se ignora un marco personalizado, use una subvista si es necesario). También tenga en cuenta quebackgroundColor
desubViews
no se muestran por alguna razón, tal vez se configuren transparentes como elcontentView
). Si no definió unselectedBackgroundView
, Cocoa creará / insertará el fondo degradado azul (o gris) y lo mostrará por usted. - La vista de
backgroundView
no ha cambiado
Cuando la celda está deseleccionada, comienza una animación para eliminar el resaltado:
- La propiedad alpha
selectedBackgroundView
está animada desde 1.0 (completamente opaca) a 0.0 (totalmente transparente). - La vista de
backgroundView
nuevamente no cambia (por lo que la animación se ve como un fundido cruzado entreselectedBackgroundView
ybackgroundView
) - SOLAMENTE UNA VEZ que la animación ha finalizado se vuelve a dibujar
contentView
en el estado "no seleccionado" y su subviewbackgroundColor
vuelve visible de nuevo (esto puede hacer que su animación se vea horrible por lo que es aconsejable que no useUIView.backgroundColor
en sucontentView
)
CONCLUSIONES:
Si necesita un backgroundColor
para persistir en la animación de resaltado, no use la propiedad backgroundColor
de UIView
en su lugar, puede probar (probablemente con la tableview:cellForRowAtIndexPath:
:
Un CALayer con un color de fondo:
UIColor *bgColor = [UIColor greenColor];
CALayer* layer = [CALayer layer];
layer.frame = viewThatRequiresBGColor.bounds;
layer.backgroundColor = bgColor.CGColor;
[cell.viewThatRequiresBGColor.layer addSublayer:layer];
o un 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];
También utilicé una técnica CALayer.border para proporcionar un separador UITableView personalizado:
// 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];
Encontré una solución bastante elegante en lugar de jugar con los métodos tableView. Puede crear una subclase de UIView que ignore establecer su color de fondo para borrar el color. Código:
class NeverClearView: UIView {
override var backgroundColor: UIColor? {
didSet {
if UIColor.clearColor().isEqual(backgroundColor) {
backgroundColor = oldValue
}
}
}
}
La versión Obj-C sería similar, lo principal aquí es la idea
Lo que sucede es que cada subvista dentro de TableViewCell recibirá los métodos setSelected y setHighlighted. El método setSelected eliminará los colores de fondo, pero si lo configura para el estado seleccionado, se corregirá.
Por ejemplo, si esos son UILabels agregados como subvistas en su celda personalizada, entonces puede agregar esto al método setSelected de su código de implementación TableViewCell:
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
self.textLabel.backgroundColor = [UIColor blackColor];
}
donde self.textLabel sería cualquiera que sean esas etiquetas que se muestran en la imagen de arriba
No estoy seguro de dónde está agregando su vista seleccionada, generalmente la agrego en el método setSelected.
Alternativamente, puede subclasificar el UILabel y anular el método setHighlighted así:
-(void)setHighlighted:(BOOL)highlighted
{
[self setBackgroundColor:[UIColor blackColor]];
}
Mi solución es guardar el backgroundColor
y restaurarlo después de la súper llamada.
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
UIColor *bgColor = self.textLabel.backgroundColor;
[super setSelected:selected animated:animated];
self.textLabel.backgroundColor = bgColor;
}
También necesita hacer lo mismo con -setHighlighted:animated:
Todo lo que necesitamos es anular el método setSelected y cambiar seleccionadoBackgroundView para tableViewCell en la clase tableViewCell personalizada.
Necesitamos agregar la vista de fondo para tableViewCell en el método cellForRowAtIndexPath.
lCell.selectedBackgroundView = [[UIView alloc] init];
A continuación, he anulado el método setSelected como se menciona a continuación.
- (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];
}
También uno de los puntos más importantes que hay que tener en cuenta es cambiar el estilo de selección tableViewCell. No debería ser UITableViewCellSelectionStyleNone.
lTableViewCell.selectionStyle = UITableViewCellSelectionStyleGray;