コンテンツに合わせてUITableViewのサイズを変更する


170

に質問UILabelが表示されUITableView、各行に複数の選択肢が表示される複数の選択肢の回答が表示されるアプリを作成しています。質問と回答はさまざまですので、これはUITableView動的に変化する必要があります。

sizeToFitテーブルの回避策を見つけたいのですが。テーブルのフレームがすべてのコンテンツの高さに設定されている場所。

誰でも私がこれを達成する方法についてアドバイスできますか?


:OSX上で同様のことを行うためにお探しの方のために、この質問を参照stackoverflow.com/questions/11322139/...
マイケルTeper

1
@MiMoこんにちは、「sizeToFit」アプローチの何が問題になっているのか説明していただけますか?:)
iamdavidlam

sizeToFit「仕事の周りに『』私が見つけるしたいと思います」。UIViewの- sizeToFitメソッドを試したことはありますか?
Slipp D. Thompson、2015

回答:


155

実際に自分で答えを見つけました。

私はちょうど新しいを作成するCGRectためtableView.frameheighttable.contentSize.height

それはの高さを設定UITableViewheight、そのコンテンツの。コードによってUIが変更されるため、メインスレッドで実行することを忘れないでください。

dispatch_async(dispatch_get_main_queue(), ^{
        //This code will run in the main thread:
        CGRect frame = self.tableView.frame;
        frame.size.height = self.tableView.contentSize.height;
        self.tableView.frame = frame;
    });

3
この方法で問題が発生する可能性があります。リストが大きい場合、フレームの高さが一部のセルをカットする可能性があります。バウンスを有効にし、下部にスクロールして一部のセルがビューの外に跳ね返るのを確認すると、これが発生することがわかります。
Whyo​​z

正確にどこに高さを指定しましたか?reloadDataを呼び出して、あとでサイズを変更しましたか?
James

27
このコードを配置するのに最適な場所はどこですか?私はそれをviewDidLoadで試しましたが、おそらくtableviewデリゲートメソッドがまだ起動されていなかったため、機能しませんでした。どのデリゲートメソッドが最適ですか?
カイル・クレッグ

4
私はこれをviewDidAppearで行いました、そしてそれはうまくいったようです。テーブルビューはセクションのヘッダーとフッターのために少しスペースを残すため、コンテンツよりも少し高くなる可能性があることに注意してください
Jameo

2
すべての最初のtableView cellForRowAtIndexPath呼び出しが完了した後に、viewDidAppearが実行したいものを配置するのに最適な場所であることがわかりました
rigdonmr

120

KVO、DispatchQueue、または制約を自分で設定しないSwift 5および4.2ソリューション。

このソリューションは、Gulzの回答に基づいています

1)のサブクラスを作成しますUITableView

import UIKit

final class ContentSizedTableView: UITableView {
    override var contentSize:CGSize {
        didSet {
            invalidateIntrinsicContentSize()
        }
    }

    override var intrinsicContentSize: CGSize {
        layoutIfNeeded()
        return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
    }
}

2)をUITableViewレイアウトに追加し、すべての側に制約を設定します。そのクラスをに設定しContentSizedTableViewます。

3)StoryboardはサブクラスintrinsicContentSizeを考慮していないため、いくつかのエラーが表示されます。これを修正するには、サイズインスペクターを開き、intrinsicContentSizeをプレースホルダー値にオーバーライドします。これは設計時のオーバーライドです。実行時には、ContentSizedTableViewクラスのオーバーライドを使用します


更新: Swift 4.2のコードが変更されました。以前のバージョンを使用している場合は、UIViewNoIntrinsicMetric代わりにUIView.noIntrinsicMetric


1
非常に良い答えです。おかげでうまくいきました。ただし、私の場合、のクラスをストーリーボードに変更する必要tableViewがあり IntrinsicTableViewました。テーブルビューを別のに埋め込む必要はありませんでしたUIView
dvp.petrov

そうだね。回答を更新しました。ステップ2は、あなたが言ったように読むことができます。もちろん私が設定するためのものtableViewIntrinsicTableView。また、追加のラッピングUIViewは必要ありません。もちろん、既存の親ビューを使用できます:)
fl034

1
Swift 4これは私にとってはうまくtableView.sizeToFit()
いきました

コンテンツが静的である場合、それは良い解決策になるかもしれません。私のアプローチでは、自動レイアウトを使用して、他のビューに動的コンテンツを含むtableViewを埋め込み、sizeToFit()呼び出す必要のある場所を
意識

2
素晴らしいありがとう!最後に、テーブルビューの親コンテンツとラップコンテンツを一致させます...
アンバーK

79

Swiftソリューション

次の手順を実行します:

1-ストーリーボードからテーブルの高さの制約を設定します。

2-ストーリーボードから高さ制約をドラッグし、ビューコントローラーファイルにそのための@IBOutletを作成します。

    @IBOutlet weak var tableHeight: NSLayoutConstraint!

3-次に、次のコードを使用してテーブルの高さを動的に変更できます。

override func viewWillLayoutSubviews() {
    super.updateViewConstraints()
    self.tableHeight?.constant = self.table.contentSize.height
}

更新

最後の行が切り取られた場合は、willDisplayセル関数でviewWillLayoutSubviews()を呼び出してみてください。

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    self.viewWillLayoutSubviews()
}

1
私のために働いた!! thnx
Pushpa Y

5
このソリューションでは、常にXcode 8.3の最後の行を切り捨てました。
ジェンC

@Jec C:私も対象外
KMC

私も働いた!!
Antony Raphel 2017年

1
これをcellForRowAtIndexPath:メソッドに配置する必要がありました。viewWillLayoutSubviewsメソッドに配置した場合、計算されたcontentSizeの高さは、実際のコンテンツの高さではなく、推定された高さに基づいています。
Manu Mateos

21

私はこれをiOS 7で試しましたが、うまくいきました

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.tableView sizeToFit];
}

3
UITableViewが動的である(つまり、情報がオンザフライで読み込まれる)場合は、viewDidLoadではなく、別の場所に配置する必要があります。私にとっては、それをcellForRowAtIndexPathに入れました。また、「tableView」をIBOutletプロパティの名前に変更して、古い「property tableView not found」エラーを回避する必要がありました。
jeremytripp 2014年

thx、ポップオーバー内のテーブルビューのサイズ変更に問題があり、動作しました
Zaporozhchenko Oleksandr

19

テーブルビューのcontentSizeプロパティにオブザーバーを追加し、それに応じてフレームサイズを調整します

[your_tableview addObserver:self forKeyPath:@"contentSize" options:0 context:NULL];

その後、コールバックで:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
    {
         CGRect frame = your_tableview.frame;
         frame.size = your_tableview.contentSize;
         your_tableview.frame = frame;
    }

これがお役に立てば幸いです。


2
もう1つ追加する必要があります。テーブルビューのオブザーバを削除する必要があります。セルの割り当てが解除されるとクラッシュするからです。
Mahesh Agrawal、2016

12

スクロールビュー内にテーブルビューがあり、それに応じてtableViewの高さを計算してサイズを変更する必要がありました。これらは私が取ったステップです:

0)UIViewをscrollViewに追加します(おそらく、この手順がなくても機能しますが、競合を回避するために行いました)-これは、テーブルビューのコンテナービューになります。この手順を実行する場合は、ビューの境界をtableviewのものに設定します。

1)UITableViewのサブクラスを作成します。

class IntrinsicTableView: UITableView {

    override var contentSize:CGSize {
        didSet {
            self.invalidateIntrinsicContentSize()
        }
    }

    override var intrinsicContentSize: CGSize {
        self.layoutIfNeeded()
        return CGSize(width: UIViewNoIntrinsicMetric, height: contentSize.height)
    }

}

2)StoryboardのテーブルビューのクラスをIntrinsicTableViewに設定:スクリーンショット:http : //joxi.ru/a2XEENpsyBWq0A

3)heightConstraintをテーブルビューに設定します。

4)テーブルのIBoutletをViewControllerにドラッグします

5)テーブルの高さ制約のIBoutletをViewControllerにドラッグします

6)このメソッドをViewControllerに追加します。

override func viewWillLayoutSubviews() {
        super.updateViewConstraints()
        self.yourTableViewsHeightConstraint?.constant = self.yourTableView.intrinsicContentSize.height
    }

お役に立てれば


あなたのサブクラスに基づいていますが、高さの制約の設定を必要としない私の答えを参照してください。
fl034 2018

@ fl034リンクを提供しますか?
Gulz 2018

@ fl034私は、私の場合はScrollViewである)あなたの要素がスクロールビューの中に埋め込まれたときに制約の使用を避けることができるとは思わない
Gulz

また、スクロールビュー内にテーブルビューがあり、完全に機能します:)
fl034

iOS 13 / Xcode 11で動作します。3日間の親切な

8

テーブルビューのコンテンツサイズの変更を自分で追跡したくない場合は、このサブクラスが役立つことがあります。

protocol ContentFittingTableViewDelegate: UITableViewDelegate {
    func tableViewDidUpdateContentSize(_ tableView: UITableView)
}

class ContentFittingTableView: UITableView {

    override var contentSize: CGSize {
        didSet {
            if !constraints.isEmpty {
                invalidateIntrinsicContentSize()
            } else {
                sizeToFit()
            }

            if contentSize != oldValue {
                if let delegate = delegate as? ContentFittingTableViewDelegate {
                    delegate.tableViewDidUpdateContentSize(self)
                }
            }
        }
    }

    override var intrinsicContentSize: CGSize {
        return contentSize
    }

    override func sizeThatFits(_ size: CGSize) -> CGSize {
        return contentSize
    }
}

1
以下のような迅速な3について、intrinsicContentSizeは、VARとなっているoverride public var intrinsicContentSize: CGSize { //... return someCGSize }
xaphod

私のために働いていません。まだテーブルの一番下に隠れています
Gulz


4

Swift 3、iOS 10.3

解決方法1: だけは入れself.tableview.sizeToFit()cellForRowAt indexPath機能。テーブルビューの高さを必要以上に高く設定してください。これは、tableviewの下にビューがない場合に適したソリューションです。ただし、使用している場合、下部のテーブルビュー制約は更新されません(解決策2を考え出したため、修正しようとしませんでした)。

例:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if let cell = tableView.dequeueReusableCell(withIdentifier: "TestCell", for: indexPath) as? TestCell {
        cell.configureCell(data: testArray[indexPath.row])
        self.postsTableView.sizeToFit()
        return cell
    }

    return UITableViewCell()
}

解決策2: ストーリーボードでテーブルビューの高さの制約を設定し、それをViewControllerにドラッグします。セルの平均の高さがわかっていて、配列に含まれる要素の数がわかっている場合は、次のようなことができます。

tableViewHeightConstraint.constant = CGFloat(testArray.count) * 90.0     // Let's say 90 is the average cell height

*編集:

私が試したすべてのソリューションの後、それらのすべてが何かを修正していましたが、完全ではありませんでしたが、これがこの問題を完全に説明して修正する答えです。


これは非常に単純な解決策で、私にとってはうまくいきました。ありがとう@Dorde Nilovic
R. Mohan

1

Mimoの回答Anooj VMの回答はどちらもすばらしいですが、リストが大きい場合は小さな問題があります。フレームの高さが一部のセルをカットする可能性があります。

そう。私は答えを少し変更しました:

dispatch_async(dispatch_get_main_queue()) {
    //This code will run in the main thread:
    CGFloat newHeight=self.tableView.contentSize.height;
    CGFloat screenHeightPermissible=(self.view.bounds.size.height-self.tableView.frame.origin.y);
    if (newHeight>screenHeightPermissible)
    {
        //so that table view remains scrollable when 'newHeight'  exceeds the screen bounds
        newHeight=screenHeightPermissible;
    }

    CGRect frame = self.tableView.frame;
    frame.size.height = newHeight;
    self.tableView.frame = frame;
}

1

AutoLayoutを使用する場合は、より良い方法があります。高さを決定する制約を変更します。テーブルのコンテンツの高さを計算し、制約を見つけて変更します。次に例を示します(テーブルの高さを決定する制約が、実際には "等しい"関係の高さ制約であると想定しています)。

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    for constraint in tableView.constraints {
        if constraint.firstItem as? UITableView == tableView {
            if constraint.firstAttribute == .height {
                constraint.constant = tableView.contentSize.height
            }
        }
    }
}

あなたはこれを改善することができます。tableviewに高さ<= 10000などの追加の制約を追加します。Controlキーを押しながらInterface Builderから.hファイルにドラッグして、この制約をビューコントローラのプロパティにします。その後、制約と検索を繰り返すことを回避できます。直接設定myTableHeightConstraint.constant = tableView.contentSize.height
RobP

1

これは、自動レイアウトを使用して、セクションが1つだけのテーブルビューで機能します。

func getTableViewContentHeight(tableView: UITableView) -> CGFloat {
        tableView.bounds = CGRect(x: 0, y: 0, width: 300, height: 40)
        let rows = tableView.numberOfRows(inSection: 0)
        var height = CGFloat(0)
        for n in 0...rows - 1 {
            height = height + tableView.rectForRow(at: IndexPath(row: n, section: 0)).height
        }
        return height
    }

自動レイアウトを設定するときにこの関数を呼び出します(ここのサンプルではSnapKitを使用していますが、アイデアはわかります)。

    let height = getTableViewContentHeight(tableView: myTableView)
    myTableView.snp.makeConstraints {
        ...
        ...
        $0.height.equalTo(height)
    }

UITableViewの高さをセルの合計の高さと同じにしたいだけです。セルをループして、セルの全体の高さを累積します。この時点ではテーブルビューのサイズはCGRect.zeroなので、セルで定義された自動レイアウトルールを尊重できるように境界を設定する必要があります。サイズは、十分に大きい任意の値に設定します。実際のサイズは、自動レイアウトシステムによって後で計算されます。


1

私は少し違った方法で行いました。実際、私のTableViewはscrollview内にあったため、高さの制約を0に設定する必要がありました。

次に、実行時に次の変更を加えました、

       func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
            self.viewWillLayoutSubviews()
       }
    
       override func viewWillLayoutSubviews() {
            super.updateViewConstraints()
             DispatchQueue.main.async {
               self.tableViewHeightConstraint?.constant = self.myTableView.contentSize.height
               self.view.layoutIfNeeded()
          }
       }

0

Anooj VMの回答の拡張として、コンテンツサイズが変更された場合にのみコンテンツサイズ更新するには、以下をお勧めします

このアプローチは、スクロールを適切に無効にしより大きなリスト回転サポートします。contentSizeの変更はメインスレッドでディスパッチされるため、dispatch_asyncは必要ありません。

- (void)viewDidLoad {
        [super viewDidLoad];
        [self.tableView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:NULL]; 
}


- (void)resizeTableAccordingToContentSize:(CGSize)newContentSize {
        CGRect superviewTableFrame  = self.tableView.superview.bounds;
        CGRect tableFrame = self.tableView.frame;
        BOOL shouldScroll = newContentSize.height > superviewTableFrame.size.height;
        tableFrame.size = shouldScroll ? superviewTableFrame.size : newContentSize;
        [UIView animateWithDuration:0.3
                                    delay:0
                                    options:UIViewAnimationOptionCurveLinear
                                    animations:^{
                            self.tableView.frame = tableFrame;
        } completion: nil];
        self.tableView.scrollEnabled = shouldScroll;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    if ([change[NSKeyValueChangeKindKey] unsignedIntValue] == NSKeyValueChangeSetting &&
        [keyPath isEqualToString:@"contentSize"] &&
        !CGSizeEqualToSize([change[NSKeyValueChangeOldKey] CGSizeValue], [change[NSKeyValueChangeNewKey] CGSizeValue])) {
        [self resizeTableAccordingToContentSize:[change[NSKeyValueChangeNewKey] CGSizeValue]];
    } 
}

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
    [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
    [self resizeTableAccordingToContentSize:self.tableView.contentSize]; }

- (void)dealloc {
    [self.tableView removeObserver:self forKeyPath:@"contentSize"];
}

0

Musa almatriのobjcバージョン

(void)viewWillLayoutSubviews
{
    [super updateViewConstraints];
    CGFloat desiredHeight = self.tableView.contentSize.height;
    // clamp desired height, if needed, and, in that case, leave scroll Enabled
    self.tableHeight.constant = desiredHeight;
    self.tableView.scrollEnabled = NO;
}

0

このカスタムを試すことができます AGTableView

ストーリーボードまたはプログラムを使用してTableViewの高さの制約を設定するには (このクラスは自動的に高さの制約をフェッチし、コンテンツビューの高さをテーブルビューの高さに設定します)。

class AGTableView: UITableView {

    fileprivate var heightConstraint: NSLayoutConstraint!

    override init(frame: CGRect, style: UITableViewStyle) {
        super.init(frame: frame, style: style)
        self.associateConstraints()
    }

    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.associateConstraints()
    }

    override open func layoutSubviews() {
        super.layoutSubviews()

        if self.heightConstraint != nil {
            self.heightConstraint.constant = self.contentSize.height
        }
        else{
            self.sizeToFit()
            print("Set a heightConstraint to Resizing UITableView to fit content")
        }
    }

    func associateConstraints() {
        // iterate through height constraints and identify

        for constraint: NSLayoutConstraint in constraints {
            if constraint.firstAttribute == .height {
                if constraint.relation == .equal {
                    heightConstraint = constraint
                }
            }
        }
    }
}

なお、いずれの問題は、その後の高さを設定した場合yourTableView.layoutSubviews()


0

fl034の回答に基づく。ただし、Xamarin.iOSユーザーの場合:

    [Register("ContentSizedTableView")]
    public class ContentSizedTableView : UITableView
    {
        public ContentSizedTableView(IntPtr handle) : base(handle)
        {
        }

        public override CGSize ContentSize { get => base.ContentSize; set { base.ContentSize = value; InvalidateIntrinsicContentSize(); } }
        public override CGSize IntrinsicContentSize
        {
            get
            {
                this.LayoutIfNeeded();
                return new CGSize(width: NoIntrinsicMetric, height: ContentSize.Height);
            }
        }
    }

0

私のSwift 5の実装では、tableViewの高さ制約をコンテンツのサイズ(contentSize.height)に設定しています。この方法は、自動レイアウトを使用していることを前提としています。このコードは、cellForRowAttableViewメソッド内に配置する必要があります。

tableView.heightAnchor.constraint(equalToConstant: tableView.contentSize.height).isActive = true

-1

テーブルを動的にする場合は、上記のようにテーブルの内容に基づいたソリューションを使用する必要があります。小さなテーブルを表示したいだけの場合は、コンテナービューを使用してUITableViewControllerを埋め込むことができます。UITableViewはコンテナーのサイズに従ってサイズ変更されます。

これにより、多くの計算とレイアウトの呼び出しが回避されます。


1
above答えがシャッフルされる可能性があるため、言葉は使用しないでください。
Nik Kov

-3

迅速な3のこれのためのMuソリューション:このメソッドをで呼び出すviewDidAppear

func UITableView_Auto_Height(_ t : UITableView)
{
        var frame: CGRect = t.frame;
        frame.size.height = t.contentSize.height;
        t.frame = frame;        
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.