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


142

autolayoutについての私の理解は、それがスーパービューのサイズと制約に基づくサイズとサブビューの位置を計算する固有のサイズを取ることです。

このプロセスを逆にする方法はありますか?制約と固有のサイズに基づいてスーパービューのサイズを変更したい。これを達成する最も簡単な方法は何ですか?

Xcodeで設計されたビューがあり、これをのヘッダーとして使用していUITableViewます。このビューには、ラベルとボタンが含まれています。ラベルのサイズはデータによって異なります。制約に応じて、ラベルはボタンを正常に押し下げます。または、ボタンとスーパービューの下部の間に制約がある場合、ラベルは圧縮されます。

同様の質問をいくつか見つけましたが、良い簡単な答えはありません。


28
トム・スウィフトがあなたの質問に確実に答えたので、あなたは以下の答えの一つを選択するべきです。すべてのポスターはあなたを助けるために膨大な時間を費やしました、今あなたはあなたの役割を果たし、あなたが一番好きな答えを選ぶべきです。
デビッドH

回答:


149

使用する正しいAPIはUIView systemLayoutSizeFittingSize:UILayoutFittingCompressedSizeまたはを渡すことUILayoutFittingExpandedSizeです。

UIViewautolayoutを使用する通常の場合、制約が正しい限り、これは機能するはずです。UITableViewCell(たとえば、行の高さを決定するために)それを使用したい場合は、セルに対して呼び出してcontentView高さを取得する必要があります。

ビューに複数行のUILabelが1つ以上ある場合は、さらに検討する必要があります。これらの場合preferredMaxLayoutWidth、ラベルintrinsicContentSizesystemLayoutSizeFittingSize's計算に使用される正しいを提供するようにプロパティを正しく設定することが不可欠です。

編集:リクエストにより、テーブルビューセルの高さ計算の例を追加

テーブルセルの高さの計算に自動レイアウトを使用するのは非常に効率的ではありませんが、特に複雑なレイアウトのセルがある場合は便利です。

上記で述べたように、複数行を使用している場合は、をラベルの幅UILabelに同期させることが不可欠preferredMaxLayoutWidthです。UILabelこれを行うには、カスタムサブクラスを使用します。

@implementation TSLabel

- (void) layoutSubviews
{
    [super layoutSubviews];

    if ( self.numberOfLines == 0 )
    {
        if ( self.preferredMaxLayoutWidth != self.frame.size.width )
        {
            self.preferredMaxLayoutWidth = self.frame.size.width;
            [self setNeedsUpdateConstraints];
        }
    }
}

- (CGSize) intrinsicContentSize
{
    CGSize s = [super intrinsicContentSize];

    if ( self.numberOfLines == 0 )
    {
        // found out that sometimes intrinsicContentSize is 1pt too short!
        s.height += 1;
    }

    return s;
}

@end

次に、heightForRowAtIndexPathを示す、意図的なUITableViewControllerサブクラスを示します。

#import "TSTableViewController.h"
#import "TSTableViewCell.h"

@implementation TSTableViewController

- (NSString*) cellText
{
    return @"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
}

#pragma mark - Table view data source

- (NSInteger) numberOfSectionsInTableView: (UITableView *) tableView
{
    return 1;
}

- (NSInteger) tableView: (UITableView *)tableView numberOfRowsInSection: (NSInteger) section
{
    return 1;
}

- (CGFloat) tableView: (UITableView *) tableView heightForRowAtIndexPath: (NSIndexPath *) indexPath
{
    static TSTableViewCell *sizingCell;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        sizingCell = (TSTableViewCell*)[tableView dequeueReusableCellWithIdentifier: @"TSTableViewCell"];
    });

    // configure the cell
    sizingCell.text = self.cellText;

    // force layout
    [sizingCell setNeedsLayout];
    [sizingCell layoutIfNeeded];

    // get the fitting size
    CGSize s = [sizingCell.contentView systemLayoutSizeFittingSize: UILayoutFittingCompressedSize];
    NSLog( @"fittingSize: %@", NSStringFromCGSize( s ));

    return s.height;
}

- (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath
{
    TSTableViewCell *cell = (TSTableViewCell*)[tableView dequeueReusableCellWithIdentifier: @"TSTableViewCell" ];

    cell.text = self.cellText;

    return cell;
}

@end

シンプルなカスタムセル:

#import "TSTableViewCell.h"
#import "TSLabel.h"

@implementation TSTableViewCell
{
    IBOutlet TSLabel* _label;
}

- (void) setText: (NSString *) text
{
    _label.text = text;
}

@end

そして、これがストーリーボードで定義された制約の写真です。ラベルには高さ/幅の制約がないことに注意してください-それらはラベルのから推測されintrinsicContentSizeます:

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


1
heightForRowAtIndexPathの実装の例を示すことができます:複数行のラベルを含むセルでこのメソッドを使用するとどうなりますか?私はそれをかなりいじりました、そしてそれを働かせていません。どのようにしてセルを取得しますか(特にセルがストーリーボードに設定されている場合)?それを機能させるにはどのような制約が必要ですか?
rdelmar 2013

@rdelmar-確かに。回答に例を追加しました。
TomSwift 2013

7
これは、セルの下部からセルの最も下のサブビューの下部に最後の垂直制約を追加するまで機能しませんでした。セルの高さの計算を成功させるには、垂直制約にセルとそのコンテンツの間の上下の垂直間隔を含める必要があるようです。
エリックベイカー

1
私はそれを機能させるためにもう一つのトリックが必要でした。そして@EricBakerのヒントがついにそれを釘付けにした。その男を共有してくれてありがとう。私は現在、sizingCellまたはそのいずれかに対してゼロ以外のサイズを取得しましたcontentView
MkVal 2015年

1
適切な高さを取得できない場合は、sizingCellの幅がの幅と一致していることを確認してくださいtableView
MkVal 2015年

30

エリックベイカーのコメントは、ビューのサイズをビュー内に配置されたコンテンツによって決定するために、ビュー内に配置されたコンテンツがビューを含むビューと明示的な関係を持ってビューの高さを駆動する必要があるという中心的なアイデアに私を導きました(または幅)動的。「サブビューの追加」は、想定されているようにこの関係を作成しません。コンテナーの高さや幅を制御するサブビューを選択する必要があります。最も一般的には、UI全体の右下隅に配置したUI要素。ポイントを説明するためのコードとインラインコメントを以下に示します。

スクロールビューを操作する人にとっては、入力した内容に基づいてサイズを動的に決定する(そしてこれをスクロールビューに伝達する)単一のコンテンツビューを中心に設計することが一般的であるため、これは特に価値があるかもしれません。幸運を祈ります。これが誰かの役に立てば幸いです。

//
//  ViewController.m
//  AutoLayoutDynamicVerticalContainerHeight
//

#import "ViewController.h"

@interface ViewController ()
@property (strong, nonatomic) UIView *contentView;
@property (strong, nonatomic) UILabel *myLabel;
@property (strong, nonatomic) UILabel *myOtherLabel;
@end

@implementation ViewController

- (void)viewDidLoad
{
    // INVOKE SUPER
    [super viewDidLoad];

    // INIT ALL REQUIRED UI ELEMENTS
    self.contentView = [[UIView alloc] init];
    self.myLabel = [[UILabel alloc] init];
    self.myOtherLabel = [[UILabel alloc] init];
    NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(_contentView, _myLabel, _myOtherLabel);

    // TURN AUTO LAYOUT ON FOR EACH ONE OF THEM
    self.contentView.translatesAutoresizingMaskIntoConstraints = NO;
    self.myLabel.translatesAutoresizingMaskIntoConstraints = NO;
    self.myOtherLabel.translatesAutoresizingMaskIntoConstraints = NO;

    // ESTABLISH VIEW HIERARCHY
    [self.view addSubview:self.contentView]; // View adds content view
    [self.contentView addSubview:self.myLabel]; // Content view adds my label (and all other UI... what's added here drives the container height (and width))
    [self.contentView addSubview:self.myOtherLabel];

    // LAYOUT

    // Layout CONTENT VIEW (Pinned to left, top. Note, it expects to get its vertical height (and horizontal width) dynamically based on whatever is placed within).
    // Note, if you don't want horizontal width to be driven by content, just pin left AND right to superview.
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_contentView]" options:0 metrics:0 views:viewsDictionary]]; // Only pinned to left, no horizontal width yet
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_contentView]" options:0 metrics:0 views:viewsDictionary]]; // Only pinned to top, no vertical height yet

    /* WHATEVER WE ADD NEXT NEEDS TO EXPLICITLY "PUSH OUT ON" THE CONTAINING CONTENT VIEW SO THAT OUR CONTENT DYNAMICALLY DETERMINES THE SIZE OF THE CONTAINING VIEW */
    // ^To me this is what's weird... but okay once you understand...

    // Layout MY LABEL (Anchor to upper left with default margin, width and height are dynamic based on text, font, etc (i.e. UILabel has an intrinsicContentSize))
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_myLabel]" options:0 metrics:0 views:viewsDictionary]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[_myLabel]" options:0 metrics:0 views:viewsDictionary]];

    // Layout MY OTHER LABEL (Anchored by vertical space to the sibling label that comes before it)
    // Note, this is the view that we are choosing to use to drive the height (and width) of our container...

    // The LAST "|" character is KEY, it's what drives the WIDTH of contentView (red color)
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_myOtherLabel]-|" options:0 metrics:0 views:viewsDictionary]];

    // Again, the LAST "|" character is KEY, it's what drives the HEIGHT of contentView (red color)
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_myLabel]-[_myOtherLabel]-|" options:0 metrics:0 views:viewsDictionary]];

    // COLOR VIEWS
    self.view.backgroundColor = [UIColor purpleColor];
    self.contentView.backgroundColor = [UIColor redColor];
    self.myLabel.backgroundColor = [UIColor orangeColor];
    self.myOtherLabel.backgroundColor = [UIColor greenColor];

    // CONFIGURE VIEWS

    // Configure MY LABEL
    self.myLabel.text = @"HELLO WORLD\nLine 2\nLine 3, yo";
    self.myLabel.numberOfLines = 0; // Let it flow

    // Configure MY OTHER LABEL
    self.myOtherLabel.text = @"My OTHER label... This\nis the UI element I'm\narbitrarily choosing\nto drive the width and height\nof the container (the red view)";
    self.myOtherLabel.numberOfLines = 0;
    self.myOtherLabel.font = [UIFont systemFontOfSize:21];
}

@end

autolayout.pngですべてのサブビューに合わせてスーパービューのサイズを変更する方法


3
これは素晴らしいトリックで、よく知られていません。繰り返します。内部ビューに固有の高さがあり、上部と下部に固定されている場合、外部ビューは高さを指定する必要がなく、実際にはコンテンツを抱きしめます。必要な結果を得るために、コンテンツの圧縮と内部ビューのハグを調整する必要がある場合があります。
ファットマン、2015

これはお金です!私が見た中でより簡潔なVFLの例の1つ。
エヴァンR

これは私が探していたものです(ありがとう@John)。これのSwiftバージョンをここに
James

1
「コンテナーの高さや幅を制御するサブビューを選択する必要があります。最も一般的には、全体的なUIの右下隅に配置したUI要素であれば何でも」..私はPureLayoutを使用しています。これが私にとって鍵でした。1つの親ビューの場合、1つの子ビューでは、子ビューに座って右下にピン留めすると、突然親ビューのサイズが決まります。ありがとう!
Steven Elliott

1
幅を調整するために1つのビューを選択することは、まさに私ができないことです。1つのサブビューが広い場合もあれば、別のサブビューが広い場合もあります。その場合のアイデアはありますか?
ヘニング

3

これを行うには、制約を作成し、それをインターフェイスビルダーを介して接続します。

説明を参照してください:Auto_Layout_Constraints_in_Interface_Builder

raywenderlich開始自動レイアウト

AutolayoutPG Articles制約の基礎

@interface ViewController : UIViewController {
    IBOutlet NSLayoutConstraint *leadingSpaceConstraint;
    IBOutlet NSLayoutConstraint *topSpaceConstraint;
}
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *leadingSpaceConstraint;

このConstraintアウトレットをサブビューConstraintに接続するか、スーパービューConstraintも接続し、このような要件に従って設定します

 self.leadingSpaceConstraint.constant = 10.0;//whatever you want to assign

これで明らかになることを願っています。


したがって、ソースコードからXCodeで作成された制約を構成することが可能です。しかし問題は、スーパービューのサイズを変更するようにそれらを構成する方法です。
DAK、2013

はい@DAK スーパービューのレイアウトについて確認してください。制限をスーパービューに配置して高さを制限し、サブビューが増えるたびに自動的にスーパービューの制限の高さを増やします。
chandan 2013

これも私にとってはうまくいきました。サイズが固定されたセル(高さ60px)があると役立ちます。したがって、ビューが読み込まれるときに、IBOutletの高さの制約を60 * xに設定しました。ここで、xはセルの数です。最終的には、全体を見ることができるように、スクロールビューでこれを使用することになるでしょう。
Sandy Chapman

-1

これはsubview、より大きな内部の通常の場合に実行できますUIViewが、の場合は自動的に機能しませんheaderViews。の高さはheaderViewによって返される値によって決まるため、との必要なスペースのプラスに基づいtableView:heightForHeaderInSection:てを計算する必要があります。あなたはこのようなことをする必要があります:heightheightUILabelUIButtonpadding

-(CGFloat)tableView:(UITableView *)tableView 
          heightForHeaderInSection:(NSInteger)section {
    NSString *s = self.headeString[indexPath.section];
    CGSize size = [s sizeWithFont:[UIFont systemFontOfSize:17] 
                constrainedToSize:CGSizeMake(281, CGFLOAT_MAX)
                    lineBreakMode:NSLineBreakByWordWrapping];
    return size.height + 60;
}

ここでheaderStringあなたが埋めるために好きな文字列でありUILabel、および281番号があるwidthUILabel(でセットアップなどInterface Builder


残念ながら、これは機能しません。スーパービューの「コンテンツに合わせたサイズ」は、ボタンの下部をスーパービューに接続している制約を、予想どおりに削除します。実行時にラベルのサイズが変更され、ボタンが押し下げられますが、スーパービューは自動的にサイズ変更されません。
DAK 2013

@DAK、申し訳ありませんが、問題はビューがテーブルビューのヘッダーであるということです。私はあなたの状況を誤解しました。ボタンとラベルを囲むビューは別のビューの内側にあると思っていましたが、それをヘッダーとして使用しているようです(ヘッダー内のビューではありません)。だから、私の元の答えは機能しません。うまくいくはずだと思うことに答えを変えました。
rdelmar 2013

これは私の現在の実装に似ていますが、もっと良い方法があることを望んでいました。ヘッダーを変更するたびにこのコードを更新することは避けたいです。
DAK、2013

@ダックさん、申し訳ありませんが、もっと良い方法はないと思います。それは、テーブルビューが機能する方法です。
rdelmar 2013

私の答えを見てください。「より良い方法」は、UIView systemLayoutSizeFittingSize:を使用することです。とはいえ、テーブルビューで必要な高さを取得するためだけにビューまたはセルで完全な自動レイアウトを実行すると、かなりコストがかかります。私は複雑なセルに対してそれを行いますが、おそらくより単純なケースではあなたのようなソリューションにフォールバックします。
TomSwift 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.