ios swift cell - UITableViewでの自動レイアウトを使用して動的なセルレイアウトと行の高さを可変





12 Answers

IOS8の場合、それは本当に簡単です:

override func viewDidLoad() {  
    super.viewDidLoad()

    self.tableView.estimatedRowHeight = 80
    self.tableView.rowHeight = UITableViewAutomaticDimension
}

または

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}

しかしIOS7の場合、キーは自動レイアウト後の高さを計算することです。

func calculateHeightForConfiguredSizingCell(cell: GSTableViewCell) -> CGFloat {
    cell.setNeedsLayout()
    cell.layoutIfNeeded()
    let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingExpandedSize).height + 1.0
    return height
}

重要

  • 複数の行ラベルがある場合は、 numberOfLines0設定することを忘れないでください。

  • label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)忘れないでください。

完全なサンプルコードはhereありhere

Swift 4.2のUITableViewAutomaticDimension変更しました。

指定 uitableviewcell 固定

スムーズなスクロールのパフォーマンスを維持しながら、各セルのコンテンツとサブビューで行の高さが決定されるようにするには、どのようにテーブルビューのUITableViewCell内の自動レイアウトを使用しますか?




@ smileyborgのiOS7ソリューションをカテゴリに入れました

私は@smileyborgによってこの巧妙な解決策をUICollectionViewCell+AutoLayoutDynamicHeightCalculationカテゴリにラップすることに決めました。

このカテゴリはまた、@ wildmonkeyの答え(nibからセルをロードし、 systemLayoutSizeFittingSize: CGRectZero

キャッシングは考慮されていませんが、今の私のニーズに合っています。 それをコピーして貼り付け、ハックしてください。

UICollectionViewCell + AutoLayoutDynamicHeightCalculation.h

#import <UIKit/UIKit.h>

typedef void (^UICollectionViewCellAutoLayoutRenderBlock)(void);

/**
 *  A category on UICollectionViewCell to aid calculating dynamic heights based on AutoLayout contraints.
 *
 *  Many thanks to @smileyborg and @wildmonkey
 *
 *  @see .com/questions/18746929/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights
 */
@interface UICollectionViewCell (AutoLayoutDynamicHeightCalculation)

/**
 *  Grab an instance of the receiving type to use in order to calculate AutoLayout contraint driven dynamic height. The method pulls the cell from a nib file and moves any Interface Builder defined contrainsts to the content view.
 *
 *  @param name Name of the nib file.
 *
 *  @return collection view cell for using to calculate content based height
 */
+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name;

/**
 *  Returns the height of the receiver after rendering with your model data and applying an AutoLayout pass
 *
 *  @param block Render the model data to your UI elements in this block
 *
 *  @return Calculated constraint derived height
 */
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width;

/**
 *  Directly calls `heightAfterAutoLayoutPassAndRenderingWithBlock:collectionViewWidth` assuming a collection view width spanning the [UIScreen mainScreen] bounds
 */
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block;

@end

UICollectionViewCell + AutoLayoutDynamicHeightCalculation.m

#import "UICollectionViewCell+AutoLayout.h"

@implementation UICollectionViewCell (AutoLayout)

#pragma mark Dummy Cell Generator

+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name
{
    UICollectionViewCell *heightCalculationCell = [[[NSBundle mainBundle] loadNibNamed:name owner:self options:nil] lastObject];
    [heightCalculationCell moveInterfaceBuilderLayoutConstraintsToContentView];
    return heightCalculationCell;
}

#pragma mark Moving Constraints

- (void)moveInterfaceBuilderLayoutConstraintsToContentView
{
    [self.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) {
        [self removeConstraint:constraint];
        id firstItem = constraint.firstItem == self ? self.contentView : constraint.firstItem;
        id secondItem = constraint.secondItem == self ? self.contentView : constraint.secondItem;
        [self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:firstItem
                                                                     attribute:constraint.firstAttribute
                                                                     relatedBy:constraint.relation
                                                                        toItem:secondItem
                                                                     attribute:constraint.secondAttribute
                                                                    multiplier:constraint.multiplier
                                                                      constant:constraint.constant]];
    }];
}

#pragma mark Height

- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block
{
    return [self heightAfterAutoLayoutPassAndRenderingWithBlock:block
                                            collectionViewWidth:CGRectGetWidth([[UIScreen mainScreen] bounds])];
}

- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width
{
    NSParameterAssert(block);

    block();

    [self setNeedsUpdateConstraints];
    [self updateConstraintsIfNeeded];

    self.bounds = CGRectMake(0.0f, 0.0f, width, CGRectGetHeight(self.bounds));

    [self setNeedsLayout];
    [self layoutIfNeeded];

    CGSize calculatedSize = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];

    return calculatedSize.height;

}

@end

使用例:

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    MYSweetCell *cell = [MYSweetCell heightCalculationCellFromNibWithName:NSStringFromClass([MYSweetCell class])];
    CGFloat height = [cell heightAfterAutoLayoutPassAndRenderingWithBlock:^{
        [(id<MYSweetCellRenderProtocol>)cell renderWithModel:someModel];
    }];
    return CGSizeMake(CGRectGetWidth(self.collectionView.bounds), height);
}

ありがたいことに私はiOS8でこのジャズをする必要はありませんが、それは今のところです!




@smileyborgが提案した解決策はほぼ完璧です。カスタムセルを持っていて、一つ以上必要な場合UILabel、動的な高さとを、その後systemLayoutSizeFittingSizeの自動レイアウトと組み合わせる方法が戻っaを有効にCGSizeZero使用すると、セルからそのcontentView(にすべてのセルの制約を移動しない限り、ここで@TomSwiftにより示唆されるように、スーパーのサイズを変更する方法autolayoutですべてのサブビューに合う?)。

これを行うには、カスタムUITableViewCell実装(@Adrianのおかげで)に次のコードを挿入する必要があります。

- (void)awakeFromNib{
    [super awakeFromNib];
    for (NSLayoutConstraint *cellConstraint in self.constraints) {
        [self removeConstraint:cellConstraint];
        id firstItem = cellConstraint.firstItem == self ? self.contentView : cellConstraint.firstItem;
        id seccondItem = cellConstraint.secondItem == self ? self.contentView : cellConstraint.secondItem;
        NSLayoutConstraint *contentViewConstraint =
        [NSLayoutConstraint constraintWithItem:firstItem
                                 attribute:cellConstraint.firstAttribute
                                 relatedBy:cellConstraint.relation
                                    toItem:seccondItem
                                 attribute:cellConstraint.secondAttribute
                                multiplier:cellConstraint.multiplier
                                  constant:cellConstraint.constant];
        [self.contentView addConstraint:contentViewConstraint];
    }
}

これと@smileyborgの答えを混ぜることはうまくいくはずです。




人々はまだこれに問題がある場合。私はAutolayoutとUITableViewsのAutolayoutを使ってAutolayoutをダイナミックセル高さで使うことについての簡単なブログ記事と、これをより抽象的で実装しやすくするためのオープンソースコンポーネントを書いています。https://github.com/Raizlabs/RZCellSizeManager




あなたのセルのレイアウトが良い限り。

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];

    return [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
}

アップデート:iOS 8で導入された動的なサイズ変更を使用する必要があります。




行の高さと推定行の高さの自動寸法を設定するには、自動寸法をセル/行の高さのレイアウトに有効にするために、次の手順を実行します。

  • テーブルビューのdataSourceとdelegateを割り当てて実装する
  • UITableViewAutomaticDimensionrowHeight&estimatedRowHeightに割り当てます。
  • delegate / dataSourceメソッドを実装する(つまりheightForRowAt、値UITableViewAutomaticDimensionを返す)

-

目的C:

// in ViewController.h
#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>

  @property IBOutlet UITableView * table;

@end

// in ViewController.m

- (void)viewDidLoad {
    [super viewDidLoad];
    self.table.dataSource = self;
    self.table.delegate = self;

    self.table.rowHeight = UITableViewAutomaticDimension;
    self.table.estimatedRowHeight = UITableViewAutomaticDimension;
}

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

    return UITableViewAutomaticDimension;
}

迅速:

@IBOutlet weak var table: UITableView!

override func viewDidLoad() {
    super.viewDidLoad()

    // Don't forget to set dataSource and delegate for table
    table.dataSource = self
    table.delegate = self

    // Set automatic dimensions for row height
    // Swift 4.2 onwards
    table.rowHeight = UITableView.automaticDimension
    table.estimatedRowHeight = UITableView.automaticDimension


    // Swift 4.1 and below
    table.rowHeight = UITableViewAutomaticDimension
    table.estimatedRowHeight = UITableViewAutomaticDimension

}



// UITableViewAutomaticDimension calculates height of label contents/text
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    // Swift 4.2 onwards
    return UITableView.automaticDimension

    // Swift 4.1 and below
    return UITableViewAutomaticDimension
}

UITableviewCellのラベルインスタンス

  • 行数を設定する= 0(&改行モード=末尾を切り捨てる)
  • スーパービュー/セルコンテナに対してすべての制約(上、下、左下)を設定します。
  • オプション:データがない場合でも、ラベルで覆われた最小垂直エリアをラベルにする場合は、ラベルの最小高さを設定します。

:動的長さのラベル(UIElements)がコンテンツサイズに応じて調整されている場合は、優先度を高くして圧縮/圧縮したいラベルに「コンテンツの抱きしめと圧縮抵抗の優先度」を調整します。




別の "解決策":この不満をすべてスキップし、代わりにUIScrollViewを使用して、UITableViewと同じ外観と感じる結果を得る。

スマイリーボーグが提案したような何かを構築しようとしていて、文字通り20時間以上もの不満を抱いていて、App Storeの多くの月間と3バージョンのリリースで失敗したのは、私にとって痛い "解決策"でした。

私の取り組みは、iOS 7のサポートが本当に必要な場合(私たちのために、それは不可欠です)、技術はあまりにも脆弱であり、あなたはあなたの髪を引き抜くでしょう。そして、UITableViewは、高度な行編集機能を使用している場合や、1000以上の "行"をサポートする必要がある場合を除いて、一般的に過剰な過労です(アプリ内では20行を超えることはありません)。

追加されたボーナスは、コードがUITableViewに付属しているデリゲートのいたずらと前後することに比べて、非常に単純なものになるということです。これは、viewOnLoadのコードの1つのループであり、エレガントで見やすく、管理が簡単です。

それを行う方法に関するいくつかのヒントがあります:

1)Storyboardまたはnibファイルを使用して、ViewControllerおよび関連するルートビューを作成します。

2)UIScrollViewをルートビューにドラッグします。

3)UIScrollViewがルートビュー全体を満たすように、トップビュー、ボトム、左および右の制約を制約に追加します。

4)UIScrollViewの内部にUIViewを追加し、それを「コンテナ」と呼んでください。上、下、左、右の制約をUIScrollView(その親)に追加します。キートリック:UIScrollViewとUIViewをリンクするために、「等しい幅」の制約を追加します。

「スクロールビューのスクロール可能なコンテンツの高さがあいまいです」というエラーが表示され、コンテナUIViewの高さが0ピクセルになるはずです。アプリが実行されているとき、どちらのエラーも問題ではないようです。

5)それぞれの "セル"のためのnibファイルとコントローラを作成します。UITableViewCellではなくUIViewを使用します。

5)ルートViewControllerでは、基本的にすべての "行"をコンテナUIViewに追加し、左端と右端をコンテナビューに連結し、コンテナの上端(最初の項目の場合)または上端前のセル。次に、最後のセルを容器の底部に連結する。

私たちにとって、各「行」はnibファイルにあります。コードは次のようになります。

class YourRootViewController {

    @IBOutlet var container: UIView! //container mentioned in step 4

    override func viewDidLoad() {

        super.viewDidLoad()

        var lastView: UIView?
        for data in yourDataSource {

            var cell = YourCellController(nibName: "YourCellNibName", bundle: nil)
            UITools.addViewToTop(container, child: cell.view, sibling: lastView)
            lastView = cell.view
            //Insert code here to populate your cell
        }

        if(lastView != nil) {
            container.addConstraint(NSLayoutConstraint(
                item: lastView!,
                attribute: NSLayoutAttribute.Bottom,
                relatedBy: NSLayoutRelation.Equal,
                toItem: container,
                attribute: NSLayoutAttribute.Bottom,
                multiplier: 1,
                constant: 0))
        }

        ///Add a refresh control, if you want - it seems to work fine in our app:
        var refreshControl = UIRefreshControl()
        container.addSubview(refreshControl!)
    }
}

UITools.addViewToTopのコードは次のとおりです。

class UITools {
    ///Add child to container, full width of the container and directly under sibling (or container if sibling nil):
    class func addViewToTop(container: UIView, child: UIView, sibling: UIView? = nil)
    {
        child.setTranslatesAutoresizingMaskIntoConstraints(false)
        container.addSubview(child)

        //Set left and right constraints so fills full horz width:

        container.addConstraint(NSLayoutConstraint(
            item: child,
            attribute: NSLayoutAttribute.Leading,
            relatedBy: NSLayoutRelation.Equal,
            toItem: container,
            attribute: NSLayoutAttribute.Left,
            multiplier: 1,
            constant: 0))

        container.addConstraint(NSLayoutConstraint(
            item: child,
            attribute: NSLayoutAttribute.Trailing,
            relatedBy: NSLayoutRelation.Equal,
            toItem: container,
            attribute: NSLayoutAttribute.Right,
            multiplier: 1,
            constant: 0))

        //Set vertical position from last item (or for first, from the superview):
        container.addConstraint(NSLayoutConstraint(
            item: child,
            attribute: NSLayoutAttribute.Top,
            relatedBy: NSLayoutRelation.Equal,
            toItem: sibling == nil ? container : sibling,
            attribute: sibling == nil ? NSLayoutAttribute.Top : NSLayoutAttribute.Bottom,
            multiplier: 1,
            constant: 0))
    }
}

これまでのところ私がこのアプローチで見つけた唯一の「落とし穴」は、スクロールするときにUITableViewがビューの上部に「フローティング」セクションヘッダーという素晴らしい機能を持っていることです。上記の解決策は、プログラミングを追加しない限りはできませんが、私たちの特別なケースでは、この機能は100%不可欠ではなく、誰も気付かれなくなりました。

セル間に仕切り線が必要な場合は、仕切り線のように見えるカスタム「セル」の下部に1ピクセル高いUIViewを追加します。

リフレッシュコントロールが機能するように、「バウンス」と「バウンスを垂直に」オンにしてください。そうすれば、テーブルビューのように見えます。

TableViewは、このソリューションではないフルスクリーンを満たしていない場合、コンテンツの下にある空の行と仕切りを示しています。しかし、個人的には、空の行が存在しない場合は好きです。可変セルの高さでは、空の行がある場合はいつもそれが "バギー"に見えます。

ここでは、他のプログラマーが20時間以上を無駄にする前に自分の投稿を読んで、自分のアプリでTable Viewで把握しようとしています。 :)




私は動的なビュー(設定ビューとコードによる制約)を使用しなければならず、私がpreferredMaxLayoutWidthを設定したいとき、ラベルの幅は0でした。だから私は間違ったセルの高さを持っています。

それから私は

[cell layoutSubviews];

実行前

[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];

その後、ラベルの幅は予想どおりで、動的な高さは右に計算されていました。




プログラムでレイアウトを行う場合は、Swiftでアンカーを使用するiOS 10について検討する必要があります。

3つのルール/ステップがあります

NUMBER 1:viewDidLoadのtableviewのこの2つのプロパティを設定します。最初はテーブルビューにセルの動的サイズが必要であることを伝えます。もう1つは、スクロールバーインジケータのサイズを計算させることですパフォーマンス。

    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.estimatedRowHeight = 100

NUMBER 2:これは、ビューではなくセルのcontentViewにサブビューを追加する必要があり、サブレイアウトを上と下に固定するためにlayoutsmarginguideを使用する必要があることが重要です。これはその実行例です。

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    setUpViews()
}

private func setUpViews() {

    contentView.addSubview(movieImageView)
    contentView.addSubview(descriptionLabel)
    let marginGuide = contentView.layoutMarginsGuide

    NSLayoutConstraint.activate([
        movieImageView.heightAnchor.constraint(equalToConstant: 80),
        movieImageView.widthAnchor.constraint(equalToConstant: 80),
        movieImageView.leftAnchor.constraint(equalTo: marginGuide.leftAnchor),
        movieImageView.topAnchor.constraint(equalTo: marginGuide.topAnchor, constant: 20),

        descriptionLabel.leftAnchor.constraint(equalTo: movieImageView.rightAnchor, constant: 15),
        descriptionLabel.rightAnchor.constraint(equalTo: marginGuide.rightAnchor),
        descriptionLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor, constant: -15),
        descriptionLabel.topAnchor.constraint(equalTo: movieImageView.topAnchor)

        ])
}

サブビューを追加してレイアウトを実行するメソッドを作成し、initメソッドで呼び出す。

番号3:方法を呼び出さないでください:

  override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    }

それを行うと、実装が上書きされます。

テーブルビューの動的セルには、この3つのルールに従ってください。

ここには動作する実装がありhttps://github.com/jamesrochabrun/MinimalViewController




私のケースでは、paddingはsectionHeaderとsectionFooterの高さのためでした。そこではstoryboardが最小1に変更できました。そこでviewDidLoadメソッドでは:

tableView.sectionHeaderHeight = 0
tableView.sectionFooterHeight = 0



@smileyborgが受け入れた回答に関して、私は見つけました

[cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]

制約があいまいである場合には信頼できないことがあります。以下のUIViewのヘルパーカテゴリを使用して、レイアウトエンジンが一方向の高さを計算するようにしてください。

-(CGFloat)systemLayoutHeightForWidth:(CGFloat)w{
    [self setNeedsLayout];
    [self layoutIfNeeded];
    CGSize size = [self systemLayoutSizeFittingSize:CGSizeMake(w, 1) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
    CGFloat h = size.height;
    return h;
}

w:はテーブルビューの幅




あなたのviewcontrollerにこれらの2つの関数を追加するだけで、あなたの問題を解決できます。ここで、listは、各行の文字列を含む文字列配列です。

 func tableView(_ tableView: UITableView, 
   estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
        tableView.rowHeight = self.calculateHeight(inString: list[indexPath.row])

    return (tableView.rowHeight) 
}

func calculateHeight(inString:String) -> CGFloat
{
    let messageString = input.text
    let attributes : [NSAttributedStringKey : Any] = [NSAttributedStringKey(rawValue: NSAttributedStringKey.font.rawValue) : UIFont.systemFont(ofSize: 15.0)]

    let attributedString : NSAttributedString = NSAttributedString(string: messageString!, attributes: attributes)

    let rect : CGRect = attributedString.boundingRect(with: CGSize(width: 222.0, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, context: nil)

    let requredSize:CGRect = rect
    return requredSize.height
}



Related