Jak zmienić rozmiar superview, aby pasował do wszystkich podwizów za pomocą autolayout?

Moje zrozumienie autolayout jest to, że przyjmuje rozmiar superview i opiera się na ograniczeniach i wewnętrznych rozmiarach oblicza pozycje podview.

Czy istnieje sposób na odwrócenie tego procesu? Chcę zmienić rozmiar superview na podstawie ograniczeń i wewnętrznych rozmiarów. Jaki jest najprostszy sposób na osiągnięcie tego celu?

Mam widok zaprojektowany w Xcode, który używam jako nagłówek dla UITableView. Ten widok zawiera etykietę i przycisk. Rozmiar etykiety różni się w zależności od danych. W zależności od ograniczeń Etykieta skutecznie naciska przycisk w dół lub jeśli istnieje ograniczenie między przyciskiem a spodem superview etykieta jest skompresowana.

Znalazłem kilka podobnych pytań, ale nie mają one dobrych i łatwych odpowiedzi.

Author: giampaolo, 2013-08-08

4 answers

Poprawnym API do użycia jest UIView systemLayoutSizeFittingSize:, przekazujący UILayoutFittingCompressedSize lub UILayoutFittingExpandedSize.

Dla normalnego UIView używanie autolayout powinno działać tak długo, jak długo Twoje ograniczenia są poprawne. Jeśli chcesz użyć go na UITableViewCell (aby określić wysokość wiersza na przykład), powinieneś wywołać go przeciwko komórce contentView i chwycić wysokość.

Istnieją dalsze rozważania, jeśli masz jedną lub więcej UILabel, które są Wielowierszowe. Dla nich jest bezwzględne, że właściwość preferredMaxLayoutWidth będzie ustawiona poprawnie tak, aby etykieta zawierała poprawną intrinsicContentSize, która zostanie użyta w obliczeniach systemLayoutSizeFittingSize's.

EDIT: na życzenie, dodanie przykładu obliczenia wysokości dla komórki widoku tabeli

Używanie autolayout do obliczania wysokości komórki tabeli nie jest zbyt wydajne, ale na pewno jest wygodne, zwłaszcza jeśli masz komórkę o złożonym układzie.

Jak powiedziałem powyżej, jeśli używasz wielolinii UILabel konieczne jest zsynchronizowanie preferredMaxLayoutWidth z szerokością etykiety. Używam podklasa UILabel do tego:

@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

Oto wymyślona podklasa UITableViewController demonstrująca 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

Prosta komórka niestandardowa:

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

@implementation TSTableViewCell
{
    IBOutlet TSLabel* _label;
}

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

@end

A oto obraz ograniczeń zdefiniowanych w Storyboardzie. Należy pamiętać, że nie ma ograniczeń wysokości/szerokości na etykiecie - są one wnioskowane z etykiety intrinsicContentSize:

Tutaj wpisz opis obrazka

 147
Author: TomSwift,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-10 17:09:28

Komentarz Erica Bakera podpowiedział mi, że aby rozmiar widoku był określony przez zawartość umieszczoną w nim, zawartość umieszczona w nim musi mieć wyraźny związek z zawartością widoku, aby dynamicznie napędzać jego wysokość (lub szerokość) . "Dodaj subview" nie tworzy tej relacji, jak można by przypuszczać. Musisz wybrać, który subview będzie wyświetlał wysokość i / lub szerokość kontenera... najczęściej niezależnie od interfejsu użytkownika element umieszczony w prawym dolnym rogu ogólnego interfejsu użytkownika. Oto trochę kodu i komentarzy inline, aby zilustrować punkt.

Uwaga, Może to mieć szczególną wartość dla osób pracujących z widokami przewijania, ponieważ powszechne jest projektowanie wokół pojedynczego widoku zawartości, który określa jego rozmiar (i przekazuje go do widoku przewijania) dynamicznie na podstawie tego, co w nim umieścisz. Powodzenia, mam nadzieję, że to komuś pomoże.

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

Jak zmienić rozmiar superview, aby pasował do wszystkich podwizów z autolayout.png

 28
Author: John Erck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-28 16:32:38

Możesz to zrobić, tworząc ograniczenie i łącząc je za pomocą narzędzia interface builder

Zobacz Wyjaśnienie: Auto_Layout_Constraints_in_Interface_builder

Raywenderlich beginning-auto-layout

AutolayoutPG Articles constraint Fundamentals

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

Podłącz to ograniczenie z ograniczeniem sub views lub podłącz również ograniczenie super views i ustaw je zgodnie z Twoimi wymaganiami w ten sposób

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

Mam nadzieję, że to wyjaśnia to.

 3
Author: chandan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-29 14:39:56

Można to zrobić dla normalnego subview wewnątrz większego UIView, ale nie działa to automatycznie dla headerViews. Wysokość headerView jest określona przez to, co jest zwrócone przez tableView:heightForHeaderInSection:, więc musisz obliczyć height na podstawie height UILabel plus przestrzeń dla UIButton i dowolnych padding, których potrzebujesz. Musisz zrobić coś takiego:

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

Tutaj headerString jest dowolny ciąg znaków, który chcesz wypełnić UILabel, a liczba 281 jest {[13] } z UILabel (jako setup w Interface Builder)

 -1
Author: rdelmar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-04 11:15:54