wake-up-neo.com

AutoLayout mehrzeiliges UILabel, das etwas Text abschneidet

Ich versuche, das automatische Layout zu lernen. Ich dachte, ich hätte den Dreh raus bekommen, als dies passierte. Ich spiele mit Prototypzellen und Labels herum. Ich versuche eine zu haben 

  • Titel, titleLbl - blauer Kasten im Bild
  • Geld, moneyLbl - grüne Box im Bild
  • Untertitel/Beschreibung, subTitleLbl - rotes Feld im Bild

Bisher habe ich folgendes:

iphone screenshot

Was ich erreichen möchte, ist:

  • Das grüne Feld sollte immer in einer Zeile angezeigt werden
  • Der Wert im grünen Feld sollte auf dem blauen Feld zentriert sein
  • Das blaue Kästchen nimmt den verbleibenden Platz (-8px für horizontale Beschränkung zwischen 2) ein und zeigt bei Bedarf mehrzeilig an
  • Das rote Feld sollte darunter sein und so viele Zeilen anzeigen, wie benötigt werden.

Wie Sie sehen, funktioniert das fast alles, mit Ausnahme des Beispiels in der letzten Zeile. Ich habe versucht, dies zu recherchieren, und was ich herausfinden kann, hat etwas mit der intrinsischen Inhaltsgröße zu tun. Viele Online-Beiträge empfehlen die Einstellung setPreferredMaxLayoutWidth, ich habe dies an mehreren Stellen für die titleLbl gemacht und es hat keine Auswirkung. Nur beim Hardcodieren in 180 erhielt ich die Ergebnisse, aber es wurde auch Leerzeichen/Polsterung zu den anderen blauen Kästchen oben/unter dem Text hinzugefügt, wodurch der Abstand zwischen den roten/blauen Kästchen deutlich vergrößert wurde.

Hier sind meine Einschränkungen:

Titel:title constraints

Geld:money constraints

Untertitel:subtitle constraints

Basierend auf Tests, die ich mit anderen Textmustern ausgeführt habe, scheint sie die im Storyboard gezeigte Breite zu verwenden, aber jeder Versuch, sie zu korrigieren, funktioniert nicht.

Ich habe ein Ivar der Zelle erstellt und es verwendet, um die Höhe der Zelle zu erstellen:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    [sizingCellTitleSubMoney.titleLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"title"]];
    [sizingCellTitleSubMoney.subtitleLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"subtitle"]];
    [sizingCellTitleSubMoney.moneyLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"cost"]];

    [sizingCellTitleSubMoney layoutIfNeeded];

    CGSize size = [sizingCellTitleSubMoney.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
    return size.height+1;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    MATitleSubtitleMoneyTableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifierTitleSubTitleMoney];

    if(!cell)
    {
        cell = [[MATitleSubtitleMoneyTableViewCell alloc] init];
    }


    cell.titleLbl.layer.borderColor = [UIColor blueColor].CGColor;
    cell.titleLbl.layer.borderWidth = 1;

    cell.subtitleLbl.layer.borderColor = [UIColor redColor].CGColor;
    cell.subtitleLbl.layer.borderWidth = 1;

    cell.moneyLbl.layer.borderColor = [UIColor greenColor].CGColor;
    cell.moneyLbl.layer.borderWidth = 1;


    [cell.titleLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"title"]];
    [cell.subtitleLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"subtitle"]];
    [cell.moneyLbl setText:[[tableViewData objectAtIndex:indexPath.row] objectForKey:@"cost"]];

    return cell;
}

Ich habe versucht, setPreferredMaxLayoutWidth in den Layout-Subviews der Zelle einzustellen:

- (void)layoutSubviews
{
    [super layoutSubviews];

    NSLog(@"Money lbl: %@", NSStringFromCGRect(self.moneyLbl.frame));
    NSLog(@"prefferredMaxSize: %f \n\n", self.moneyLbl.frame.Origin.x-8);

    [self.titleLbl setPreferredMaxLayoutWidth: self.moneyLbl.frame.Origin.x-8];
}

log:

2014-04-30 21:37:32.475 AutoLayoutTestBed[652:60b] Money lbl: {{209, 20}, {91, 61}}
2014-04-30 21:37:32.475 AutoLayoutTestBed[652:60b] prefferredMaxSize: 201.000000 

Welches ist, was ich erwarten würde, da es 8px zwischen den 2 Elementen gibt und die Konsole dies überprüft. Es funktioniert perfekt in der ersten Zeile, unabhängig davon, wie viel Text ich dem blauen Feld hinzufüge, was dem Storyboard entspricht, aber jede Erhöhung des grünen Felds wirft die Breitenberechnung ab. Ich habe versucht, dies in tableView:willDisplayCell und tableView:heightForRowAtIndexPath: einzustellen, basierend auf den Antworten anderer Fragen. Aber egal was es gerade nicht Layout ist.

Jede Hilfe, Ideen oder Kommentare würden uns sehr freuen

29

Eine noch bessere Antwort gefunden, nachdem Sie das hier gelesen haben: http://johnszumski.com/blog/auto-layout-for-table-view-cells-with-dynamic-heights

erstellen einer UILabel-Unterklasse zum Überschreiben von layoutSubviews wie folgt:

- (void)layoutSubviews
{
    [super layoutSubviews];

    self.preferredMaxLayoutWidth = CGRectGetWidth(self.bounds);

    [super layoutSubviews];
}

Dies stellt sicher, dass die preferredMaxLayoutWidth immer korrekt ist.

Update:

Nach einigen weiteren Releases von iOS und dem Testen weiterer Usecases habe ich herausgefunden, dass das oben Genannte nicht für jeden Fall ausreichend ist. Seit iOS 8 (glaube ich) wurde unter bestimmten Umständen nur didSetbounds rechtzeitig vor dem Laden des Bildschirms aufgerufen.

In iOS 9 bin ich kürzlich auf ein anderes Problem gestoßen, als ich eine modale UIVIewController mit einer UITableView verwendete, nämlich dass layoutSubviews und set boundsafterheightForRowAtIndexPath genannt wurden. Nach langem Debuggen bestand die einzige Lösung darin, set frame zu überschreiben.

Der folgende Code scheint nun notwendig zu sein, um sicherzustellen, dass er für alle iOS-Geräte, Controller, Bildschirmgrößen usw. funktioniert.

override public func layoutSubviews()
{
    super.layoutSubviews()
    self.preferredMaxLayoutWidth = self.bounds.width
    super.layoutSubviews()
}

override public var bounds: CGRect
{
    didSet
    {
        self.preferredMaxLayoutWidth = self.bounds.width
    }
}

override public var frame: CGRect
{
    didSet
    {
        self.preferredMaxLayoutWidth = self.frame.width
    }
}
27

Ich bin nicht sicher, ob ich Sie richtig verstehe und meine Lösung ist wirklich weit von der besten Lösung entfernt.

Ich habe eine Höhenbeschränkung für das ausgeschnittene Etikett hinzugefügt und diese Beschränkung mit dem Auslass der Zelle verbunden. Ich habe die layoutSubview-Methode überschrieben:

- (void) layoutSubviews {
    [super layoutSubviews];

    [self.badLabel setNeedsLayout];
    [self.badLabel layoutIfNeeded];

    self.badLabelHeightConstraint.constant = self.badLabel.intrinsicContentSize.height;
}

und Bratsche! Bitte probieren Sie diese Methode aus und sagen Sie mir die Ergebnisse. Danke.

2
Nikita Took

Ich habe einige Zeit damit verbracht, die Zelle einzuwickeln und sah so aus:

+--------------------------------------------------+
| This is my cell.contentView                      |
| +------+ +-----------------+ +--------+ +------+ |
| |      | |    multiline    | | title2 | |      | |
| | img  | +-----------------+ +--------+ | img  | |
| |      | +-----------------+ +--------+ |      | |
| |      | |     title3      | | title4 | |      | |
| +------+ +-----------------+ +--------+ +------+ |
|                                                  |
+--------------------------------------------------+

Nach vielen Versuchen kam ich zu einer Lösung, wie man Prototypzellen verwendet, die tatsächlich funktionieren.

Das Hauptproblem war, dass meine Labels vorhanden sein könnten oder nicht. Jedes Element sollte optional sein.

Zuallererst sollte unsere "Prototyp" -Zelle nur dann Layouts haben, wenn wir uns nicht in einer "echten" Umgebung befinden.

Daher habe ich das Flag 'heightCalculationInProgress' erstellt.

Wenn jemand (die dataSource von tableView) nach der Höhe berechnet, stellen wir der Blockzelle folgende Daten für die Prototypzelle zur Verfügung: Und dann bitten wir unsere Prototypzelle, den Inhalt zu gestalten und die Höhe herauszufiltern. Einfach.

Um einen iOS7-Fehler bei der Rekursion von layoutSubview zu vermeiden, gibt es eine Variable "layoutingLock"

- (CGFloat)heightWithSetupBlock:(nonnull void (^)(__kindof UITableViewCell *_Nonnull cell))block {
    block(self);

    self.heightCalculationInProgress = YES;

    [self setNeedsLayout];
    [self layoutIfNeeded];

    self.layoutingLock = NO;
    self.heightCalculationInProgress = NO;

    CGFloat calculatedHeight = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
    CGFloat height = roundf(MAX(calculatedHeight, [[self class] defaultHeight]));

    return height;
}

Dieser "Block" von außen könnte so aussehen:

[prototypeCell heightWithSetupBlock:^(__kindof UITableViewCell * _Nonnull cell) {
    @strongify(self);
    cell.frame = CGRectMake(0, 0, CGRectGetWidth(self.view.frame), 0);
    cell.dataObject = dataObject;
    cell.multilineLabel.text = @"Long text";
    // Include any data here
}];

Und jetzt beginnt der interessanteste Teil. Layouting:

- (void)layoutSubviews {
    if(self.layoutingLock){
        return;
    }

    // We don't want to do this layouting things in 'real' cells
    if (self.heightCalculationInProgress) {

    // Because of:
    // Support for constraint-based layout (auto layout)
    // If nonzero, this is used when determining -intrinsicContentSize for multiline labels

    // We're actually resetting internal constraints here. And then we could reuse this cell with new data to layout it correctly
        _multilineLabel.preferredMaxLayoutWidth = 0.f;
        _title2Label.preferredMaxLayoutWidth = 0.f;
        _title3Label.preferredMaxLayoutWidth = 0.f;
        _title4Label.preferredMaxLayoutWidth = 0.f;
    }

    [super layoutSubviews];

    // We don't want to do this layouting things in 'real' cells
    if (self.heightCalculationInProgress) {

        // Make sure the contentView does a layout pass here so that its subviews have their frames set, which we
        // need to use to set the preferredMaxLayoutWidth below.
        [self.contentView setNeedsLayout];
        [self.contentView layoutIfNeeded];

        // Set the preferredMaxLayoutWidth of the mutli-line bodyLabel based on the evaluated width of the label's frame,
        // as this will allow the text to wrap correctly, and as a result allow the label to take on the correct height.
        _multilineLabel.preferredMaxLayoutWidth = CGRectGetWidth(_multilineLabel.frame);
        _title2Label.preferredMaxLayoutWidth = CGRectGetWidth(_title2Label.frame);
        _title3Label.preferredMaxLayoutWidth = CGRectGetWidth(title3Label.frame);
        _title4Label.preferredMaxLayoutWidth = CGRectGetWidth(_title4Label.frame);
    }

    if (self.heightCalculationInProgress) {
        self.layoutingLock = YES;
    }
}
1
Aleksei Minaev

Omg, hatte gerade das gleiche Problem. Die Antwort von @Simon McLoughlin hat mir nicht geholfen. Aber

titleLabel.adjustsFontSizeToFitWidth = true

hat einen Zauber gemacht! Es funktioniert, ich weiß nicht warum, aber es tut es. Und ja, Ihr Label muss auch einen expliziten prefMaxLayoutWidth-Wert haben.

0
Mike Demidov