Làm cách nào để thay đổi kích thước giám sát để phù hợp với tất cả các cuộc phỏng vấn với autolayout?


142

Sự hiểu biết của tôi về tự động thanh toán là nó lấy kích thước của giám sát và dựa trên các ràng buộc và kích thước nội tại mà nó tính toán các vị trí của các cuộc phỏng vấn.

Có cách nào để đảo ngược quá trình này? Tôi muốn thay đổi kích thước giám sát trên cơ sở các ràng buộc và kích thước nội tại. Cách đơn giản nhất để đạt được điều này là gì?

Tôi có chế độ xem được thiết kế trong Xcode mà tôi sử dụng làm tiêu đề UITableView. Chế độ xem này bao gồm nhãn và nút. Kích thước của nhãn khác nhau tùy thuộc vào dữ liệu. Tùy thuộc vào các ràng buộc, nhãn sẽ đẩy nút xuống thành công hoặc nếu có một ràng buộc giữa nút và dưới cùng của giám sát, nhãn được nén.

Tôi đã tìm thấy một vài câu hỏi tương tự nhưng chúng không có câu trả lời hay và dễ.


28
Bạn nên chọn một trong những câu trả lời dưới đây, vì chắc chắn Tom Swifts đã trả lời câu hỏi của bạn. Tất cả các áp phích đã dành một lượng lớn thời gian để giúp bạn, bây giờ bạn nên thực hiện phần của mình và chọn câu trả lời bạn thích nhất.
David H

Câu trả lời:


149

API chính xác để sử dụng là UIView systemLayoutSizeFittingSize:, vượt qua UILayoutFittingCompressedSizehoặc UILayoutFittingExpandedSize.

Đối với một người bình thường UIViewsử dụng tính năng tự động thanh toán, điều này sẽ chỉ hoạt động miễn là các ràng buộc của bạn là chính xác. Nếu bạn muốn sử dụng nó trên một UITableViewCell(để xác định chiều cao hàng chẳng hạn) thì bạn nên gọi nó dựa vào ô của bạn contentViewvà lấy chiều cao.

Cân nhắc thêm tồn tại nếu bạn có một hoặc nhiều UILabel theo quan điểm của bạn là đa dòng. Đối với những điều này, điều bắt buộc là thuộc preferredMaxLayoutWidthtính phải được đặt chính xác sao cho nhãn cung cấp chính xác intrinsicContentSize, sẽ được sử dụng trong systemLayoutSizeFittingSize'stính toán.

EDIT: theo yêu cầu, thêm ví dụ về tính toán chiều cao cho ô xem bảng

Sử dụng tính năng tự động thanh toán để tính toán chiều cao của ô bảng không phải là siêu hiệu quả nhưng chắc chắn sẽ thuận tiện, đặc biệt nếu bạn có một ô có bố cục phức tạp.

Như tôi đã nói ở trên, nếu bạn đang sử dụng đa dòng UILabel, bắt buộc phải đồng bộ hóa preferredMaxLayoutWidthvới chiều rộng nhãn. Tôi sử dụng một UILabellớp con tùy chỉnh để làm điều này:

@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

Đây là một lớp con UITableViewControll được trình bày thể hiện heightForRowAtIndexPath:

#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

Một ô tùy chỉnh đơn giản:

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

@implementation TSTableViewCell
{
    IBOutlet TSLabel* _label;
}

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

@end

Và, đây là hình ảnh về các ràng buộc được xác định trong Bảng phân cảnh. Lưu ý rằng không có ràng buộc về chiều cao / chiều rộng trên nhãn - chúng bị suy ra từ nhãn intrinsicContentSize:

nhập mô tả hình ảnh ở đây


1
Bạn có thể đưa ra một ví dụ về việc triển khai heightForRowAtIndexPath: sẽ như thế nào khi sử dụng phương thức này với một ô chứa nhãn nhiều dòng không? Tôi đã làm hỏng nó khá nhiều, và đã không làm cho nó hoạt động. Làm thế nào để bạn có được một ô (đặc biệt là nếu ô được thiết lập trong bảng phân cảnh)? Những hạn chế nào bạn cần để làm cho nó hoạt động?
ndelmar

@rdelmar - chắc chắn rồi. Tôi đã thêm một ví dụ cho câu trả lời của tôi.
TomSwift

7
Điều này không hiệu quả với tôi cho đến khi tôi thêm một ràng buộc dọc cuối cùng từ dưới cùng của ô vào cuối của khung nhìn thấp nhất trong ô. Dường như các ràng buộc dọc phải bao gồm khoảng cách dọc trên và dưới giữa ô và nội dung của nó để tính toán chiều cao của ô để thành công.
Eric Baker

1
Tôi chỉ cần thêm một mẹo để làm cho nó hoạt động. Và mẹo của @EricBaker cuối cùng cũng đóng đinh nó. Cảm ơn đã chia sẻ người đàn ông đó. Bây giờ tôi có kích thước khác không so với sizingCellhoặc của nó contentView.
MkVal

1
Đối với những người gặp khó khăn khi không có chiều cao phù hợp, hãy đảm bảo sizingCellchiều rộng của bạn phù hợp với chiều rộng của bạn tableView.
MkVal

30

Nhận xét của Eric Baker đưa tôi đến ý tưởng cốt lõi rằng để một khung nhìn có kích thước của nó được xác định bởi nội dung được đặt trong đó, thì nội dung được đặt trong đó phải có mối quan hệ rõ ràng với chế độ xem có chứa để tăng chiều cao của nó (hoặc chiều rộng) một cách linh hoạt . "Thêm subview" không tạo mối quan hệ này như bạn có thể giả định. Bạn phải chọn chế độ xem phụ nào sẽ điều khiển chiều cao và / hoặc chiều rộng của vùng chứa ... phổ biến nhất là bất kỳ yếu tố UI nào bạn đã đặt ở góc dưới bên phải của giao diện người dùng tổng thể. Dưới đây là một số mã và nhận xét nội tuyến để minh họa điểm.

Lưu ý, điều này có thể có giá trị đặc biệt đối với những người làm việc với chế độ xem cuộn vì thường thiết kế xung quanh một chế độ xem nội dung xác định kích thước của nó (và truyền thông tin này đến chế độ xem cuộn) một cách linh hoạt dựa trên bất cứ điều gì bạn đặt vào đó. Chúc may mắn, hy vọng điều này sẽ giúp ai đó ngoài kia.

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

Làm cách nào để thay đổi kích thước giám sát để phù hợp với tất cả các cuộc phỏng vấn với autolayout.png


3
Đây là một mẹo hay và không được nhiều người biết đến. Lặp lại: nếu các chế độ xem bên trong có chiều cao nội tại và được ghim vào trên cùng và dưới cùng, thì chế độ xem bên ngoài không cần chỉ định chiều cao của nó và trên thực tế sẽ ôm nội dung của nó. Bạn có thể cần điều chỉnh nén nội dung và ôm cho các chế độ xem bên trong để có kết quả mong muốn.
phatmann

Đây là tiền! Một trong những ví dụ VFL súc tích tốt hơn tôi từng thấy.
Evan R

Đây là những gì tôi đang tìm kiếm (Cảm ơn @John) vì vậy tôi đã đăng một phiên bản Swift của cái này ở đây
James

1
"Bạn phải chọn chế độ xem phụ nào sẽ điều khiển chiều cao và / hoặc chiều rộng của vùng chứa ... phổ biến nhất là bất kỳ yếu tố UI nào bạn đã đặt ở góc dưới bên phải của giao diện người dùng tổng thể của bạn" .. Tôi đang sử dụng PureLayout, và đây là chìa khóa cho tôi Đối với chế độ xem một phụ huynh, với chế độ xem một con, chính chế độ xem con đang ghim xuống phía dưới bên phải, đột nhiên tạo cho bố mẹ một kích thước. Cảm ơn!
Steven Elliott

1
Chọn một chế độ xem để điều khiển chiều rộng là chính xác những gì tôi không thể làm. Đôi khi một subview rộng hơn, đôi khi một subview khác lại rộng hơn. Bất kỳ ý tưởng cho trường hợp đó?
Henning

3

Bạn có thể làm điều này bằng cách tạo một ràng buộc và kết nối nó thông qua trình xây dựng giao diện

Xem giải thích: Auto_Layout_Constraints_in_Interface_Builder

raywenderlich bắt đầu tự động bố trí

AutolayoutPG Bài viết ràng buộc Nguyên tắc cơ bản

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

kết nối ổ cắm ràng buộc này với các khung nhìn phụ của bạn Ràng buộc hoặc kết nối các siêu khung nhìn ràng buộc và đặt nó theo các yêu cầu của bạn như thế này

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

Tôi hy vọng điều này làm rõ nó.


Vì vậy, có thể định cấu hình các ràng buộc được tạo trong XCode từ mã nguồn. Nhưng câu hỏi là làm thế nào để cấu hình chúng để thay đổi kích thước giám sát.
DAK

CÓ @DAK. hãy chắc chắn về bố trí giám sát. Đặt ràng buộc vào giám sát và tạo chiều cao Hạn chế để bất cứ khi nào tầm nhìn của bạn tăng lên, nó sẽ tự động tăng chiều cao giám sát.
chandan

Điều này cũng làm việc cho tôi. Nó giúp tôi có các ô có kích thước cố định (chiều cao 60px). Vì vậy, khi chế độ xem tải, tôi đặt ràng buộc chiều cao IBOutlet của mình là 60 * x trong đó x là số lượng ô. Cuối cùng, bạn có thể muốn điều này trong chế độ xem cuộn để bạn có thể thấy toàn bộ nội dung.
Sandy Chapman

-1

Điều này có thể được thực hiện cho một bình thường subviewbên trong lớn hơn UIView, nhưng nó không hoạt động tự động cho headerViews. Chiều cao của một headerViewđược xác định bởi những gì đang được trả về bởi tableView:heightForHeaderInSection:vì vậy bạn phải tính toán heightdựa trên heightcác UILabelkhông gian cộng cho UIButtonvà bất kỳ paddingmà bạn cần. Bạn cần phải làm một cái gì đó như thế này:

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

Dưới đây headerStringlà bất cứ điều gì bạn muốn chuỗi để cư UILabel, và số 281 là widthcủa UILabel(như thiết lập trong Interface Builder)


Thật không may, điều này không làm việc. Kích thước phù hợp để phù hợp với nội dung Xếp hạng trên giám sát sẽ loại bỏ các ràng buộc kết nối dưới cùng của nút với giám sát như bạn đã dự đoán. Trong thời gian chạy, nhãn được thay đổi kích thước và nó ấn nút xuống nhưng giám sát không tự động thay đổi kích thước.
DAK

@DAK, xin lỗi, vấn đề là chế độ xem của bạn là tiêu đề cho chế độ xem bảng. Tôi hiểu lầm tình hình của bạn. Tôi đã nghĩ rằng chế độ xem bao quanh nút và nhãn nằm trong một chế độ xem khác, nhưng có vẻ như bạn đang sử dụng nó làm tiêu đề (chứ không phải là chế độ xem bên trong tiêu đề). Vì vậy, câu trả lời ban đầu của tôi sẽ không hoạt động. Tôi đã thay đổi câu trả lời của tôi thành những gì tôi nghĩ nên làm việc.
ndelmar

điều này tương tự như cách thực hiện hiện tại của tôi nhưng tôi đã hy vọng rằng có một cách tốt hơn. Tôi muốn tránh cập nhật mã này mỗi lần khi tôi thay đổi tiêu đề.
DAK

@Dak, Xin lỗi, tôi không nghĩ có một cách tốt hơn, đó chỉ là cách các chế độ xem bảng hoạt động.
ndelmar

Xin vui lòng xem câu trả lời của tôi. "Cách tốt hơn" là sử dụng hệ thống UIViewLayoutSizeFitsSize :. Điều đó nói rằng, thực hiện tự động hoàn toàn trên chế độ xem hoặc ô chỉ để có được chiều cao cần thiết trong chế độ xem bảng là khá tốn kém. Tôi sẽ làm điều đó cho các ô phức tạp nhưng có lẽ là một giải pháp như của bạn cho các trường hợp đơn giản hơn.
TomSwift
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.