Равномерное размещение нескольких представлений в представлении контейнера

Авто макет делает мою жизнь трудной. Теоретически, это было бы действительно полезно, когда я перешел, но я, кажется, борюсь с этим все время.

Я сделал демонстрационный проект, чтобы попытаться найти помощь. Кто-нибудь знает, как сделать интервалы между представлениями увеличиваться или уменьшаться равномерно при изменении размера представления?

Вот три метки (вручную разнесены по вертикали):

image1

Я хочу, чтобы они меняли размер интервала (а не размер изображения) равномерно, когда я поворачиваюсь. По умолчанию вид сверху и снизу смещается к центру:

image2

27 ответов

Решение

Так что мой подход позволяет вам сделать это в конструкторе интерфейсов. То, что вы делаете, это создаете "разделительные виды", которые вы установили так, чтобы они одинаково соответствовали высотам. Затем добавьте верхние и нижние ограничения к меткам (см. Скриншот).

введите описание изображения здесь

Более конкретно, у меня есть верхнее ограничение на "Spacer View 1" для суперпредставления с ограничением высоты с более низким приоритетом, чем 1000, и с высотой, равной всем другим "представлениям Spacer". "Spacer View 4" имеет ограничение пространства снизу для суперпредставления. Каждая метка имеет соответствующие ограничения сверху и снизу для своих ближайших "видов распорок".

Примечание: убедитесь, что у вас нет дополнительных верхних / нижних пространственных ограничений на ваших ярлыках для супервизора; только те, которые относятся к "космическим видам". Это будет удовлетворительным, так как верхнее и нижнее ограничения находятся в "Space View 1" и "Spacer View 4" соответственно.

Дух 1: Я продублировал свой взгляд и просто перевел его в ландшафтный режим, чтобы вы могли видеть, что это работает.

Дух 2: "Вид спейсера" мог бы быть прозрачным.

Дух 3: Этот подход может применяться горизонтально.

СМОТРИ, НЕ ПРОСТРАНСТВ!

Основываясь на предложениях в разделе комментариев моего исходного ответа, особенно на полезных предложениях @Rivera, я упростил свой оригинальный ответ.

Я использую картинки, чтобы проиллюстрировать, насколько это просто. Надеюсь, вы найдете гифки полезными. На случай, если у вас возникли проблемы с гифками, я включил старый ответ ниже с простыми снимками экрана.

Инструкции:

1) Добавьте свои кнопки или ярлыки. Я использую 3 кнопки.

2) Добавьте ограничение по центру от каждой кнопки в суперпредставление:

введите описание изображения здесь

3) Добавьте ограничение для каждой кнопки в ограничение нижнего макета:

введите описание изображения здесь

4) Настройте ограничение, добавленное в #3 выше, следующим образом:

а) выберите ограничение, б) удалите константу (установите в 0), в) измените множитель следующим образом: возьмите количество кнопок + 1 и, начиная сверху, установите множитель как buttonCountPlus1: 1, а затем buttonCountPlus1:2 и, наконец, buttonCountPlus1: 3. (Я объясню, откуда я взял эту формулу в старом ответе ниже, если вам интересно).

введите описание изображения здесь

5) Вот демо-ход!

введите описание изображения здесь

Примечание. Если ваши кнопки имеют большую высоту, вам нужно будет компенсировать это постоянным значением, поскольку ограничение находится снизу кнопки.


Старый ответ


Несмотря на то, что говорится в документах Apple и в превосходной книге Эрики Садун (Auto Layout Demystified), можно равномерно распределять изображения без прокладок. Это очень просто сделать в IB и в коде для любого количества элементов, которые вы хотите разместить равномерно. Все, что вам нужно, это математическая формула, называемая "формула раздела". Это проще сделать, чем объяснить. Я сделаю все возможное, продемонстрировав это в IB, но это так же легко сделать в коде.

В рассматриваемом примере вы бы

1) начните с установки каждой метки, чтобы иметь ограничение по центру. Это очень просто сделать. Просто управляйте перетаскиванием от каждой метки вниз.

2) Удерживайте нажатой клавишу Shift, поскольку вы также можете добавить другое ограничение, которое мы будем использовать, а именно "руководство по разметке от нижнего пространства к нижнему".

3) Выберите "руководство по разметке от нижнего пространства до нижнего" и "центрировать горизонтально в контейнере". Сделайте это для всех 3 ярлыков.

Сдвиг, чтобы добавить эти два ограничения для каждой метки

По сути, если мы возьмем метку, координату которой мы хотим определить, и разделим ее на общее количество меток плюс 1, то у нас будет число, которое мы можем добавить в IB, чтобы получить динамическое местоположение. Я упрощаю формулу, но вы можете использовать ее для установки горизонтального или вертикального и горизонтального интервалов одновременно. Это супер мощный!

Вот наши множители.

Label1 = 1/4 =.25,

Label2 = 2/4 =.5,

Label3 = 3/4 = 0,75

(Правка: @Rivera прокомментировал, что вы можете просто использовать отношения прямо в поле множителя, а xCode с помощью math!)

4) Итак, давайте выберем Label1 и выберем нижнее ограничение. Как это:введите описание изображения здесь

5) Выберите "Второй элемент" в инспекторе атрибутов.

6) В раскрывающемся списке выберите "Поменять местами первый и второй элемент".

7) Обнулить константу и значение wC hAny. (Вы можете добавить смещение здесь, если вам это нужно).

8) Это критическая часть: в поле множителя добавьте наш первый множитель 0,25.

9) Пока вы это делаете, установите верхний "Первый элемент" на "CenterY", так как мы хотим центрировать его по центру y метки. Вот как все это должно выглядеть.

введите описание изображения здесь

10) Повторите этот процесс для каждой метки и вставьте соответствующий множитель: 0,5 для метки 2 и 0,75 для метки 3. Вот конечный продукт во всех направлениях со всеми компактными устройствами! Супер просто. Я смотрел на множество решений, включающих множество кода и разделителей. Это, безусловно, лучшее решение, которое я видел по этому вопросу.

Обновление: @kraftydevil добавляет, что руководство по макету Bottom появляется только в раскадровках, а не в xibs. Используйте "Нижнее пространство для контейнера" в XIBS. Хороший улов!

введите описание изображения здесь

Очень быстрое решение Interface Builder:

Для любого количества представлений, которые должны быть равномерно распределены в суперпредставлении, просто дайте каждому ограничение "Выровнять центр X по суперпредставлению" для горизонтальной компоновки или "Выровнять суперцентр по центру Y" для вертикальной компоновки и установите множитель равным N:p (ПРИМЕЧАНИЕ: некоторым повезло больше с p:N - см. ниже)

где

N = total number of views, а также

p = position of the view including spaces

Первая позиция - 1, затем пробел, следующая позиция - 3, поэтому p становится серией [1,3,5,7,9,...]. Работает для любого количества просмотров.

Так что, если у вас есть 3 вида для разметки, это выглядит так:

Иллюстрация о том, как равномерно распределить взгляды в IB

РЕДАКТИРОВАТЬ Примечание: выбор N:p или же p:N зависит от порядка отношений вашего ограничения выравнивания. Если "Первый элемент" - Superview.Center, вы можете использовать p:N если Superview.Center является "Вторым элементом", вы можете использовать N:p, Если есть сомнения, попробуйте оба варианта...:-)

Начиная с iOS 9, Apple сделала это очень легко с (долгожданным) UIStackView, Просто выберите представления, которые вы хотите содержать в Интерфейсном Разработчике, и выберите "Редактор" -> "Вставить" -> "Представление в стеке". Установите соответствующие ограничения ширины / высоты / поля для представления стека и убедитесь, что для свойства Distribution установлено значение "Равный интервал":

Конечно, если вам нужна поддержка iOS 8 или ниже, вам придется выбрать один из других вариантов.

Я был на американских горках, любя автопутешествия и ненавидя его. Ключ к любви это, кажется, принять следующее:

  1. Редактирование интерфейса и "полезное" автоматическое создание ограничений практически бесполезно для всех, кроме самого тривиального случая
  2. Создание категорий для упрощения общих операций спасает жизнь, поскольку код является настолько повторяющимся и многословным.

Тем не менее, то, что вы пытаетесь сделать, не является простым и будет трудно достичь в конструкторе интерфейсов. Это довольно просто сделать в коде. Этот код, в viewDidLoad, создает и позиционирует три метки так, как вы их просите:

// Create three labels, turning off the default constraints applied to views created in code
UILabel *label1 = [UILabel new];
label1.translatesAutoresizingMaskIntoConstraints = NO;
label1.text = @"Label 1";

UILabel *label2 = [UILabel new];
label2.translatesAutoresizingMaskIntoConstraints = NO;
label2.text = @"Label 2";

UILabel *label3 = [UILabel new];
label3.translatesAutoresizingMaskIntoConstraints = NO;
label3.text = @"Label 3";

// Add them all to the view
[self.view addSubview:label1];
[self.view addSubview:label2];
[self.view addSubview:label3];

// Center them all horizontally
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label1 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]];

[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label2 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]];

[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label3 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]];

// Center the middle one vertically
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label2 attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0]];

// Position the top one half way up
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label1 attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:label2 attribute:NSLayoutAttributeCenterY multiplier:0.5 constant:0]];

// Position the bottom one half way down
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label3 attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:label2 attribute:NSLayoutAttributeCenterY multiplier:1.5 constant:0]];

Как я уже сказал, этот код значительно упрощен с помощью нескольких методов категорий UIView, но для ясности я проделал длинный путь здесь.

Категория здесь для интересующихся, и у нее есть метод для равномерного распределения массива видов по определенной оси.

Правильный и простой способ - использовать Stack Views.

  1. Добавьте ваши ярлыки / представления в Stack View:

  1. Выберите представление стека и установите для параметра Распределение равное расстояние:

  1. Добавьте Spacing к ограничениям ближайшего соседа в Stack View и обновите фреймы:

  1. Добавьте ограничения высоты ко всем меткам:

  1. Предварительный просмотр:

Большинство из этих решений зависят от наличия нечетного количества элементов, чтобы вы могли взять средний элемент и отцентрировать его. Что, если у вас есть четное количество предметов, которые вы хотите распределить равномерно? Вот более общее решение. Эта категория будет равномерно распределять любое количество элементов по вертикальной или горизонтальной оси.

Пример использования для вертикального распределения 4 меток в их суперпредставлении:

[self.view addConstraints:
     [NSLayoutConstraint constraintsForEvenDistributionOfItems:@[label1, label2, label3, label4]
                                        relativeToCenterOfItem:self.view
                                                    vertically:YES]];

NSLayoutConstraint + EvenDistribution.h

@interface NSLayoutConstraint (EvenDistribution)

/**
 * Returns constraints that will cause a set of views to be evenly distributed horizontally
 * or vertically relative to the center of another item. This is used to maintain an even
 * distribution of subviews even when the superview is resized.
 */
+ (NSArray *) constraintsForEvenDistributionOfItems:(NSArray *)views
                             relativeToCenterOfItem:(id)toView
                                         vertically:(BOOL)vertically;

@end

NSLayoutConstraint + EvenDistribution.m

@implementation NSLayoutConstraint (EvenDistribution)

+(NSArray *)constraintsForEvenDistributionOfItems:(NSArray *)views
                           relativeToCenterOfItem:(id)toView vertically:(BOOL)vertically
{
    NSMutableArray *constraints = [NSMutableArray new];
    NSLayoutAttribute attr = vertically ? NSLayoutAttributeCenterY : NSLayoutAttributeCenterX;

    for (NSUInteger i = 0; i < [views count]; i++) {
        id view = views[i];
        CGFloat multiplier = (2*i + 2) / (CGFloat)([views count] + 1);
        NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:view
                                                                      attribute:attr
                                                                      relatedBy:NSLayoutRelationEqual
                                                                         toItem:toView
                                                                      attribute:attr
                                                                     multiplier:multiplier
                                                                       constant:0];
        [constraints addObject:constraint];
    }

    return constraints;
}

@end

Проверьте библиотеку с открытым исходным кодом PureLayout. Он предлагает несколько методов API для распределения видов, в том числе варианты, в которых интервал между каждым видом фиксирован (размер вида изменяется по мере необходимости) и размер каждого вида фиксирован (интервал между представлениями изменяется по мере необходимости). Обратите внимание, что все это выполняется без использования каких-либо "вид спейсера".

Из NSArray + PureLayout.h:

// NSArray+PureLayout.h

// ...

/** Distributes the views in this array equally along the selected axis in their superview. Views will be the same size (variable) in the dimension along the axis and will have spacing (fixed) between them. */
- (NSArray *)autoDistributeViewsAlongAxis:(ALAxis)axis
                                alignedTo:(ALAttribute)alignment
                         withFixedSpacing:(CGFloat)spacing;

/** Distributes the views in this array equally along the selected axis in their superview. Views will be the same size (fixed) in the dimension along the axis and will have spacing (variable) between them. */
- (NSArray *)autoDistributeViewsAlongAxis:(ALAxis)axis
                                alignedTo:(ALAttribute)alignment
                            withFixedSize:(CGFloat)size;

// ...

Поскольку это все с открытым исходным кодом, если вам интересно посмотреть, как это достигается без разделительных представлений, просто взгляните на реализацию. (Это зависит от использования как constant а также multiplier для ограничений.)

У меня похожая проблема и обнаружил этот пост. Однако ни один из предоставленных в настоящее время ответов не решает проблему так, как вы хотите. Они не делят интервал одинаково, а скорее равномерно распределяют центр меток. Важно понимать, что это не одно и то же. Я построил небольшую диаграмму, чтобы проиллюстрировать это.

Посмотреть интервал иллюстрации

Есть 3 вида, все 20pt в высоту. Использование любого из предложенных методов в равной степени распределяет центры видов и дает вам иллюстрированный макет. Обратите внимание, что y-центр представлений расположен на одинаковом расстоянии. Тем не менее, интервал между суперпредставлением и видом сверху составляет 15pt, а интервал между подпредставлениями - всего 5pt. Чтобы обеспечить одинаковое расстояние между видами, они должны быть 10pt, т.е. все синие стрелки должны быть 10pt.

Тем не менее, я еще не нашел хорошего общего решения. В настоящее время моя лучшая идея состоит в том, чтобы вставить "промежуточные виды" между подпредставлениями и установить равные значения высоты промежуточных видов.

Мне удалось решить это полностью в IB:

  1. Сделайте ограничения, чтобы выровнять центр Y каждого из ваших подпредставлений по нижнему краю суперпредставления.
  2. Установите множитель каждого из этих ограничений на 1/2n, 3/2n, 5/2n, …, n-1/2n, где n - количество распространяемых вами подпредставлений.

Поэтому, если у вас есть три метки, установите множители для каждого из этих ограничений на 0,1666667, 0,5, 0,833333.

Я нашел идеальный и простой метод. Автоматическая разметка не позволяет одинаково изменять размеры пространств, но позволяет одинаково изменять размеры представлений. Просто поместите некоторые невидимые виды между вашими полями и сообщите авторазметке, чтобы они оставались одинаковыми по размеру. Работает отлично!

Начальный XIB

Растянутый XIB

Однако следует отметить одну вещь; когда я уменьшал размер в конструкторе интерфейсов, иногда он путался и оставлял метку там, где она была, и возникал конфликт, если размер изменялся нечетным количеством. В противном случае это работало отлично.

редактировать: я обнаружил, что конфликт стал проблемой. Из-за этого я взял одно из интервальных ограничений, удалил его и заменил двумя ограничениями: больше или равно или меньше или равно. Оба были одинакового размера и имели гораздо более низкий приоритет, чем другие ограничения. Результатом не стало дальнейшего конфликта.

Основываясь на ответе Бена Долмана, это распределяет взгляды более равномерно (с отступами и т. Д.):

+(NSArray *)constraintsForEvenDistributionOfItems:(NSArray *)views
                           relativeToCenterOfItem:(id)toView vertically:(BOOL)vertically
{
    NSMutableArray *constraints = [NSMutableArray new];
    NSLayoutAttribute attr = vertically ? NSLayoutAttributeCenterY : NSLayoutAttributeCenterX;

    CGFloat min = 0.25;
    CGFloat max = 1.75;
    CGFloat d = (max-min) / ([views count] - 1);
    for (NSUInteger i = 0; i < [views count]; i++) {
        id view = views[i];
        CGFloat multiplier = i * d + min;
        NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:view
                                                                      attribute:attr
                                                                      relatedBy:NSLayoutRelationEqual
                                                                         toItem:toView
                                                                      attribute:attr
                                                                     multiplier:multiplier
                                                                       constant:0];
        [constraints addObject:constraint];
    }

    return constraints;
}

Проверьте https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/AutoLayoutbyExample/AutoLayoutbyExample.html хорошее описание решения вашей проблемы.

С надписями это нормально работает как минимум:

@"H:|-15-[first(==second)]-[second(==third)]-[third(==first)]-15-|

Если первый имеет такую ​​же ширину, как второй, а второй третий и третий третий, то все они получат одинаковую ширину... Вы можете сделать это как по горизонтали (H), так и по вертикали (V).

Swift 3 версия

let _redView = UIView()
        _redView.backgroundColor = UIColor.red
        _redView.translatesAutoresizingMaskIntoConstraints = false

        let _yellowView = UIView()
        _yellowView.backgroundColor = UIColor.yellow
        _yellowView.translatesAutoresizingMaskIntoConstraints = false

        let _blueView = UIView()
        _blueView.backgroundColor = UIColor.blue
        _blueView.translatesAutoresizingMaskIntoConstraints = false

        self.view.addSubview(_redView)
        self.view.addSubview(_yellowView)
        self.view.addSubview(_blueView)

        var views = ["_redView": _redView, "_yellowView": _yellowView, "_blueView":_blueView]

        var nslayoutConstraint_H = NSLayoutConstraint.constraints(withVisualFormat: "|->=0-[_redView(40)]->=0-[_yellowView(40)]->=0-[_blueView(40)]->=0-|", options: [.alignAllTop, .alignAllBottom], metrics: nil, views: views)
        self.view.addConstraints(nslayoutConstraint_H)

        var nslayoutConstraint_V = NSLayoutConstraint.constraints(withVisualFormat: "V:[_redView(60)]", options: NSLayoutFormatOptions.init(rawValue: 0), metrics: nil, views: views)
        self.view.addConstraints(nslayoutConstraint_V)


        let constraint_red = NSLayoutConstraint.init(item: self.view, attribute: .centerY, relatedBy: .equal, toItem: _redView, attribute: .centerY, multiplier: 1, constant: 0)
        self.view.addConstraint(constraint_red)

        let constraint_yellow = NSLayoutConstraint.init(item: self.view, attribute: .centerX, relatedBy: .equal, toItem: _yellowView, attribute: .centerX, multiplier: 1, constant: 0)
        self.view.addConstraint(constraint_yellow)

        let constraint_yellow1 = NSLayoutConstraint.init(item: _redView, attribute: .centerX, relatedBy: .equal, toItem: _yellowView, attribute: .leading, multiplier: 0.5, constant: 0)
        self.view.addConstraint(constraint_yellow1)

        let constraint_yellow2 = NSLayoutConstraint.init(item: _blueView, attribute: .centerX, relatedBy: .equal, toItem: _yellowView, attribute: .leading, multiplier: 1.5, constant: 40)
        self.view.addConstraint(constraint_yellow2)

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

Я установил свои взгляды на viewDidLoad:

- (void)viewDidLoad {

    [super viewDidLoad];

    cancelButton = [UIButton buttonWithType: UIButtonTypeRoundedRect];
    cancelButton.translatesAutoresizingMaskIntoConstraints = NO;
    [cancelButton setTitle:@"Cancel" forState:UIControlStateNormal];
    [self.view addSubview:cancelButton];

    middleButton = [UIButton buttonWithType: UIButtonTypeRoundedRect];
    middleButton.translatesAutoresizingMaskIntoConstraints = NO;
    [middleButton setTitle:@"Middle" forState:UIControlStateNormal];
    [self.view addSubview:middleButton];

    nextButton = [UIButton buttonWithType: UIButtonTypeRoundedRect];
    nextButton.translatesAutoresizingMaskIntoConstraints = NO;
    [nextButton setTitle:@"Next" forState:UIControlStateNormal];
    [self.view addSubview:nextButton];


    [self.view setNeedsUpdateConstraints];

}

И затем, на updateViewConstrains, сначала я удаляю все ограничения, затем я создаю словарь представлений и затем вычисляю пространство, которое будет использоваться между представлениями. После этого я просто использую Visual Language Format, чтобы установить ограничения:

- (void)updateViewConstraints {


    [super updateViewConstraints];

    [self.view removeConstraints:self.view.constraints];

    NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(cancelButton, nextButton, middleButton);

    float distance=(self.view.bounds.size.width-cancelButton.intrinsicContentSize.width-nextButton.intrinsicContentSize.width-middleButton.intrinsicContentSize.width-20-20)/  ([viewsDictionary count]-1);  // 2 times 20 counts for the left & rigth margins
    NSNumber *distancies=[NSNumber numberWithFloat:distance];

//    NSLog(@"Distancies: %@", distancies);
//    
//    NSLog(@"View Width: %f", self.view.bounds.size.width);
//    NSLog(@"Cancel Width: %f", cancelButton.intrinsicContentSize.width);
//    NSLog(@"Middle Width: %f", middleButton.intrinsicContentSize.width);
//    NSLog(@"Next Width: %f", nextButton.intrinsicContentSize.width);



    NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-[cancelButton]-dis-[middleButton]-dis-[nextButton]-|"
                                                                   options:NSLayoutFormatAlignAllBaseline
                                                                   metrics:@{@"dis":distancies}
                                                                     views:viewsDictionary];


    [self.view addConstraints:constraints];



    constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[nextButton]-|"
                                                          options:0
                                                          metrics:nil
                                                            views:viewsDictionary];
    [self.view addConstraints:constraints];



}

Хорошая вещь об этом методе - то, что вы должны делать очень мало математики. Я не говорю, что это идеальное решение, но я работаю для макета, который я пытался достичь.

Я надеюсь, что это помогает.

Я только что решил свою проблему, используя функцию умножения. Я не уверен, что это работает для всех случаев, но для меня это работало отлично. Я на Xcode 6.3 FYI.

Я закончил тем, что сделал:

1) Сначала расположение кнопок на экране шириной 320px распределялось так, как я хотел, чтобы это выглядело на устройстве 320px.

Шаг 1: расположение кнопок

2) Затем я добавил ведущее ограничение пространства для суперпредставления на всех моих кнопках.

Шаг 2: добавить ведущие ограничения пространства

3) Затем я изменил свойства начального пространства так, чтобы константа была 0, а множитель - это смещение по x, деленное на ширину экрана (например, моя первая кнопка была на 8 пикселей от левого края, поэтому я установил свой множитель на 8/320)

4) Затем важным шагом здесь является изменение второго элемента в отношении ограничений на superview.Trailing вместо superview.leading. Это имеет ключевое значение, потому что superview.Leading равно 0, а трейлинг в моем случае равен 320, поэтому 8/320 - это 8 пикселей на устройстве 320px, затем, когда ширина суперпредставления меняется на 640 или что-то еще, все представления перемещаются в соотношении относительно ширины. размером экрана 320 пикселей. Математика здесь намного проще для понимания.

Шаг 3 и 4: измените множитель на xPos / screenWidth и установите второй элемент на.Trailing

Вот еще один ответ. Я отвечал на аналогичный вопрос и увидел ссылку на этот вопрос. Я не видел ни одного ответа, похожего на мой. Итак, я подумал написать это здесь.

  class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = UIColor.whiteColor()
        setupViews()
    }

    var constraints: [NSLayoutConstraint] = []

    func setupViews() {

        let container1 = createButtonContainer(withButtonTitle: "Button 1")
        let container2 = createButtonContainer(withButtonTitle: "Button 2")
        let container3 = createButtonContainer(withButtonTitle: "Button 3")
        let container4 = createButtonContainer(withButtonTitle: "Button 4")

        view.addSubview(container1)
        view.addSubview(container2)
        view.addSubview(container3)
        view.addSubview(container4)

        [

            // left right alignment
            container1.leftAnchor.constraintEqualToAnchor(view.leftAnchor, constant: 20),
            container1.rightAnchor.constraintEqualToAnchor(view.rightAnchor, constant: -20),
            container2.leftAnchor.constraintEqualToAnchor(container1.leftAnchor),
            container2.rightAnchor.constraintEqualToAnchor(container1.rightAnchor),
            container3.leftAnchor.constraintEqualToAnchor(container1.leftAnchor),
            container3.rightAnchor.constraintEqualToAnchor(container1.rightAnchor),
            container4.leftAnchor.constraintEqualToAnchor(container1.leftAnchor),
            container4.rightAnchor.constraintEqualToAnchor(container1.rightAnchor),


            // place containers one after another vertically
            container1.topAnchor.constraintEqualToAnchor(view.topAnchor),
            container2.topAnchor.constraintEqualToAnchor(container1.bottomAnchor),
            container3.topAnchor.constraintEqualToAnchor(container2.bottomAnchor),
            container4.topAnchor.constraintEqualToAnchor(container3.bottomAnchor),
            container4.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor),


            // container height constraints
            container2.heightAnchor.constraintEqualToAnchor(container1.heightAnchor),
            container3.heightAnchor.constraintEqualToAnchor(container1.heightAnchor),
            container4.heightAnchor.constraintEqualToAnchor(container1.heightAnchor)
            ]
            .forEach { $0.active = true }
    }


    func createButtonContainer(withButtonTitle title: String) -> UIView {
        let view = UIView(frame: .zero)
        view.translatesAutoresizingMaskIntoConstraints = false

        let button = UIButton(type: .System)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setTitle(title, forState: .Normal)
        view.addSubview(button)

        [button.centerYAnchor.constraintEqualToAnchor(view.centerYAnchor),
            button.leftAnchor.constraintEqualToAnchor(view.leftAnchor),
            button.rightAnchor.constraintEqualToAnchor(view.rightAnchor)].forEach { $0.active = true }

        return view
    }
}

введите описание изображения здесь

И снова, это может быть сделано довольно легко с iOS9 UIStackViews также.

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = UIColor.greenColor()
        setupViews()
    }

    var constraints: [NSLayoutConstraint] = []

    func setupViews() {

        let container1 = createButtonContainer(withButtonTitle: "Button 1")
        let container2 = createButtonContainer(withButtonTitle: "Button 2")
        let container3 = createButtonContainer(withButtonTitle: "Button 3")
        let container4 = createButtonContainer(withButtonTitle: "Button 4")

        let stackView = UIStackView(arrangedSubviews: [container1, container2, container3, container4])
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.axis = .Vertical
        stackView.distribution = .FillEqually
        view.addSubview(stackView)

        [stackView.topAnchor.constraintEqualToAnchor(view.topAnchor),
            stackView.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor),
            stackView.leftAnchor.constraintEqualToAnchor(view.leftAnchor, constant: 20),
            stackView.rightAnchor.constraintEqualToAnchor(view.rightAnchor, constant: -20)].forEach { $0.active = true }
    }


    func createButtonContainer(withButtonTitle title: String) -> UIView {
        let button = UIButton(type: .Custom)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.backgroundColor = UIColor.redColor()
        button.setTitleColor(UIColor.whiteColor(), forState: .Normal)
        button.setTitle(title, forState: .Normal)
        let buttonContainer = UIStackView(arrangedSubviews: [button])
        buttonContainer.distribution = .EqualCentering
        buttonContainer.alignment = .Center
        buttonContainer.translatesAutoresizingMaskIntoConstraints = false
        return buttonContainer
    }
}

Обратите внимание, что это точно такой же подход, как и выше. Он добавляет четыре вида контейнера, которые заполнены одинаково, и вид добавляется к каждому виду стека, который выровнен по центру. Но эта версия UIStackView уменьшает объем кода и выглядит красиво.

Вот решение, которое будет вертикально центрировать любое количество подпредставлений, даже если они имеют уникальные размеры. Что вы хотите сделать, это создать контейнер среднего уровня, центрировать его в суперпредставлении, затем поместить все подпредставления в контейнер и расположить их относительно друг друга. Но, что самое важное, вам также необходимо ограничить их верх и низ контейнера, чтобы контейнер мог быть правильно подобран по размеру и центрирован в суперпредставлении. Определив правильную высоту из своих подпредставлений, контейнер может быть центрирован по вертикали.

В этом примере self это супервизия, в которой вы центрируете все подпредставления.

NSArray *subviews = @[ (your subviews in top-to-bottom order) ];

UIView *container = [[UIView alloc] initWithFrame:CGRectZero];
container.translatesAutoresizingMaskIntoConstraints = NO;
for (UIView *subview in subviews) {
    subview.translatesAutoresizingMaskIntoConstraints = NO;
    [container addSubview:subview];
}
[self addSubview:container];

[self addConstraint:[NSLayoutConstraint constraintWithItem:container attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual
                                                    toItem:self attribute:NSLayoutAttributeLeft multiplier:1.0f constant:0.0f]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:container attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual
                                                    toItem:self attribute:NSLayoutAttributeRight multiplier:1.0f constant:0.0f]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:container attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual
                                                    toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0f constant:0.0f]];

if (0 < subviews.count) {
    UIView *firstSubview = subviews[0];
    [container addConstraint:[NSLayoutConstraint constraintWithItem:firstSubview attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual
                                                             toItem:container attribute:NSLayoutAttributeTop multiplier:1.0f constant:0.0f]];
    UIView *lastSubview = subviews.lastObject;
    [container addConstraint:[NSLayoutConstraint constraintWithItem:lastSubview attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual
                                                             toItem:container attribute:NSLayoutAttributeBottom multiplier:1.0f constant:0.0f]];

    UIView *subviewAbove = nil;
    for (UIView *subview in subviews) {
        [container addConstraint:[NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual
                                                                 toItem:container attribute:NSLayoutAttributeCenterX multiplier:1.0f constant:0.0f]];
        if (subviewAbove) {
            [container addConstraint:[NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual
                                                                     toItem:subviewAbove attribute:NSLayoutAttributeBottom multiplier:1.0f constant:10.0f]];
        }
        subviewAbove = subview;
    }
}

Многие ответы не верны, но получают много очков. Здесь я просто пишу решение программно, три вида выровнены по горизонтали, без использования разделительных видов, но это работает только тогда, когда известны ширины меток при использовании в раскадровке.

NSDictionary *views = NSDictionaryOfVariableBindings(_redView, _yellowView, _blueView);

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|->=0-[_redView(40)]->=0-[_yellowView(40)]->=0-[_blueView(40)]->=0-|" options:NSLayoutFormatAlignAllTop | NSLayoutFormatAlignAllBottom metrics:nil views:views]];

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_redView(60)]" options:0 metrics:nil views:views]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:_redView attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]];

[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:_yellowView attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:_redView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:_yellowView attribute:NSLayoutAttributeLeading multiplier:0.5 constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:_blueView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:_yellowView attribute:NSLayoutAttributeLeading multiplier:1.5 constant:40]];

Другой подход может заключаться в том, чтобы верхняя и нижняя метки имели ограничения относительно вида сверху и снизу, соответственно, и чтобы средний вид имел ограничения сверху и снизу относительно первого и третьего вида соответственно.

Обратите внимание, что у вас больше контроля над ограничениями, чем может показаться, перетаскивая представления близко друг к другу, пока не появятся направляющие пунктирные линии - они указывают на ограничения между двумя объектами, которые будут сформированы, а не между объектом и суперпредставлением.

В этом случае вы захотите изменить ограничения на "Больше или равно" желаемому значению вместо "равно", чтобы позволить им изменить размер. Не уверен, что это будет делать именно то, что вы хотите.

Поздно до вечеринки, но у меня есть рабочее решение для создания меню горизонтально с интервалом. Это можно легко сделать используя == в NSLayoutConstraint

const float MENU_HEIGHT = 40;

- (UIView*) createMenuWithLabels: (NSArray *) labels
    // labels is NSArray of NSString
    UIView * backgroundView = [[UIView alloc]init];
    backgroundView.translatesAutoresizingMaskIntoConstraints = false;

    NSMutableDictionary * views = [[NSMutableDictionary alloc] init];
    NSMutableString * format = [[NSMutableString alloc] initWithString: @"H:|"];
    NSString * firstLabelKey;

    for(NSString * str in labels)
    {
        UILabel * label = [[UILabel alloc] init];
        label.translatesAutoresizingMaskIntoConstraints = false;
        label.text = str;
        label.textAlignment = NSTextAlignmentCenter;
        label.textColor = [UIColor whiteColor];
        [backgroundView addSubview: label];
        [label fixHeightToTopBounds: MENU_HEIGHT-2];
        [backgroundView addConstraints: [label fixHeightToTopBounds: MENU_HEIGHT]];
        NSString * key = [self camelCaseFromString: str];
        [views setObject: label forKey: key];
        if(firstLabelKey == nil)
        {
            [format appendString: [NSString stringWithFormat: @"[%@]", key]];
            firstLabelKey = key;
        }
        else
        {
            [format appendString: [NSString stringWithFormat: @"[%@(==%@)]", key, firstLabelKey]];
        }
    }

    [format appendString: @"|"];

    NSArray * constraints = [NSLayoutConstraint constraintsWithVisualFormat: (NSString *) format
                                                                               options: 0
                                                                               metrics: nil
                                                                                 views: (NSDictionary *) views];
    [backgroundView addConstraints: constraints];
    return backgroundView;
}

Я хотел выровнять 5 изображений по горизонтали, поэтому в итоге я последовал ответу Мете с небольшой разницей.

Первое изображение будет центрировано по горизонтали в контейнере, равном 0 и множителем 1:5:

Второе изображение будет центрировано по горизонтали в контейнере, равном 0 и множителем 3: 5:

То же самое и с остальными изображениями. Например, пятое (и последнее) изображение будет центрировано по горизонтали в контейнере, равном 0 и множителем 9: 5:

Как объяснил Мете, порядок идет 1, 3, 5, 7, 9 и т. Д. Позиции следуют той же логике: первая позиция - 1, затем пробел, затем следующая позиция - 3 и так далее.

Очень простой способ решить эту проблему в InterfaceBuilder:

Установите метку по центру (label2) на "Горизонтальный центр в контейнере" и "Вертикальный центр в контейнере"

Выберите центрированную метку и верхнюю метку (label1 + label2) и добавьте ДВА ограничения для вертикального интервала. Один с большим или равным минимальному интервалу. Один с меньшим или равным максимальному интервалу.

То же самое для метки по центру и метки снизу (метка2 + метка3).

Кроме того, вы также можете добавить два ограничения для label1 - верхнее пространство для SuperView и два ограничения для label2 - нижнее пространство для SuperView.

В результате все 4 расстояния изменят свои размеры одинаково.

Я сделал функцию, которая может помочь. Этот пример использования:

 [self.view addConstraints: [NSLayoutConstraint fluidConstraintWithItems:NSDictionaryOfVariableBindings(button1, button2, button3)
                                                                asString:@[@"button1", @"button2", @"button3"]
                                                               alignAxis:@"V"
                                                          verticalMargin:100
                                                        horizontalMargin:50
                                                             innerMargin:25]];

будет вызывать это вертикальное распределение (извините, у вас нет 10 репутации для встраивания изображений). И если вы измените ось и некоторые значения полей:

alignAxis:@"H"
verticalMargin:120
horizontalMargin:20
innerMargin:10

Вы получите это горизонтальное распределение.

Я новичок в iOS, но вуаля!

EvenDistribution.h

@interface NSLayoutConstraint (EvenDistribution)

/**
 * Returns constraints that will cause a set of subviews
 * to be evenly distributed along an axis.
 */
+ (NSArray *)  fluidConstraintWithItems:(NSDictionary *) views
                               asString:(NSArray *) stringViews
                              alignAxis:(NSString *) axis
                         verticalMargin:(NSUInteger) vMargin
                       horizontalMargin:(NSUInteger) hMargin
                            innerMargin:(NSUInteger) inner;
@end

EvenDistribution.m

#import "EvenDistribution.h"

@implementation NSLayoutConstraint (EvenDistribution)

+ (NSArray *) fluidConstraintWithItems:(NSDictionary *) dictViews
                              asString:(NSArray *) stringViews
                             alignAxis:(NSString *) axis
                        verticalMargin:(NSUInteger) vMargin
                      horizontalMargin:(NSUInteger) hMargin
                           innerMargin:(NSUInteger) iMargin

{
    NSMutableArray *constraints = [NSMutableArray arrayWithCapacity: dictViews.count];
    NSMutableString *globalFormat = [NSMutableString stringWithFormat:@"%@:|-%d-",
                                     axis,
                                     [axis isEqualToString:@"V"] ? vMargin : hMargin
                                     ];



        for (NSUInteger i = 0; i < dictViews.count; i++) {

            if (i == 0)
                [globalFormat appendString:[NSString stringWithFormat: @"[%@]-%d-", stringViews[i], iMargin]];
            else if(i == dictViews.count - 1)
                [globalFormat appendString:[NSString stringWithFormat: @"[%@(==%@)]-", stringViews[i], stringViews[i-1]]];
            else
               [globalFormat appendString:[NSString stringWithFormat: @"[%@(==%@)]-%d-", stringViews[i], stringViews[i-1], iMargin]];

            NSString *localFormat = [NSString stringWithFormat: @"%@:|-%d-[%@]-%d-|",
                                     [axis isEqualToString:@"V"] ? @"H" : @"V",
                                     [axis isEqualToString:@"V"] ? hMargin : vMargin,
                                     stringViews[i],
                                     [axis isEqualToString:@"V"] ? hMargin : vMargin];

            [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:localFormat
                                                                                     options:0
                                                                                     metrics:nil
                                                                                       views:dictViews]];


    }
    [globalFormat appendString:[NSString stringWithFormat:@"%d-|",
                                [axis isEqualToString:@"V"] ? vMargin : hMargin
                                ]];

    [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:globalFormat
                                                                             options:0
                                                                             metrics:nil
                                                                               views:dictViews]];

    return constraints;

}

@end

Да, вы можете сделать это исключительно в конструкторе интерфейсов и без написания кода - единственное предостережение в том, что вы изменяете размер метки, а не распределяете пробелы. В этом случае совместите метки X и Y с суперпредставлением, чтобы оно было зафиксировано в центре. Затем установите вертикальное пространство метки 1 для суперпредставления и метку 2 для стандарта, повторите для метки 3. После установки метки 2 самый простой способ установить метки 1 и 3 - изменить их размер, пока они не защелкнутся.

Вот горизонтальный дисплей, обратите внимание, что вертикальное пространство между меткой 1 и 2 установлено на стандартное:горизонтальный дисплей

А вот портретная версия:

Я понимаю, что они не на 100% равномерно распределены между базовыми линиями из-за разницы между стандартным пространством между метками и стандартным пространством для суперпредставления. Если это вас беспокоит, установите размер 0 вместо стандартного

Я установил значение ширины только для первого элемента (>= ширина) и минимальное расстояние между каждым элементом (>= расстояние). Затем я использую Ctrl, чтобы перетащить второй, третий... элемент на первый, чтобы связать зависимости между элементами.

введите описание изображения здесь

В Android есть метод объединения видов в систему на основе ограничений, которую я хотел имитировать. Поиски привели меня сюда, но ни один из ответов не сработал. Я не хотел использовать StackViews, потому что они вызывают у меня больше горя в будущем, чем экономят сразу. Я закончил тем, что создал решение, которое использовало UILayoutGuides, помещенное между представлениями. Управление их шириной позволяет использовать различные типы дистрибутивов, стили цепочек на языке Android. Функция принимает начальный и конечный якорь вместо родительского представления. Это позволяет размещать цепочку между двумя произвольными представлениями, а не распределять внутри родительского представления. Он использует UILayoutGuide который доступен только в iOS 9+, но это больше не должно быть проблемой.

public enum LayoutConstraintChainStyle {
    case spread //Evenly distribute between the anchors
    case spreadInside //Pin the first & last views to the sides and then evenly distribute
    case packed //The views have a set space but are centered between the anchors.
}

public extension NSLayoutConstraint {

    static func chainHorizontally(views: [UIView],
                                  leadingAnchor: NSLayoutXAxisAnchor,
                                  trailingAnchor: NSLayoutXAxisAnchor,
                                  spacing: CGFloat = 0.0,
                                  style: LayoutConstraintChainStyle = .spread) -> [NSLayoutConstraint] {
    var constraints = [NSLayoutConstraint]()
    guard views.count > 1 else { return constraints }
    guard let first = views.first, let last = views.last, let superview = first.superview else { return constraints }

    //Setup the chain of views
    var distributionGuides = [UILayoutGuide]()
    var previous = first
    let firstGuide = UILayoutGuide()
    superview.addLayoutGuide(firstGuide)
    distributionGuides.append(firstGuide)
    firstGuide.identifier = "ChainDistribution\(distributionGuides.count)"
    constraints.append(firstGuide.leadingAnchor.constraint(equalTo: leadingAnchor))
    constraints.append(first.leadingAnchor.constraint(equalTo: firstGuide.trailingAnchor, constant: spacing))
    views.dropFirst().forEach { view in
        let g = UILayoutGuide()
        superview.addLayoutGuide(g)
        distributionGuides.append(g)
        g.identifier = "ChainDistribution\(distributionGuides.count)"
        constraints.append(contentsOf: [
            g.leadingAnchor.constraint(equalTo: previous.trailingAnchor),
            view.leadingAnchor.constraint(equalTo: g.trailingAnchor)
        ])
        previous = view
    }
    let lastGuide = UILayoutGuide()
    superview.addLayoutGuide(lastGuide)
    constraints.append(contentsOf: [lastGuide.leadingAnchor.constraint(equalTo: last.trailingAnchor),
                                    lastGuide.trailingAnchor.constraint(equalTo: trailingAnchor)])
    distributionGuides.append(lastGuide)

    //Space the according to the style.
    switch style {
    case .packed:
        if let first = distributionGuides.first, let last = distributionGuides.last {
            constraints.append(first.widthAnchor.constraint(greaterThanOrEqualToConstant: spacing))
            constraints.append(last.widthAnchor.constraint(greaterThanOrEqualToConstant: spacing))
            constraints.append(last.widthAnchor.constraint(equalTo: first.widthAnchor))
            constraints.append(contentsOf:
                distributionGuides.dropFirst().dropLast()
                    .map { $0.widthAnchor.constraint(equalToConstant: spacing) }
                )
        }
    case .spread:
        if let first = distributionGuides.first {
            constraints.append(contentsOf:
                distributionGuides.dropFirst().map { $0.widthAnchor.constraint(equalTo: first.widthAnchor) })
        }
    case .spreadInside:
        if let first = distributionGuides.first, let last = distributionGuides.last {
            constraints.append(first.widthAnchor.constraint(equalToConstant: spacing))
            constraints.append(last.widthAnchor.constraint(equalToConstant: spacing))
            let innerGuides = distributionGuides.dropFirst().dropLast()
            if let key = innerGuides.first {
                constraints.append(contentsOf:
                    innerGuides.dropFirst().map { $0.widthAnchor.constraint(equalTo: key.widthAnchor) }
                )
            }
        }
    }

    return constraints
}

Почему бы вам просто не создать TableView и сделать isScrollEnabled = false

Другие вопросы по тегам