webentwicklung-frage-antwort-db.com.de

mehrlinien-UILabel für iOS AutoLayout

Die folgende Frage ist eine Art Fortsetzung von dieser:

iOS: Mehrzeiliges UILabel im automatischen Layout

Die Hauptidee ist, dass in jeder Ansicht angegeben werden soll, dass sie die "bevorzugte" (intrinsische) Größe hat, damit AutoLayout wissen kann, wie sie richtig angezeigt wird. UILabel ist nur ein Beispiel für eine Situation, in der eine Ansicht kann selbst nicht wissen, welche Größe sie zur Anzeige benötigt. Es hängt davon ab, welche Breite zur Verfügung gestellt wird.

Wie mwhuss hervorgehoben hat, hat setPreferredMaxLayoutWidth den Trick gemacht, die Beschriftung über mehrere Zeilen zu verteilen. Aber das ist hier nicht die Hauptfrage. Die Frage ist, wo und wann ich diesen width - Wert bekomme, den ich als Argument an setPreferredMaxLayoutWidth sende.

Es ist mir gelungen, etwas zu schaffen, das echt aussieht, also korrigiere mich, wenn ich mich irre, und sag mir bitte, wenn du einen besseren Weg kennst. 

In den UIView's

-(CGSize) intrinsicContentSize

I setPreferredMaxLayoutWidth für meine UILabels gemäß self.frame.width.

UIViewController's

-(void) viewDidLayoutSubviews

ist die erste Rückmeldemethode, die ich kenne, wo Subviews der Hauptansicht mit ihren genauen Frames festgelegt werden, die sie auf dem Bildschirm bewohnen. Innerhalb dieser Methode bearbeite ich dann meine Unteransichten und mache ihre intrinsischen Größen ungültig, so dass UILabels in mehrere Zeilen unterteilt werden, basierend auf der Breite, die für sie festgelegt wurde.

59
ancajic

Es scheint ärgerlich zu sein, dass ein UILabel seine Breite für die bevorzugte maximale Layoutbreite nicht standardmäßig verwendet, wenn Einschränkungen vorliegen, die diese Breite für Sie eindeutig definieren. 

In fast jedem Fall, in dem ich Etiketten unter Autolayout verwendet habe, war die bevorzugte maximale Layoutbreite die tatsächliche Breite des Etiketts, nachdem der Rest meines Layouts ausgeführt wurde. 

Damit dies automatisch geschieht, habe ich eine UILabel-Unterklasse verwendet, die setBounds: überschreibt. Rufen Sie hier die Superimplementierung auf und setzen Sie falls dies nicht bereits der Fall ist / die bevorzugte maximale Layoutbreite auf die Breite der Begrenzungen. 

Die Betonung ist wichtig - wenn Sie das bevorzugte maximale Layout festlegen, wird ein weiterer Layout-Durchlauf ausgeführt, sodass Sie eine Endlosschleife erhalten. 

42
jrturton

Eine Antwort auf diese Frage gibt es auf objc.io im Abschnitt "Intrinsische Inhaltsgröße von mehrzeiligem Text" von Advanced Auto Layout Toolbox . Hier ist die relevante Info:

Die intrinsische Inhaltsgröße von UILabel und NSTextField ist für mehrzeiligen Text mehrdeutig. Die Höhe des Textes hängt von der Breite der Zeilen ab, die beim Lösen der Abhängigkeiten noch festgelegt werden muss. Um dieses Problem zu lösen, verfügen beide Klassen über eine neue Eigenschaft namens PreferMaxLayoutWidth, die die maximale Zeilenbreite für die Berechnung der intrinsischen Inhaltsgröße angibt.

Da wir diesen Wert normalerweise nicht im Voraus kennen, müssen wir einen zweistufigen Ansatz verfolgen, um diesen Wert zu erhalten. Zuerst lassen wir Auto Layout seine Arbeit erledigen, und dann verwenden wir den resultierenden Rahmen im Layout-Durchlauf, um die bevorzugte maximale Breite zu aktualisieren und das Layout erneut auszulösen.

Der Code, den sie für die Verwendung in einem View-Controller angeben:

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];
    myLabel.preferredMaxLayoutWidth = myLabel.frame.size.width;
    [self.view layoutIfNeeded];
}

Werfen Sie einen Blick auf ihren Beitrag. Es gibt weitere Informationen, warum das Layout zweimal gemacht werden muss.

57
nevan king

Update

Meine ursprüngliche Antwort scheint hilfreich zu sein, daher habe ich sie im Folgenden unangetastet gelassen. In meinen eigenen Projekten habe ich jedoch eine zuverlässigere Lösung gefunden, die Fehler in iOS 7 und iOS 8 umgeht. https://github.com/nicksnyder/ios-cell-layout

Ursprüngliche Antwort

Dies ist eine Komplettlösung, die für mich unter iOS 7 und iOS 8 funktioniert

Ziel C

@implementation AutoLabel

- (void)setBounds:(CGRect)bounds {
  if (bounds.size.width != self.bounds.size.width) {
    [self setNeedsUpdateConstraints];
  }
  [super setBounds:bounds];
}

- (void)updateConstraints {
  if (self.preferredMaxLayoutWidth != self.bounds.size.width) {
    self.preferredMaxLayoutWidth = self.bounds.size.width;
  }
  [super updateConstraints];
}

@end

Swift

import Foundation

class EPKAutoLabel: UILabel {

    override var bounds: CGRect {
        didSet {
            if (bounds.size.width != oldValue.size.width) {
                self.setNeedsUpdateConstraints();
            }
        }
    }

    override func updateConstraints() {
        if(self.preferredMaxLayoutWidth != self.bounds.size.width) {
            self.preferredMaxLayoutWidth = self.bounds.size.width
        }
        super.updateConstraints()
    }
}
32
Nick Snyder

Wir hatten eine Situation, in der ein UILabel mit automatischem Layout in einem UIScrollView im Hochformat angeordnet wurde, aber wenn er in Querformat gedreht wurde, wurde die Höhe des UILabel nicht neu berechnet.

Wir haben festgestellt, dass die Antwort von @jrturton das Problem behoben hat, vermutlich weil jetzt die PreferredMaxLayoutWidth richtig eingestellt ist.

Hier ist der Code, den wir verwendet haben. Setzen Sie die benutzerdefinierte Klasse im Interface Builder einfach auf CVFixedWidthMultiLineLabel.

CVFixedWidthMultiLineLabel.h

@interface CVFixedWidthMultiLineLabel : UILabel

@end 

CVFixedWidthMultiLineLabel.m

@implementation CVFixedWidthMultiLineLabel

// Fix for layout failure for multi-line text from
// http://stackoverflow.com/questions/17491376/ios-autolayout-multi-line-uilabel
- (void) setBounds:(CGRect)bounds {
    [super setBounds:bounds];

    if (bounds.size.width != self.preferredMaxLayoutWidth) {
        self.preferredMaxLayoutWidth = self.bounds.size.width;
    }
}

@end
9
Ben Clayton

Da ich keinen Kommentar hinzufügen darf, muss ich ihn als Antwort hinzufügen. Die Version von jrturton funktionierte nur für mich, wenn ich layoutIfNeeded in updateViewConstraints anrufe, bevor ich das preferredMaxLayoutWidth des betreffenden Labels erhalte Ohne den Aufruf von layoutIfNeeded war preferredMaxLayoutWidth immer 0 in updateViewConstraints. Und dennoch hatte es immer den gewünschten Wert, wenn es in setBounds: eingecheckt wurde. Ich habe es nicht geschafft zu erfahren, WENN die richtige preferredMaxLayoutWidth eingestellt wurde. Ich überschreibe setPreferredMaxLayoutWidth: in der UILabel-Unterklasse, aber es wurde nie aufgerufen.

Zusammengefasst ich:

  • ... das sublimierte UILabel
  • ... und überschreiben Sie setBounds: mit, falls noch nicht gesetzt, setzen Sie preferredMaxLayoutWidth auf CGRectGetWidth(bounds)
  • ... rufen Sie [super updateViewConstraints] vor dem folgenden auf
  • Rufen Sie layoutIfNeeded auf, bevor Sie preferredMaxLayoutWidth zur Berechnung der Etikettengröße erhalten

EDIT: Diese Problemumgehung scheint nur manchmal zu funktionieren oder wird benötigt. Ich hatte gerade ein Problem (iOS 7/8), bei dem die Etikettenhöhe nicht korrekt berechnet wurde, da preferredMaxLayoutWidth0 zurückgegeben hat, nachdem der Layoutprozess einmal ausgeführt wurde. Nach einigen Versuchen und Fehlern (und nachdem ich diesen Blog-Eintrag gefunden hatte ), wechselte ich wieder zu UILabel und legte nur die oberen, unteren, linken und rechten Einschränkungen für das automatische Layout fest. Und aus welchem ​​Grund auch immer, die Höhe des Labels wurde nach der Aktualisierung des Textes korrekt eingestellt.

2
dergab

Verwenden von boundingRectWithSize

Ich habe meinen Kampf mit zwei mehrzeiligen Beschriftungen in einer älteren UITableViewCell, die "\ n" als Zeilenumbruch verwendet hat, gelöst, indem ich die gewünschte Breite wie folgt gemessen habe:

- (CGFloat)preferredMaxLayoutWidthForLabel:(UILabel *)label
{
    CGFloat preferredMaxLayoutWidth = 0.0f;
    NSString *text = label.text;
    UIFont *font = label.font;
    if (font != nil) {
        NSMutableParagraphStyle *mutableParagraphStyle = [[NSMutableParagraphStyle alloc] init];
        mutableParagraphStyle.lineBreakMode = NSLineBreakByWordWrapping;

        NSDictionary *attributes = @{NSFontAttributeName: font,
                                     NSParagraphStyleAttributeName: [mutableParagraphStyle copy]};
        CGRect boundingRect = [text boundingRectWithSize:CGSizeZero options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil];
        preferredMaxLayoutWidth = ceilf(boundingRect.size.width);

        NSLog(@"Preferred max layout width for %@ is %0.0f", text, preferredMaxLayoutWidth);
    }


    return preferredMaxLayoutWidth;
}

Dann war das Aufrufen der Methode so einfach wie:

CGFloat labelPreferredWidth = [self preferredMaxLayoutWidthForLabel:textLabel];
if (labelPreferredWidth > 0.0f) {
    textLabel.preferredMaxLayoutWidth = labelPreferredWidth;
}
[textLabel layoutIfNeeded];

Wie in einer anderen Antwort vorgeschlagen, habe ich versucht, viewDidLayoutSubviews zu überschreiben:

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];
    _subtitleLabel.preferredMaxLayoutWidth = self.view.bounds.size.width - 40;
    [self.view layoutIfNeeded];
}

Dies funktionierte, war aber auf der Benutzeroberfläche sichtbar und verursachte ein "sichtbares Flimmern", d. H. Das Etikett wurde mit der Höhe von zwei Zeilen gerendert und dann mit der Höhe von nur einer Zeile wiedergegeben.

Das war für mich nicht akzeptabel.

Ich habe dann eine bessere Lösung gefunden, indem ich updateViewConstraints überschreibe: 

-(void)updateViewConstraints {
    [super updateViewConstraints];

    // Multiline-Labels and Autolayout do not work well together:
    // In landscape mode the width is still "portrait" when the label determines the count of lines
    // Therefore the preferredMaxLayoutWidth must be set
    _subtitleLabel.preferredMaxLayoutWidth = self.view.bounds.size.width - 40;
}

Dies war die bessere Lösung für mich, da es kein "visuelles Flackern" verursachte.

1
jbandi

Eine saubere Lösung ist, rowcount = 0 einzustellen und eine Eigenschaft für die Höhenbeschränkung Ihres Etiketts zu verwenden. Dann nachdem der Inhalt eingestellt ist, rufen Sie auf

CGSize sizeThatFitsLabel = [_subtitleLabel sizeThatFits:CGSizeMake(_subtitleLabel.frame.size.width, MAXFLOAT)];
_subtitleLabelHeightConstraint.constant = ceilf(sizeThatFitsLabel.height);

-(void) updateViewConstraints hat seit iOS 7.1 ein Problem.

0
netshark1000

In iOS 8 können Sie mehrzeilige Etikettenlayoutprobleme in einer Zelle beheben, indem Sie cell.layoutIfNeeded() aufrufen, nachdem Sie die Zelle entreißen und konfigurieren. Der Anruf ist in iOS 9 harmlos.

Siehe Nick Snyders Antwort. Diese Lösung wurde seinem Code unter https://github.com/nicksnyder/ios-cell-layout/blob/master/CellLayout/TableViewController.Swift entnommen. 

0
phatmann