UIView backgroundColor verschwindet, wenn UITableViewCell ausgewählt ist

70

Ich habe einen einfachen tableViewCell Build in Interface Builder. Es enthält eine UIView, die ein Bild enthält. Wenn ich nun die Zelle auswähle, wird der standardmäßige blaue Auswahlhintergrund angezeigt, aber die Hintergrundfarbe meiner UIView ist nicht mehr vorhanden.

Die Implementierungsdatei von UITableViewCell macht nichts Besonderes. Es ist nur init's & gibt sich selbst zurück und alles was ich in setSelected mache ist call super.

Wie kann ich meine UIView backgroundColor anzeigen lassen, wenn tableView ausgewählt ist?

Tycho Pandelaar
quelle
Welche anderen Schnittstellenelemente befinden sich in der Ansicht, die von der Zellenauswahlfarbe abgedeckt werden?
Hennes
Derzeit ist es nur ein Bild. Es soll das Bild wie ein Foto aussehen lassen. Später werde ich dort auch ein Etikett hinzufügen.
Tycho Pandelaar

Antworten:

108

Das Problem hierbei ist, dass die [super] Implementierung von

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

Setzt alle Hintergrundfarben in der UITableViewCell auf rgba (0,0,0,0). Warum? Vielleicht, um uns alle ins Schwitzen zu bringen?

Es ist nicht so, dass ganze Ansichten verschwinden (wie durch die Tatsache belegt wird, dass diese beibehalten werden, wenn Sie die Rahmeneigenschaften der Ansichtsebene beibehalten).

Hier ist die Reihenfolge der Funktionsaufrufe, die sich aus dem Berühren einer Zelle ergibt

  1. setHighlighted
  2. berührtEnded
  3. layoutSubviews
  4. willSelectRowAtIndexPath (Delegatenseite)
  5. setSelected (!!! Hier werden alle Hintergrundfarben Ihrer Ansicht zum Verschwinden gebracht)
  6. didSelectRowAtIndexPath (Delegatenseite)
  7. setSelected (wieder) (Interessanterweise werden die Hintergrundfarben bei diesem Aufruf nicht gelöscht. Welche Seltsamkeit geschieht in dieser Supermethode?)
  8. layoutSubviews (wieder)

Sie haben also die Wahl

  1. Override - (void) setSelected: (BOOL) ausgewählt animiert: (BOOL) animiert; ohne [super setSelected: ausgewählt animiert: animiert] aufzurufen . Auf diese Weise erhalten Sie die technisch korrekteste Implementierung, da a) der Code in der UITableViewCell-Unterklasse eingeschlossen ist und b) er nur bei Bedarf aufgerufen wird (bei Bedarf zweimal, aber möglicherweise gibt es einen Weg, dies zu umgehen). Der Nachteil ist, dass Sie alle erforderlichen Funktionen (im Gegensatz zu unnötigen Farblöschfunktionen) von setSelected erneut implementieren müssen. Fragen Sie mich jetzt noch nicht, wie Sie setSelected richtig überschreiben sollen. Ihre Vermutung ist vorerst so gut wie meine (seien Sie geduldig, ich werde diese Antwort bearbeiten, sobald ich es herausgefunden habe).
  2. Aktivieren Sie die Hintergrundfarben in didSelectRowAtIndexPath erneut . Dies ist nicht so toll, weil es den Instanzcode außerhalb der Instanz platziert. Es hat den Vorteil, dass es nur aufgerufen wird, wenn es benötigt wird, im Gegensatz zu ...
  3. Aktivieren Sie die Hintergrundfarben in layoutSubviews erneut . Das ist überhaupt nicht großartig, weil layoutSubviews wie eine MILLION Mal aufgerufen wird! Es wird jedes Mal aufgerufen, wenn die Tabelle aktualisiert wird, jedes Mal, wenn sie scrollt, jedes Mal, wenn Ihre Großmutter eine Dauerwelle bekommt ... wie im Ernst, millionenfach. Das bedeutet, dass es viele unnötige Hintergrund-Re-Assertions und viel zusätzlichen Verarbeitungsaufwand gibt. Auf der positiven Seite wird der Code in die UITableViewCell-Unterklasse eingefügt, was sehr schön ist.

Leider bewirkt das erneute Aktivieren der Hintergrundfarben in setHighlighted nichts, da setHighlighted aufgerufen wird, bevor alle Hintergrundfarben beim ersten Aufruf von setSelected auf [r: 0 b: 0 g: 0 a: 0] gesetzt werden.

// TODO: Geben Sie eine gute Beschreibung, wie setSelected überschrieben werden kann (bleiben Sie dran)

Brooks
quelle
1
Tolle Resonanz. Macht sehr viel Sinn!. Ich werde für die
Außerkraftsetzung
Nur ein Follow-up. Ich habe einiges damit herumgespielt, aber immer noch nicht gefunden, was ich als saubere, generische Lösung zum Überschreiben von setSelected bezeichnen würde. Wenn / wenn ich es tue, werde ich weitere Informationen posten .... Happy Coding
Brooks
1
Wenn Sie die UIView-Hintergrundfarbe in setSelected einstellen, sollten Sie sie auch auf setHighlighted setzen, da sonst möglicherweise ein Flimmern auftritt.
nh32rg
10
Zumindest auf iOS 7, können Sie auch die Zelle gesetzt selectionStylezu UITableViewCellSelectionStyleNone, und es wird nicht Hintergründe löschen. Der einzige Nachteil ist, dass die selectedBackgroundViewEigenschaft ignoriert wird .
Austin
Noch nichts? Es ist 8 Jahre her und ich bin immer noch dran
Nicholas
72
- (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;
}
Michal Zaborowski
quelle
1
Tolle Lösung. Viel sauberer als alle Unterklassen- und CALayer-Lösungen.
Joel
Vielen Dank! Ihre Lösung ist die beste!
Insider
Dies war die nützlichste Antwort für mich. Es wurde leicht für meinen Anwendungsfall geändert (mehrere Zellen mit mehreren gesperrten Hintergrundansichten), aber ein großes Lob für die funktionierende Lösung!
Yasir
Beste Lösung aller Zeiten!
Yongqiang Zhou
17

Wenn Sie UITableViewCellausgewählt sind, sollten Sie auf zwei Zustände achten: Highlightedund Selected.

In Szenarien, in denen Sie eine benutzerdefinierte Zellklasse haben, zu der eine Unterklasse gehört UITableViewCell, können Sie diese beiden Methoden leicht überschreiben, um diese Situation zu vermeiden (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
    }

}
Kubilay
quelle
beste Antwort für schnell
Rukshan Marapana
16

Früher habe ich getan, wie @ P5ycH0 sagte (1x1 Bild gestreckt), aber nach @Brooks stellte ich fest, dass das Überschreiben -(void)setHighlighted:(BOOL)highlighted animated:(BOOL)animatedin meiner benutzerdefinierten UITableViewCellImplementierung und das Zurücksetzen der Hintergrundfarben nach dem Aufruf [super setHighlighted:highlighted animated:animated];meine Hintergrundfarben beibehält, wenn die Zelle ausgewählt / hervorgehoben wird

-(void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated {
    [super setHighlighted:highlighted animated:animated];
    myView.backgroundColor = myColor;
}
ZeCodea
quelle
Dank war 2 Tage lang auf der Suche
Sanjeev
Sie sparen meine Zeit Vielen Dank
mychar
Ich denke, diese setHighlightedFunktion wurde erstellt, um benutzerdefiniertes Verhalten hinzuzufügen, wenn Zelle ausgewählt wird, übrigens gute Arbeit
Wendy Liga
6

Dieses Problem kann (endlich) in iOS 13 behoben werden. Dieser süße Absatz wurde in den Versionshinweisen zu iOS 13 Beta 3 gefunden.

Die UITableViewCell-Klasse ändert die Eigenschaften backgroundColor oder isOpaque der contentView und ihrer Unteransichten nicht mehr, wenn Zellen hervorgehoben oder ausgewählt werden. Wenn Sie in Unteransichten der Zelle in (und einschließlich) der Inhaltsansicht eine undurchsichtige Hintergrundfarbe festlegen, kann dies das Erscheinungsbild beeinträchtigen, wenn die Zelle hervorgehoben oder ausgewählt wird. Der einfachste Weg, um Probleme mit Ihren Unteransichten zu beheben, besteht darin, sicherzustellen, dass deren Hintergrundfarbe auf Null oder Löschen gesetzt ist und ihre undurchsichtige Eigenschaft falsch ist. Bei Bedarf können Sie jedoch die Methoden setHighlighted ( : animiert :) und setSelected ( : animiert :) überschreiben , um diese Eigenschaften in Ihren Unteransichten manuell zu ändern, wenn Sie in den hervorgehobenen und ausgewählten Status wechseln. (13955336)

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

PatrickDotStar
quelle
Schließlich reparieren sie diese seltsame Sache
Tycho Pandelaar
Aber das macht selectedBackgroundViewobsolet :( weil es verborgen bleiben könnte
Vilém Kurz
4

Brooks hat eine gute Erklärung dafür, warum dies passiert, aber ich denke, ich habe eine bessere Lösung.

Überschreiben setBackgroundColor:Sie in Ihrer Unteransicht die gewünschte Farbe. Der Setter wird weiterhin aufgerufen, aber nur die angegebene Farbe wird erzwungen.

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

Ok, das Verlieren der Hintergrundfarbe einer UIView-Klasse ist normal, wenn sie sich in einer ausgewählten Tabellenansichtszelle befindet. Ich konnte nicht herausfinden, wie ich das verhindern konnte. Jetzt habe ich gerade das UIView durch ein UIImageView ersetzt, das ein gestrecktes 1x1 weißes Pixel enthält. Hässlich imo, aber es funktioniert.

Tycho Pandelaar
quelle
3

Sie müssen die nächsten beiden Methoden in Ihrer benutzerdefinierten Zelle überschreiben:

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

Beachten Sie, dass:

  • Sie sollten [super setSelected:animated:]und [super setHighlighted:animated:]zu Beginn Ihrer benutzerdefinierten Implementierung Methoden oder entsprechende Methoden aufrufen .
  • Sie sollten den UITableViewCellSelectionStyleNoneAuswahlstil für Ihre benutzerdefinierte Zelle festlegen , um das Standard- UITableViewCellStyling zu deaktivieren .

Hier das Beispiel der Implementierung:

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

Dies contentViewist die Eigenschaft UITableViewCell, die in iOS 7 angezeigt wird. Beachten Sie, dass Sie stattdessen die untergeordnete Ansicht oder die Ansichten Ihrer eigenen Zelle verwenden können.

Igor Vasilev
quelle
1

Fügen Sie dies Ihrer UITableViewCell hinzu

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()
        })
    }
}
Tanel Teemusk
quelle
1

Im Zusammenhang mit der Antwort von @ Brooks habe ich Folgendes getan, damit es in Swift und iOS8 / iOS9 funktioniert.

  • Überschreiben Sie das setSelectedundsetHighlighted
  • Ruf nicht super an
  • Räumen Sie das aus contentView.backgroundColor, da es sich nicht über die gesamte Breite der Zelle erstrecken muss (dh Zubehör).
  • Verwenden Sie die backgroundColorder Zelle selbst und stellen Sie sie entsprechend ein.

    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()
        }
    }
    
Kevin R.
quelle
1

Zusammenfassung

Mit dieser Lösung können Sie einige Hintergrundfarben einer Zelle sperren , während der Rest vom Systemverhalten gesteuert wird.


Basierend auf der Antwort von mientus habe ich eine Lösung erstellt, mit der Sie festlegen können, welche Ansichten ihre Hintergrundfarbe behalten sollen .

Dies ermöglicht weiterhin, dass der Hintergrund anderer Zellen-Unteransichten beim Hervorheben / Auswählen entfernt wird, und ist die einzige Lösung, die in unserem Fall funktioniert (zwei Ansichten benötigen einen permanenten Hintergrund).

Ich habe einen protokollorientierten Ansatz verwendet, bei dem ein BackgroundLockableProtokoll die Liste der zu sperrenden Ansichten enthält und ein Abschluss ausgeführt wird, während die Farben beibehalten werden:

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

Dann habe ich eine Unterklasse von UITableViewCell, die das Hervorheben und Auswählen überschreibt, um den Abschluss des Protokolls auszuführen, um das Standardverhalten (Superverhalten) aufzurufen:

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

Jetzt muss ich nur noch LockableBackgroundTableViewCelldas BackgroundLockableProtokoll in einer Zellklasse unterordnen oder verwenden, um einigen Zellen auf einfache Weise ein Sperrverhalten hinzuzufügen!

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]
    }
}
Yasir
quelle
1

Swift 4

In Ihrer UITableViewCell- Klasse:

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

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

Nachdem Sie gesagt haben, dass Sie eine tableViewCell mit IB erstellt haben, möchte ich überprüfen, ob Sie Ihre Ansicht als Unteransicht contentViewvon UITableViewCell hinzufügen, nichtview . Die Inhaltsansicht ist die Standardübersicht für Inhalte, die von der Zelle angezeigt werden.

Aus der Referenz:

Die Inhaltsansicht eines UITableViewCell-Objekts ist die Standardübersicht für den von der Zelle angezeigten Inhalt. Wenn Sie Zellen durch einfaches Hinzufügen zusätzlicher Ansichten anpassen möchten, sollten Sie sie der Inhaltsansicht hinzufügen, damit sie beim Übergang der Zelle in den Bearbeitungsmodus und aus dem Bearbeitungsmodus entsprechend positioniert werden.

MHC
quelle
Meine Ansichtshierarchie in InterfaceBuilders UITableViewCell lautet TableViewCell> Ansicht> ImageView. Wenn ich die tableViewCell mit Code erstellt hätte, hätte ich meine Ansicht fälschlicherweise zur Ansicht der tableviewcell anstelle der contentView hinzufügen können. Ich sehe jedoch nicht, wie ich diesen Fehler in IB machen könnte ... Beachten Sie, dass mein Bild angezeigt wird, aber Das übergeordnete Ansichtselement ist nicht.
Tycho Pandelaar
0

Sie können das Verhalten von tableViewCell ändern, indem Sie die in der UITableViewCell-Klasse festgelegte Funktion setHighlighted überschreiben (Sie müssen davon erben). Beispiel für meinen Code, bei dem ich das Hintergrundbild meiner Zelle ändere:

// 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"]; 
    }
}

Sie können den Auswahlmodus im Interface Builder auch in Grau, Blau oder Keine ändern.

CedricSoubrie
quelle
Ja, ich weiß. aber das war nicht das Problem, das ich hatte. Ich habe mich gefragt, ob ich verhindern kann, dass die Hintergrundfarbe eines UIView-basierten Elements verloren geht, wenn es sich in einer ausgewählten Tabellenansichtszelle befindet. Ich habe eine Problemumgehung gefunden, indem ich eine Bildansicht anstelle einer Ansicht verwendet habe.
Tycho Pandelaar
Noch mehr Informationen: In meinem letzten Test habe ich gezeigt, dass Hintergrundfarben JEDES Mal gelöscht werden, wenn eine Zelle ausgewählt wird, nicht nur nach der Initialisierung oder was haben Sie. Die Abfolge der Ereignisse scheint 1) setHighlighted genannt 2) touchEnded genannt 3) layoutSubviews aufgerufen 4) didSelectRowAtIndexPath aufgerufen 4) layoutSubviews erneut aufgerufen zu sein. Die Hintergrundfarben scheinen kurz vor didSelectRowAtIndexPath
Brooks
0

In iOS 7 hat es für mich funktioniert, setSelected:animated:die UITableViewCellUnterklasse zu überschreiben , aber entgegen dem Tipp von @Brooks habe ich angerufen [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];
}

Auf diese Weise kann ich die Standardauswahlanimation des Systems beibehalten, wenn der Benutzer auf die Zelle tippt, und sie auch in den Delegierten der Tabellenansicht abwählen didSelectRowAtIndexPath:.

MLQ
quelle
0

Ich habe gerade einige Zeit mit diesem seltsamen Thema verbracht. Ich wollte den UITableViewCellSelectionStyleNone-Stil nicht festlegen, um eine schöne Animation zu erhalten, wenn meine Zeile ausgewählt wurde. Aber keine der vorgeschlagenen Ideen hat bei mir funktioniert - ich habe versucht, setSelected und setHighlighted zu überschreiben und dort meine Unteransicht backgroundColor festzulegen - sie wurde von iOS immer wieder zurückgesetzt und blinkte immer noch (neue Farbe -> alte Farbe). Für mich war die Lösung ganz einfach. Wenn meine Zeile ausgewählt ist, wird ein anderer Ansichts-Controller gedrückt. Der Benutzer wählt eine Option auf diesem Bildschirm aus und der Delegat wird aufgerufen, wobei ich die Farbe basierend auf der Benutzerauswahl ändere. In diesem Delegaten mache ich einfach [cell setSelected: NO animiert: NO] für meine Zelle. (Ich habe statischen UITableViewController und habe Ausgänge zu Zellen). Sie könnten wahrscheinlich die Auswahl der Zelle in der didSelect-Methode aufheben, aber in meinem Fall verwende ich Segues.

Dimaclopin
quelle
0

Hier ist meine Meinung dazu. Ich habe eine Unterklasse, von der alle meine Zellen erben. So mache ich das, um die Hintergrundänderung in meinen UIImageViews zu vermeiden:

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

Dadurch wird das Problem automatisch für alle behoben UIImageView.

Allaire
quelle