자동 레이아웃으로 모든 하위보기에 맞게 수퍼 뷰 크기를 조정하는 방법은 무엇입니까?


142

autolayout에 대한 나의 이해는 그것이 superview의 크기를 가지고 그것이 subview의 위치를 ​​계산하는 제약과 본질적인 크기에 기초한다는 것입니다.

이 과정을 되돌릴 수있는 방법이 있습니까? 구속 및 기본 크기의 기초에서 수퍼 뷰의 크기를 조정하고 싶습니다. 이것을 달성하는 가장 간단한 방법은 무엇입니까?

에 대한 헤더로 사용하는 Xcode에서 설계된보기가 있습니다 UITableView. 이보기에는 레이블과 버튼이 포함되어 있습니다. 라벨의 크기는 데이터에 따라 다릅니다. 구속 조건에 따라 레이블이 성공적으로 버튼을 아래로 눌렀거나 버튼과 수퍼 뷰 하단 사이에 구속 조건이있는 경우 레이블이 압축됩니다.

비슷한 질문이 몇 개 있지만 좋은 대답이 없습니다.


28
Tom Swifts가 귀하의 질문에 확실히 답변 했으므로 아래 답변 중 하나를 선택해야합니다. 모든 포스터는 당신을 돕기 위해 많은 시간을 보냈습니다.
David H

답변:


149

사용할 올바른 API UIView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize또는을 (를 ) 전달하는 것 UILayoutFittingExpandedSize입니다.

UIView자동 레이아웃을 사용 하는 일반적인 경우 제약 조건이 올바른 한 작동해야합니다. UITableViewCell예를 들어 행 높이를 결정 하기 위해 그것을 사용하려면 셀에 대해 호출 contentView하고 높이를 가져와야합니다.

하나 이상의 UILabel이 뷰에 여러 줄인 경우 추가 고려 사항이 있습니다. 이러한 경우 preferredMaxLayoutWidth레이블이 올바른을 제공하여 계산에 intrinsicContentSize사용될 속성을 올바르게 설정해야합니다 systemLayoutSizeFittingSize'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

@rdelmar-확실합니다. 내 대답에 예를 추가했습니다.
TomSwift

7
셀 하단에서 셀의 가장 낮은 하위 뷰 하단에 최종 수직 제약 조건을 추가 할 때까지 이것은 작동하지 않았습니다. 수직 구속 조건에는 셀 높이 계산에 성공하기 위해 셀과 그 내용 사이의 상단 및 하단 수직 간격이 포함되어야합니다.
Eric Baker

1
나는 그것을 작동시키기 위해 한 번 더 트릭이 필요했습니다. 그리고 @EricBaker의 팁이 마침내 그것을 손에 넣었습니다. 그 남자를 공유해 주셔서 감사합니다. 나는 sizingCell또는 에 대해 0이 아닌 크기를 얻었 습니다 contentView.
MkVal

1
올바른 높이를 얻지 못하는 사람은 sizingCell너비가 너비와 일치 하는지 확인하십시오 tableView.
MkVal

30

에릭 베이커 (Eric Baker)의 의견 은 뷰가 그 안에 배치 된 컨텐츠에 의해 크기를 결정하려면 그 안에 배치 된 컨텐츠가 높이를 높이기 위해 포함하는 뷰와 명시 적 관계를 가져야 한다는 핵심 아이디어를 알려주었습니다. (또는 너비) dynamic . "하위 뷰 추가"는 예상 한대로이 관계를 생성하지 않습니다. 컨테이너의 높이 및 / 또는 너비를 구동 할 서브 뷰를 선택해야합니다. 가장 일반적으로 전체 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
이것은 훌륭한 트릭이며 잘 알려져 있지 않습니다. 반복 : 내부 뷰의 높이가 고유하고 상단과 하단에 고정 된 경우 외부 뷰는 높이를 지정할 필요가 없으며 실제로 내용을 포옹합니다. 원하는 결과를 얻기 위해 내부보기에 대해 컨텐츠 압축 및 껴안기를 조정해야 할 수도 있습니다.
phatmann

이것은 돈이다! 내가 본 더 간결한 VFL 예제 중 하나입니다.
Evan R

이것은 내가 찾고있는 것입니다 (감사합니다 @John) 그래서 여기에
James

1
"컨테이너의 높이 및 / 또는 너비를 구동 할 서브 뷰를 선택해야합니다. 가장 일반적으로 전체 UI의 오른쪽 아래 모서리에 배치 한 UI 요소는 무엇입니까?" 이것이 나를위한 열쇠였습니다. 하나의 부모보기의 경우 하나의 자식보기가있어 오른쪽 아래에 고정하여 갑자기 부모보기에 크기를 부여하는 것은 자식보기에 있습니다. 감사!
Steven Elliott

1
너비를 높이기 위해 하나의보기를 선택하는 것은 내가 할 수없는 일입니다. 때로는 하나의 하위보기가 더 넓고 때로는 다른 하위보기가 더 넓습니다. 그 사건에 대한 아이디어가 있습니까?
Henning

3

구속 조건을 작성하고 인터페이스 빌더를 통해 연결하여이를 수행 할 수 있습니다.

설명 참조 : Auto_Layout_Constraints_in_Interface_Builder

raywenderlich 자동 레이아웃

AutolayoutPG 기사 제약 조건 기본 사항

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

이 Constraint 콘센트를 하위 뷰와 연결 Constraint 또는 Super Views Constraint도 연결하고 다음과 같은 요구 사항에 따라 설정하십시오.

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

나는 이것이 그것을 명확히하기를 바랍니다.


따라서 소스 코드에서 XCode로 작성된 제약 조건을 구성 할 수 있습니다. 그러나 문제는 슈퍼 뷰의 크기를 조정하도록 구성하는 방법입니다.
DAK

예 @DAK. 슈퍼 뷰 레이아웃을 확인하십시오. 수퍼 뷰에 구속 조건을 설정하고 하위 뷰가 증가 할 때마다 수퍼 뷰 구속 조건 높이가 자동으로 증가하도록 높이 구속 조건도 만듭니다.
chandan

이것은 또한 나를 위해 일했습니다. 크기가 지정된 셀 (높이 60px)을 고정시키는 데 도움이됩니다. 따라서 뷰가로드 될 때 IBOutlet의 높이 제한을 60 * x로 설정했습니다. 여기서 x는 셀 수입니다. 궁극적으로 스크롤보기에서 전체 내용을 볼 수 있습니다.
Sandy Chapman

-1

subview더 큰 내부의 경우에는이 작업을 수행 할 수 UIView있지만에 대해서는 자동으로 작동하지 않습니다 headerViews. a의 높이 headerView에 의해 반환 있는지에 의해 결정된다 tableView:heightForHeaderInSection:당신이 계산해야하므로 height에 기초 heightUILabel플러스 공간 UIButton과 하나가 padding당신이 필요. 다음과 같이해야합니다.

-(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 번호는이다 width의을 UILabel(에서 설정으로 Interface Builder)


불행히도 이것은 작동하지 않습니다. 수퍼 뷰의 "내용에 맞는 크기"는 예측 한대로 버튼의 하단을 수퍼 뷰에 연결하는 구속 조건을 제거합니다. 런타임시 레이블의 크기가 조정되고 버튼이 아래로 밀리지 만 수퍼 뷰의 크기는 자동으로 조정되지 않습니다.
DAK

@ DAK, 죄송합니다. 문제는 뷰가 테이블 뷰의 헤더라는 것입니다. 나는 당신의 상황을 오해했습니다. 버튼과 레이블을 둘러싼보기가 다른보기 안에 있다고 생각했지만 헤더로보기보다는 헤더로 사용하는 것처럼 들립니다. 따라서 원래 답변이 작동하지 않습니다. 나는 내가 생각하는 바에 대한 나의 대답을 바꿨다.
rdelmar

이것은 현재 구현과 비슷하지만 더 나은 방법이 있기를 바랐습니다. 헤더를 변경할 때 마다이 코드를 업데이트하지 않는 것이 좋습니다.
DAK

@Dak, 죄송합니다. 더 좋은 방법은 없다고 생각합니다. 테이블 뷰가 작동하는 방식 일뿐입니다.
rdelmar

내 대답을 참조하십시오. "더 나은 방법"은 UIView systemLayoutSizeFittingSize :를 사용하는 것입니다. 즉, 테이블 뷰에서 필요한 높이를 얻기 위해 뷰 또는 셀에서 전체 자동 레이아웃을 수행하는 것은 상당히 비쌉니다. 나는 복잡한 세포를 위해 그것을 할 것이지만 아마도 더 간단한 경우를 위해 당신과 같은 해결책으로 대체 될 것입니다.
TomSwift
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.