Як змінити розмір нагляду, щоб відповідати всім підглядам з автоматичним розкладом?


142

Я розумію, що авторозкладка полягає в тому, що він приймає розмір огляду і базується на обмеженнях і внутрішніх розмірах, які він обчислює позицій підпрезентацій.

Чи є спосіб змінити цей процес? Я хочу змінити розмір нагляду на основі обмежень та внутрішніх розмірів. Який найпростіший спосіб досягти цього?

У мене перегляд створений в Xcode, який я використовую в якості заголовка UITableView. Цей вигляд включає мітку та кнопку. Розмір етикетки відрізняється залежно від даних. Залежно від обмежень етикетка успішно натискає кнопку вниз або якщо між кнопкою та дном огляду є обмеження, ярлик стискається.

Я знайшов кілька подібних питань, але вони не мають гарних і простих відповідей.


28
Ви повинні вибрати одну з наведених нижче відповідей, оскільки напевно Том Свіфт відповів на ваше запитання. Усі плакати витратили величезну кількість часу, щоб допомогти вам, тепер ви повинні зробити свою роль і вибрати відповідь, який вам найбільше подобається.
Девід Н

Відповіді:


149

Правильний API, який використовується, є UIView systemLayoutSizeFittingSize:проходження UILayoutFittingCompressedSizeабо UILayoutFittingExpandedSize.

Для звичайного UIViewвикористання автоматичного розкладу це повинно працювати лише до тих пір, поки ваші обмеження будуть правильними. Якщо ви хочете використовувати його на UITableViewCell(наприклад, щоб визначити висоту рядка), тоді слід зателефонувати йому до своєї комірки contentViewта схопити висоту.

Подальші міркування існують, якщо у вас є один або кілька UILabel, які є багатолінійними. Для цього не є властивим встановити preferredMaxLayoutWidthвластивість правильно таким чином, щоб мітка містила правильну інформацію intrinsicContentSize, яка буде використана при systemLayoutSizeFittingSize'sобчисленні.

EDIT: за запитом, додаючи приклад розрахунку висоти для комірки подання таблиці

Використання автоматичного розкладу для обчислення висоти комірки таблиці не надто ефективно, але це, звичайно, зручно, особливо якщо у вас клітина зі складною компонуванням.

Як я вже говорив вище, якщо ви використовуєте багаторядкові 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

Ось надуманий підклас UITableViewController, що демонструє висотуForRowAtIndexPath:

#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
Чи можете ви навести приклад того, як виглядає реалізація висотиForRowAtIndexPath: використання цього методу з коміркою, що містить багаторядкову мітку? Я з цим досить заплутався і не змусив його працювати. Як отримати клітинку (особливо якщо клітинка встановлена ​​на дошці розкадрування)? Які обмеження вам потрібні, щоб він працював?
rdelmar

@rdelmar - точно. Я додав приклад до своєї відповіді.
TomSwift

7
Це не спрацювало для мене, поки я не додав остаточного вертикального обмеження від нижньої частини комірки до нижньої частини нижнього підвиду в комірці. Схоже, що вертикальні обмеження повинні включати верхній і нижній вертикальні відстані між коміркою та її вмістом, щоб успіх обчислення висоти комірки.
Ерік Бейкер

1
Мені потрібен був ще один трюк, щоб він працював. І порада @EricBaker нарешті прибила його. Дякуємо, що поділилися цією людиною. Я отримав ненульовий розмір зараз проти sizingCellабо його contentView.
MkVal

1
Для тих, у кого проблеми не досягають потрібної висоти, переконайтесь, sizingCellщо ширина вашої ширини відповідає вашій tableViewширині.
MkVal

30

Коментар Еріка Бейкера підкреслив мене до основної ідеї, що для того, щоб розмір виду визначався вмістом, розміщеним у ньому, тоді вміст, розміщений у ньому, повинен мати явний зв'язок із вмістом, що містить його, щоб досягти його висоти (або ширина) динамічно . "Додати підпогляд" не створює цього взаємозв'язку, як ви могли припустити. Ви повинні вибрати, який підрозділ буде визначати висоту та / або ширину контейнера ... найчастіше, будь-який елемент інтерфейсу, який ви розмістили в нижньому правому куті вашого загального інтерфейсу. Ось код та вбудовані коментарі, щоб проілюструвати суть.

Зауважте, це може мати особливу цінність для тих, хто працює з поданнями прокрутки, оскільки прийнято проектувати навколо одного контенту подання, яке визначає його розмір (і повідомляє це перегляду прокрутки) динамічно, залежно від того, що ви вкладете в нього. Удачі, сподіваємось, це допоможе комусь там.

//
//  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, який я бачив.
Еван Р

Це те, що я шукав (спасибі @John), тому я опублікував тут
Джеймс,

1
"Ви повинні вибрати, який підрозділ буде керувати висотою та / або шириною контейнера ... найчастіше будь-який елемент інтерфейсу, який ви розмістили в нижньому правому куті вашого загального інтерфейсу". Я використовую PureLayout, і це було для мене ключовим. Для одного батьківського подання, з одним дитячим видом, сидів дитячий погляд, щоб закріпити праворуч внизу, що раптом надало батьківському виду розмір. Дякую!
Стівен Елліотт

1
Вибір одного виду для ширини - це саме те, що я не можу зробити. Іноді один підпогляд ширший, іноді інший підвід - ширший. Якісь ідеї для цього випадку?
Хеннінг

3

Це можна зробити, створивши обмеження і підключивши його за допомогою конструктора інтерфейсів

Дивіться пояснення: Auto_Layout_Constraints_in_Interface_Builder

raywenderlich початок-авто-макет

Основи обмеження статей у AutolayoutPG

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

підключіть цю розетку обмежень до підпоглядів обмеження або підключіть суперпогляди обмеження теж і встановіть її відповідно до ваших вимог

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

Я сподіваюся, що це прояснить це.


Таким чином, можна налаштувати обмеження, створені в XCode, з вихідного коду. Але питання полягає в тому, як налаштувати їх на розмір нагляду.
DAK

ТАК @DAK. будь ласка, переконайтесь у макеті перегляду. Покладіть обмеження на перегляд і зробіть висоту обмеження занадто, тому щоразу, коли ваш підвід збільшуватиметься, він автоматично збільшуватиме висоту обмеження нагляду.
чандан

Це теж працювало для мене. Це допомагає мені мати клітини фіксованого розміру (висота 60 пікселів). Отже, коли подання завантажується, я встановлюю обмеження висоти IBOutlet'd на 60 * x, де x - кількість комірок. Зрештою, ви, швидше за все, хочете, щоб це було у вікні прокрутки, щоб ви могли бачити всю справу.
Сенді Чапман

-1

Це можна зробити для звичайного subviewвсередині більшого UIView, але це не працює автоматично headerViews. Висота a headerViewвизначається тим, що повертається, tableView:heightForHeaderInSection:тому ви повинні обчислити heightвиходячи heightз UILabelплюсового простору для потрібного 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.