web-dev-qa-db-ja.com

AutoLayoutマルチラインUILabelはテキストを切り取ります

私は自動レイアウトを学ぼうとしていますが、最終的に私はこれが起こったときにハングしていると思いました。プロトタイプのセルとラベルで遊んでいます。私が持っているしようとしています

  • タイトル、titleLbl-写真の青いボックス
  • お金、moneyLbl-写真の緑色のボックス
  • 字幕/説明、subTitleLbl-写真の赤いボックス

これまでのところ私はこれを持っています:

iphone screenshot

私が達成したいことは:

  • 緑色のボックスは常に1行に完全に表示されるはずです
  • 緑のボックス内の値は、青のボックスに基づいて中央に配置する必要があります
  • 青いボックスは残りのスペース(2の間の水平方向の制約の場合は8ピクセル)を取り、必要に応じて複数の行に表示します
  • 赤いボックスは下にあり、必要な数の行を表示する必要があります。

ご覧のように、最後の行の例を除き、これはほぼすべて機能しています。私はこれを調査しようとしましたが、収集できることから、本質的なコンテンツサイズに関係しています。オンラインの多くの投稿はsetPreferredMaxLayoutWidthを設定することを推奨していますが、titleLblに対して複数の場所でこれを実行しましたが、効果はありませんでした。 180にハードコーディングした場合にのみ結果が得られましたが、テキストの上/下の他の青いボックスに空白/パディングも追加され、赤/青のボックス間のスペースが大幅に増加しました。

私の制約は次のとおりです。

題名: title constraints

お金: money constraints

字幕: subtitle constraints

他のテキストサンプルで実行したテストに基づいて、ストーリーボードに表示されている幅を使用しているように見えますが、それを修正しようとしても機能しません。

セルのivarを作成し、それを使用してセルの高さを作成しました。

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

セルのレイアウトサブビュー内でsetPreferredMaxLayoutWidthを設定しようとしました。

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

ログ:

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 

2つの要素間に8pxがあり、コンソールがこれを検証するので、これは私が期待するものです。青いボックスに追加するテキストの量(ストーリーボードと同じ)に関係なく、最初の行で完全に機能しますが、緑色のボックスを増やすと幅の計算が無効になります。他の質問からの回答に基づいて、tableView:willDisplayCellおよびtableView:heightForRowAtIndexPath:でこれを設定しようとしました。しかし、それが何であれレイアウトしません。

どんな助け、アイデアやコメントも大歓迎です

29

これを読んだ後、さらに良い答えを見つけました: http://johnszumski.com/blog/auto-layout-for-table-view-cells-with-dynamic-heights

UILabelサブクラスを作成して、layoutSubviewsをオーバーライドします。

- (void)layoutSubviews
{
    [super layoutSubviews];

    self.preferredMaxLayoutWidth = CGRectGetWidth(self.bounds);

    [super layoutSubviews];
}

これにより、preferredMaxLayoutWidthが常に正しいことが保証されます。

更新:

IOSをさらに数回リリースし、さらにユースケースをテストした結果、上記のケースではすべてのケースで十分ではないことがわかりました。 iOS 8(私が信じる)の時点では、いくつかの状況下では、画面がロードされる前にdidSetboundsだけが呼び出されました。

IOS 9では、UIVIewControllerでモーダルUITableViewを使用しているときに、layoutSubviewsset boundsの両方が呼び出されていたafterheightForRowAtIndexPath。多くのデバッグの後、唯一の解決策はset frameをオーバーライドすることでした。

以下のコードは、すべてのiOS、コントローラー、画面サイズなどで機能するようにするために必要なようです。

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

私があなたを正しく理解しているかどうかはわかりませんが、私の解決策は最高のものからはほど遠いですが、ここにあります:

カットされたラベルに高さの制約を追加し、その制約をセルのアウトレットに接続しました。 layoutSubviewメソッドをオーバーライドしました:

- (void) layoutSubviews {
    [super layoutSubviews];

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

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

そしてヴィオラ!その方法を試して結果を教えてください、ありがとう。

2
Nikita Took

次のように、セルをラップするのに少し時間を費やしました。

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

多くの試みの後、実際に動作しているプロトタイプセルを使用する方法のソリューションを見つけました。

主な問題は、私のラベルがそこにあるかもしれないし、ないかもしれないということでした。すべての要素はオプションである必要があります。

まず、「プロトタイプ」セルは、「実際の」環境にな​​いときにのみレイアウトする必要があります。

したがって、フラグ 'heightCalculationInProgress'を作成しました。

誰か(tableViewのdataSource)が高さを計算することを求めているとき、ブロックを使用してプロトタイプセルにデータを提供しています:そして、プロトタイプセルにコンテンツをレイアウトし、そこから高さを取得するように求めています。シンプル。

LayoutSubview再帰によるiOS7のバグを回避するために、「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;
}

外部からのこの「ブロック」は次のようになります。

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

そして今、最も興味深い部分を開始します。レイアウト:

- (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、ちょうど同じ問題がありました。 @Simon McLoughlinの答えは私を助けませんでした。だが

titleLabel.adjustsFontSizeToFitWidth = true

魔法をしました!動作しますが、理由はわかりませんが、動作します。はい、ラベルにも明示的なpreferredMaxLayoutWidth値が必要です。

0
Mike Demidov