自動レイアウトを使用してtableHeaderView(UITableView)の高さを設定するにはどうすればよいですか?


86

過去3、4時間、これで頭を壁にぶつけてきましたが、理解できないようです。フルスクリーンのUITableViewを内部に持つUIViewControllerがあり(画面上に他のものがあるため、UITableViewControllerを使用できません)、tableHeaderViewのサイズを自動レイアウトで変更したいと考えています。言うまでもなく、それは協力的ではありません。

以下のスクリーンショットを参照してください。

ここに画像の説明を入力してください

OverviewLabel(「ここに概要情報を一覧表示する」テキストなど)には動的コンテンツがあるため、自動レイアウトを使用してサイズを変更し、スーパービューにします。階層内の視差テーブルビューのすぐ下にあるtableHeaderViewを除いて、すべてのサイズを適切に変更できます。

そのヘッダービューのサイズを変更する唯一の方法は、次のコードを使用してプログラムで行うことです。

CGRect headerFrame = self.headerView.frame;
headerFrame.size.height = headerFrameHeight;
self.headerView.frame = headerFrame;
[self.listTableView setTableHeaderView:self.headerView];

この場合、headerFrameHeightは、次のようにtableViewHeaderの高さを手動で計算します(innerHeaderViewは白い領域、つまり2番目の「ビュー」です。headerViewはtableHeaderViewです)

CGFloat startingY = self.innerHeaderView.frame.origin.y + self.overviewLabel.frame.origin.y;
CGRect overviewSize = [self.overviewLabel.text
                       boundingRectWithSize:CGSizeMake(290.f, CGFLOAT_MAX)
                       options:NSStringDrawingUsesLineFragmentOrigin
                       attributes:@{NSFontAttributeName: self.overviewLabel.font}
                       context:nil];
CGFloat overviewHeight = overviewSize.size.height;
CGFloat overviewPadding = ([self.overviewLabel.text length] > 0) ? 10 : 0; // If there's no overviewText, eliminate the padding in the overall height.
CGFloat headerFrameHeight = ceilf(startingY + overviewHeight + overviewPadding + 21.f + 10.f);

手動計算は機能しますが、不格好で、将来状況が変わるとエラーが発生しやすくなります。私ができるようにしたいのは、他の場所でできるように、提供された制約に基づいてtableHeaderViewのサイズを自動変更することです。しかし、私の人生のために、私はそれを理解することはできません。

これについてSOにいくつかの投稿がありますが、明確なものはなく、私をさらに混乱させることになりました。ここにいくつかあります:

translatesAutoresizingMaskIntoConstraintsプロパティをNOに変更することは、実際には意味がありません。これは、エラーが発生するだけで、概念的には意味がないためです。

どんな助けでも本当にありがたいです!

編集1: トムスウィフトの提案のおかげで、私はそれを理解することができました。概要の高さを手動で計算する代わりに、次のように計算してから、以前のようにtableHeaderViewを再設定できます。

[self.headerView setNeedsLayout];
[self.headerView layoutIfNeeded];
CGFloat height = [self.innerHeaderView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + self.innerHeaderView.frame.origin.y; // adding the origin because innerHeaderView starts partway down headerView.

CGRect headerFrame = self.headerView.frame;
headerFrame.size.height = height;
self.headerView.frame = headerFrame;
[self.listTableView setTableHeaderView:self.headerView];

編集2:他の人が指摘しているように、編集1に投稿されたソリューションはviewDidLoadでは機能しないようです。ただし、viewWillLayoutSubviewsでは機能するようです。以下のサンプルコード:

// Note 1: The variable names below don't match the variables above - this is intended to be a simplified "final" answer.
// Note 2: _headerView was previously assigned to tableViewHeader (in loadView in my case since I now do everything programatically).
// Note 3: autoLayout code can be setup programatically in updateViewConstraints.
- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];

    [_headerWrapper setNeedsLayout];
    [_headerWrapper layoutIfNeeded];
    CGFloat height = [_headerWrapper systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;

    CGRect headerFrame = _headerWrapper.frame;
    headerFrame.size.height = height;
    _headerWrapper.frame = headerFrame;
    _tableView.tableHeaderView = _headerWrapper;
}

2
setTableHeaderViewXcode6では機能しません。問題は、セルがtableHeaderViewによってオーバーラップしていることです。ただし、Xcode5
DawnSong

@DawnSongは、答えを更新できるようにXcode6の解決策を知っていますか?
アンドリュークロス

1
何度も試した後、tableHeaderViewの代わりにUITableViewCellを使用しましたが、機能します。
DawnSong 2015

私も頭を壊しました!
Akshit Zaveri 2015年

真の完全な自動レイアウトソリューションはここに
malex 2016

回答:


35

このUIView systemLayoutSizeFittingSize:メソッドを使用して、ヘッダービューの最小境界サイズを取得する必要があります。

このQ / Aで、このAPIの使用についてさらに説明します。

自動レイアウトですべてのサブビューに合うようにスーパービューのサイズを変更するにはどうすればよいですか?


高さ= 0で戻ってきますが、これはUITableViewCellではないため、正しく構成したかどうかは100%わかりません。したがって、「systemLayoutSizeFittingSize」を呼び出すcontentViewはありません。私が試したこと:[self.headerView setNeedsLayout]; [self.headerView layoutIfNeeded]; CGFloatの高さ= [self.headerView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize] .height; CGRect headerFrame = self.headerView.frame; headerFrame.size.height =高さ; self.headerView.frame = headerFrame; [self.listTableView setTableHeaderView:self.headerView]; また、preferredMax ...が有効であることも確認しました。
アンドリュークロス

あはは!それを理解したので、実際の動的コンテンツがあるので、self.headerViewの代わりに[self.innerHeaderView systemLayoutSizeFittingSize ...]を実行する必要がありました。トンありがとう!
アンドリュークロス

それは機能しますが、私のアプリではわずかな高さの不一致があります。おそらく私のラベルが属性付きテキストと動的タイプを使用しているためです。
bio

23

私は本当にこれと戦い、フレームがviewDidLoadに設定されていないため、セットアップをviewDidLoadに挿入しても機能しませんでした。また、ヘッダーのカプセル化された自動レイアウトの高さが0に減少するという厄介な警告が大量に発生しました。 。フォームプレゼンテーションでtableViewを表示するときに、iPadでのみ問題に気づきました。

私にとって問題を解決したのは、viewDidLoadではなくviewWillLayoutSubviewsでtableViewHeaderを設定することでした。

func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        if tableView.tableViewHeaderView == nil {
            let header: MyHeaderView = MyHeaderView.createHeaderView()
            header.setNeedsUpdateConstraints()
            header.updateConstraintsIfNeeded()
            header.frame = CGRectMake(0, 0, CGRectGetWidth(tableView.bounds), CGFloat.max)
            var newFrame = header.frame
            header.setNeedsLayout()
            header.layoutIfNeeded()
            let newSize = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
            newFrame.size.height = newSize.height
            header.frame = newFrame
            self.tableView.tableHeaderView = header
        }
    }

最小限のコードで更新されたソリューションについては、これを試してください:stackoverflow.com/a/63594053/3933302
Sourabh Sharma

23

アニメーションの有無にかかわらず、自動レイアウトを使用してテーブルヘッダーのサイズを変更するエレガントな方法を見つけました。

これをViewControllerに追加するだけです。

func sizeHeaderToFit(tableView: UITableView) {
    if let headerView = tableView.tableHeaderView {
        let height = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
        var frame = headerView.frame
        frame.size.height = height
        headerView.frame = frame
        tableView.tableHeaderView = headerView
        headerView.setNeedsLayout()
        headerView.layoutIfNeeded()
    }
}

動的に変化するラベルに従ってサイズを変更するには:

@IBAction func addMoreText(sender: AnyObject) {
    self.label.text = self.label.text! + "\nThis header can dynamically resize according to its contents."
}

override func viewDidLayoutSubviews() {
    // viewDidLayoutSubviews is called when labels change.
    super.viewDidLayoutSubviews()
    sizeHeaderToFit(tableView)
}

制約の変更に応じてサイズ変更をアニメーション化するには:

@IBOutlet weak var makeThisTallerHeight: NSLayoutConstraint!

@IBAction func makeThisTaller(sender: AnyObject) {

    UIView.animateWithDuration(0.3) {
        self.tableView.beginUpdates()
        self.makeThisTallerHeight.constant += 20
        self.sizeHeaderToFit(self.tableView)
        self.tableView.endUpdates()
    }
}

これが実際に動作していることを確認するには、AutoResizingHeaderプロジェクトを参照してください。 https://github.com/p-sun/Swift2-iOS9-UI

AutoResizingHeader


ねえp-sun。素晴らしい例をありがとう。tableViewHeaderに問題があるようです。私はあなたの例と同じ制約を持たせようとしましたが、機能しませんでした。高さを上げたいラベルにどの程度正確に制約を追加する必要がありますか?提案をありがとう
Bonnke 2016

1
あなたは、あなたがに背の高いようにしたいラベルフック作り@IBOutlet makeThisTaller@IBAction fun makeThisTaller例のように。また、ラベルのすべての側面をtableViewHeaderに制約します(たとえば、上、下、左、右)。
p-sun

どうもありがとう!最後に、この行を追加することで解決しました。lblFeedDescription.preferredMaxLayoutWidth = lblFeedDescription.bounds.widthここで、labelは、サイズを大きくしたいラベルです。ありがとう!
Bonnke 2016

ありがとう!ViewControllerではなくView内でこれを行う場合は、viewDidLayoutSubviewsをオーバーライドする代わりに、layoutSubviewsをオーバーライドできます
Andy Weinstein

4

このソリューションは、tableHeaderViewのサイズを変更しviewDidLayoutSubviews()、他のいくつかの回答で私が持っていたメソッドの無限ループを回避します。

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    if let headerView = tableView.tableHeaderView {
        let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
        var headerFrame = headerView.frame

        // comparison necessary to avoid infinite loop
        if height != headerFrame.size.height {
            headerFrame.size.height = height
            headerView.frame = headerFrame
            tableView.tableHeaderView = headerView
        }
    }
}

この投稿も参照してください:https//stackoverflow.com/a/34689293/1245231


1
私もこの方法を使っています。制約をいじる必要がないので、それが最善だと思います。また、非常にコンパクトです。
bio

3

systemLayoutSizeFittingSizeを使用するソリューションは、ヘッダービューが各ビューの外観で1回だけ更新される場合に機能します。私の場合、ステータスの変更を反映するためにヘッダービューが複数回更新されました。ただし、systemLayoutSizeFittingSize:は常に同じサイズを報告します。つまり、最初の更新に対応するサイズです。

systemLayoutSizeFittingSizeを取得するには:更新のたびに正しいサイズを返すには、更新して再度追加する前に、まずテーブルヘッダービューを削除する必要がありました。

self.listTableView.tableHeaderView = nil;
[self.headerView removeFromSuperview];

2

これは、iOS10とXcode8で私のために働きました

func layoutTableHeaderView() {

    guard let headerView = tableView.tableHeaderView else { return }
    headerView.translatesAutoresizingMaskIntoConstraints = false

    let headerWidth = headerView.bounds.size.width;
    let temporaryWidthConstraints = NSLayoutConstraint.constraintsWithVisualFormat("[headerView(width)]", options: NSLayoutFormatOptions(rawValue: UInt(0)), metrics: ["width": headerWidth], views: ["headerView": headerView])

    headerView.addConstraints(temporaryWidthConstraints)

    headerView.setNeedsLayout()
    headerView.layoutIfNeeded()

    let headerSize = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
    let height = headerSize.height
    var frame = headerView.frame

    frame.size.height = height
    headerView.frame = frame

    self.tableView.tableHeaderView = headerView

    headerView.removeConstraints(temporaryWidthConstraints)
    headerView.translatesAutoresizingMaskIntoConstraints = true

}

2

ヘッダービューとフッターの両方で機能します。ヘッダーをフッターに置き換えるだけです。

func sizeHeaderToFit() {
    if let headerView = tableView.tableHeaderView {

        headerView.setNeedsLayout()
        headerView.layoutIfNeeded()

        let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
        var frame = headerView.frame
        frame.size.height = height
        headerView.frame = frame

        tableView.tableHeaderView = headerView
    }
}

1

iOS 12以降の場合、次の手順により、テーブルとヘッダーの両方で自動レイアウトが正しく機能するようになります。

  1. 最初にtableViewを作成し、次にヘッダーを作成します。
  2. ヘッダー作成コードの最後に、次の呼び出しを行います。
[headerV setNeedsLayout];
[headerV layoutIfNeeded];
  1. 向きが変更されたら、レイアウトのヘッダーを再度マークしてテーブルを再読み込みします。これは、向きの変更が報告された後、少し行う必要があります。
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 *NSEC_PER_SEC), dispatch_get_main_queue(), ^{

  [tableV.tableHeaderView setNeedsLayout];
  [tableV.tableHeaderView layoutIfNeeded];
  [tableV reloadData];});

0

私の場合viewDidLayoutSubviewsはうまくいきました。viewWillLayoutSubviewsaの白い線がtableView表示されます。また、headerViewオブジェクトがすでに存在するかどうかのチェックを追加しました。

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];

    if ( ! self.userHeaderView ) {
        // Setup HeaderView
        self.userHeaderView = [[[NSBundle mainBundle] loadNibNamed:@"SSUserHeaderView" owner:self options:nil] objectAtIndex:0];
        [self.userHeaderView setNeedsLayout];
        [self.userHeaderView layoutIfNeeded];
        CGFloat height = [self.userHeaderView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
        CGRect headerFrame = self.userHeaderView.frame;
        headerFrame.size.height = height;
        self.userHeaderView.frame = headerFrame;
        self.tableView.tableHeaderView = self.userHeaderView;

        // Update HeaderView with data
        [self.userHeaderView updateWithProfileData];
    }
}

0

UIViewとして任意のAL内部サブビュー構造で一般的なAutoLayoutベースを使用することはかなり可能tableHeaderViewです。

必要なのは、tableFooterView前に簡単に設定することだけです。

レッツは、self.headerViewいくつかの制約ベースですUIView

- (void)viewDidLoad {

    ........................

    self.tableView.tableFooterView = [UIView new];

    [self.headerView layoutIfNeeded]; // to set initial size

    self.tableView.tableHeaderView = self.headerView;

    [self.tableView.leadingAnchor constraintEqualToAnchor:self.headerView.leadingAnchor].active = YES;
    [self.tableView.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor].active = YES;
    [self.tableView.topAnchor constraintEqualToAnchor:self.headerView.topAnchor].active = YES;

    // and the key constraint
    [self.tableFooterView.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor].active = YES;
}

self.headerViewUI回転で高さを変更する場合は、実装する必要があります

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];

    [coordinator animateAlongsideTransition: ^(id<UIViewControllerTransitionCoordinatorContext> context) {
        // needed to resize header height
        self.tableView.tableHeaderView = self.headerView;
    } completion: NULL];
}

使用できます この目的のためにObjCカテゴリを

@interface UITableView (AMHeaderView)
- (void)am_insertHeaderView:(UIView *)headerView;
@end

@implementation UITableView (AMHeaderView)

- (void)am_insertHeaderView:(UIView *)headerView {

    NSAssert(self.tableFooterView, @"Need to define tableFooterView first!");

    [headerView layoutIfNeeded];

    self.tableHeaderView = headerView;

    [self.leadingAnchor constraintEqualToAnchor:headerView.leadingAnchor].active = YES;
    [self.trailingAnchor constraintEqualToAnchor:headerView.trailingAnchor].active = YES;
    [self.topAnchor constraintEqualToAnchor:headerView.topAnchor].active = YES;

    [self.tableFooterView.trailingAnchor constraintEqualToAnchor:headerView.trailingAnchor].active = YES;
}
@end

また、Swift拡張機能

extension UITableView {

    func am_insertHeaderView2(_ headerView: UIView) {

        assert(tableFooterView != nil, "Need to define tableFooterView first!")

        headerView.layoutIfNeeded()

        tableHeaderView = headerView

        leadingAnchor.constraint(equalTo: headerView.leadingAnchor).isActive = true
        trailingAnchor.constraint(equalTo: headerView.trailingAnchor).isActive = true
        topAnchor.constraint(equalTo: headerView.topAnchor).isActive = true

        tableFooterView?.trailingAnchor.constraint(equalTo: headerView.trailingAnchor).isActive = true
    }
}

0

このソリューションは私にとって完璧に機能します:

https://spin.atomicobject.com/2017/08/11/swift-extending-uitableviewcontroller/

UITableViewControllerを拡張します。ただし、UITableViewを使用しているだけの場合でも機能し、UITableViewControllerの代わりにUITableViewを拡張するだけです。メソッドを呼び出すsizeHeaderToFit()sizeFooterToFit()tableViewHeader高さを変更するイベントがあるときはいつでも。


0

この投稿からコピー

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    
    if let headerView = tableView.tableHeaderView {

        let height = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
        var headerFrame = headerView.frame
        
        //Comparison necessary to avoid infinite loop
        if height != headerFrame.size.height {
            headerFrame.size.height = height
            headerView.frame = headerFrame
            tableView.tableHeaderView = headerView
        }
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.