web-dev-qa-db-ja.com

autolayoutでtableHeaderView(UITableView)の高さを設定するにはどうすればよいですか?

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

以下のスクリーンショットをご覧ください。

enter image description here

OverviewLabel(たとえば、「List overview information here。」というテキスト)には動的コンテンツがあるため、自動レイアウトを使用してサイズを変更し、スーパービューにしています。 tableHeaderViewを除き、すべてがうまくサイズ変更されています。tableHeaderViewは、階層構造のParalax Table Viewのすぐ下にあります。

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

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に変更するのは意味がありません。それは単にエラーを引き起こし、概念的には意味をなさないからです。

どんな助けでも本当に感謝されます!

EDIT 1:TomSwiftの提案のおかげで、私はそれを理解することができました。概要の高さを手動で計算する代わりに、次のように計算してから、以前のように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];

Edit 2:他の人が指摘したように、Edit 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;
}
83
Andrew Cross

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

このQ/AでこのAPIの使用に関する詳細な説明を提供します。

自動レイアウトですべてのサブビューに合うようにスーパービューのサイズを変更する方法?

35
TomSwift

私はこれと本当に戦いましたが、viewDidLoadにフレームが設定されていないため、viewDidLoadにセットアップを入れてもうまくいきませんでした。フォームプレゼンテーションで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
        }
    }
22
Daniel Galasko

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

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

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

17
p-sun

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

SystemLayoutSizeFittingSizeを取得するには、各更新後に正しいサイズを返すために、更新してから再追加する前に最初にテーブルヘッダービューを削除する必要がありました。

self.listTableView.tableHeaderView = nil;
[self.headerView removeFromSuperview];
3
Kim André Sand

このソリューションは、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

3
petrsyn

これはios10とXcode 8でうまくいきました

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
Ravi Randeria

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

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
    }
}
0
Sai kumar Reddy

一般的なAutoLayoutベースのUIViewをAL内部サブビュー構造で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.headerViewがUI回転で高さを変更する場合は、実装する必要があります

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

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
malex

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

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

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

0
meow2x

私の場合、viewDidLayoutSubviewsの方がうまく機能しました。 viewWillLayoutSubviewsは、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
wzbozon