web-dev-qa-db-ja.com

iOS AutoLayout複数行UILabel

次の質問は、この質問の続きです。

iOS:自動レイアウトの複数行UILabel

主なアイデアは、すべてのビューが「優先」(固有)サイズを示すようになっているため、AutoLayoutが適切に表示する方法を認識できるようにすることです。 UILabelは、ビューcannot自体が表示に必要なサイズを認識している状況の単なる例です。提供される幅によって異なります。

mwhussが指摘したように、setPreferredMaxLayoutWidthはラベルを複数行にまたがるトリックを行いました。しかし、それはここでの主要な質問ではありません。問題は、setPreferredMaxLayoutWidthへの引数として送信するこのwidth値をどこで、いつ取得するかです。

私は何とか合法的に見えるものを作ることができたので、私が何らかの形で間違っている場合は私を修正し、より良い方法を知っているなら教えてください。

UIViewの

-(CGSize) intrinsicContentSize

Self.frame.widthによるUILabelsのIsetPreferredMaxLayoutWidth.

UIViewControllerの

-(void) viewDidLayoutSubviews

は、メインビューのサブビューが画面上に存在する正確なフレームで指定される場所である最初のコールバックメソッドです。そのメソッドの内部から、サブビューを操作し、その固有のサイズを無効にして、UILabelsに指定された幅に基づいて複数の行に分割します。

59
ancajic

その幅を明確に定義している制約がある場合、UILabelが優先最大レイアウト幅のデフォルトの幅にならないのは面倒です。

Autolayoutでラベルを使用したほぼすべての場合、残りのレイアウトが実行されると、推奨される最大レイアウト幅はラベルの実際の幅になります。

そのため、これを自動的に行うために、setBounds:をオーバーライドするUILabelサブクラスを使用しました。ここで、スーパー実装を呼び出してから、まだそうでない場合、優先最大レイアウト幅を境界サイズ幅に設定します。

強調が重要です-優先最大レイアウトを設定すると、別のレイアウトパスが実行されるため、無限ループになる可能性があります。

42
jrturton

Advanced Auto Layout Toolbox の「複数行テキストの本質的なコンテンツサイズ」セクションの objc.io に、この質問に対する答えがあります。関連情報は次のとおりです。

UILabelとNSTextFieldの本質的なコンテンツサイズは、複数行のテキストではあいまいです。テキストの高さは、線の幅に依存しますが、これは制約を解決するときにまだ決定されていません。この問題を解決するために、両方のクラスにpreferredMaxLayoutWidthという新しいプロパティがあります。このプロパティは、固有のコンテンツサイズを計算するための最大行幅を指定します。

通常、この値は事前にわからないため、これを正しく行うには2段階のアプローチをとる必要があります。最初に自動レイアウトを機能させ、次にレイアウトパスで結果のフレームを使用して、優先最大幅を更新し、レイアウトを再度トリガーします。

View Controller内で使用するために提供するコード:

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

彼らの投稿を見てください、なぜレイアウトを2回行う必要があるのか​​についての詳細があります。

58
nevan king

更新

私の元の答えは有用であるように見えるので、以下に触れずに残しましたが、自分のプロジェクトでは、iOS 7とiOS 8のバグを回避するより信頼性の高いソリューションを見つけました https://github.com/nicksnyder/ios-cell-layout

元の答え

これは、iOS 7およびiOS 8で動作する完全なソリューションです

目的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

UIScrollView内の自動レイアウトUILabelが縦向きにレイアウトされていたが、横向きに回転してもUILabelの高さが再計算されない状況がありました。

おそらくpreferredMaxLayoutWidthが正しく設定されたため、@ jrturtonからの回答がこれを修正したことがわかりました。

使用したコードは次のとおりです。 Interface BuilderのCustomクラスを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

コメントを追加することは許可されていないので、回答として追加する必要があります。 jrturtonのバージョンは、問題のラベルのlayoutIfNeededを取得する前にupdateViewConstraintspreferredMaxLayoutWidthを呼び出す場合にのみ機能しました。 layoutIfNeededへの呼び出しなしでは、preferredMaxLayoutWidthは常にupdateViewConstraints0でした。それでも、setBounds:でチェックすると、常に目的の値になりました。正しいpreferredMaxLayoutWidthが設定されているとき、私はなんとか知りませんでした。 UILabelサブクラスでsetPreferredMaxLayoutWidth:をオーバーライドしますが、呼び出されませんでした。

要約すると、I:

  • ...サブクラス化されたUILabel
  • ... setBounds:をオーバーライドし、まだ設定されていない場合preferredMaxLayoutWidthCGRectGetWidth(bounds)に設定します
  • ...次の前に[super updateViewConstraints]を呼び出します
  • ...ラベルのサイズ計算で使用されるlayoutIfNeededを取得する前にpreferredMaxLayoutWidthを呼び出す

EDIT:この回避策は、機能している、または必要な場合があるようです。レイアウトプロセスが1回実行された後にpreferredMaxLayoutWidth0を返したため、ラベルの高さが正しく計算されないという問題(iOS 7/8)がありました。試行錯誤の後(そして、これを見つけた ブログエントリ )、UILabelを再び使用するように切り替えて、上下左右の自動レイアウト制約を設定しました。また、何らかの理由で、テキストの更新後にラベルの高さが正しく設定されました。

2
dergab

BoundingRectWithSizeを使用する

次のように目的の幅を測定することで、「\ n」を改行として使用していた従来のUITableViewCellにある2つの複数行ラベルの問題を解決しました。

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

次に、メソッドの呼び出しは次のように簡単でした:

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

別の答えが示唆するように、viewDidLayoutSubviewsをオーバーライドしようとしました:

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

これは機能しましたが、UIで表示され、「目に見えるちらつき」が発生しました。つまり、最初にラベルが2行の高さでレンダリングされ、次に1行だけの高さで再レンダリングされました。

これは私には受け入れられませんでした。

updateViewConstraintsをオーバーライドすることにより、より良い解決策を見つけました。

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

「視覚的なちらつき」を引き起こさなかったので、これは私にとってより良い解決策でした。

1
jbandi

きれいな解決策は、_rowcount = 0_を設定し、ラベルの高さの制約にプロパティを使用することです。次に、コンテンツが設定された後

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

-(void) updateViewConstraintsにはiOS 7.1以降の問題があります。

0
netshark1000

IOS 8では、セルをデキューして構成した後にcell.layoutIfNeeded()を呼び出すことにより、セル内の複数行ラベルレイアウトの問題を修正できます。この呼び出しはiOS 9では無害です。

Nick Snyderの答えをご覧ください。このソリューションは、 https://github.com/nicksnyder/ios-cell-layout/blob/master/CellLayout/TableViewController.Swift の彼のコードから取られました。

0
phatmann