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

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

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

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

image1

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

image2

Ответ 1

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

enter image description here

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

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

Duh 1: я дублировал свой взгляд и просто помещал его в альбомный режим, чтобы вы могли видеть, что он сработал.

Duh 2: "Промежуточные представления" могли быть прозрачными.

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

Ответ 2

LOOK, NO SPACERS!

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

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

Инструкция:

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

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

enter image description here

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

enter image description here

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

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

enter image description here

5) Здесь работает демонстрация!

enter image description here

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


Старый ответ


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

В данном примере вы бы

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

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

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

Hold down shift to add these two constraints for each label

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

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

Label1 = 1/4 =.25,

Label2 = 2/4 =.5,

Label3 = 3/4 =.75

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

4) Итак, выберите Label1 и выберите нижнее ограничение. Как это: enter image description here

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

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

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

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

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

enter image description here

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

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

enter image description here

Ответ 3

Очень быстрое решение 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 вида на пробел, это выглядит так:

Illustration of how to evenly spread views in IB

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

Ответ 4

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

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

Ответ 5

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

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

Тем не менее, то, что вы пытаетесь, не просто и сложно реализовать в построителе интерфейса. Это довольно просто сделать в коде. Этот код в 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, но для ясности я сделал это длинным путем.

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

Ответ 6

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

Пример использования для вертикального распространения 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

Ответ 7

Проверьте библиотеку с открытым исходным кодом 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 для ограничений.)

Ответ 8

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

  • Добавьте метки/представления в представление Стека:

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

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

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

  1. Добавьте Ограничения на ближайший соседний ограничения на просмотр и обновление фреймов:

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

  1. Добавить Высота для всех ярлыков:

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

  1. Наслаждайтесь просмотром:

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

Ответ 9

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

View Spacing Illustration

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

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

Ответ 10

Я смог полностью решить это в IB:

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

Итак, если у вас есть три метки, установите множители для каждого из этих ограничений в 0.1666667, 0.5, 0.833333.

Ответ 11

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

Initial XIB

Stretched XIB

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

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

Ответ 12

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

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

Ответ 14

С ярлыками это работает как минимум:

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

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

Ответ 15

версия 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)

Ответ 16

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

Я устанавливаю свои представления в 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 для установки ограничений:

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



}

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

Надеюсь, это поможет.

Ответ 17

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

В этом примере 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;
    }
}

Ответ 18

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

Что я в итоге сделал:

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

step 1: getting buttons positioned

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

step 2: add leading space constraints

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

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

step 3 & 4: change multiplier to xPos/screenWidth and set second item to .Trailing

Ответ 19

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

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]];

Ответ 20

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

  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 уменьшает код и выглядит красиво.

Ответ 21

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

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

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

Ответ 22

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

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

Выберите центральную метку и верхнюю метку (label1 + label2) и добавьте ограничения TWO​​strong > для вертикального интервала. Один с Большим или равным минимальным интервалом. Один с Меньше или равно максимальный интервал.

То же самое для центральной метки и нижней метки (label2 + label3).

Кроме того, вы также можете добавить два ограничения на label1 - Top Space To SuperView и два ограничения на label2 - Bottom Space To SuperView.

Результат будет состоять в том, что все 4 расстояния будут одинаково менять их размеры.

Ответ 23

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

 [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

Ответ 24

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

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

И вот портретная версия: enter image description here

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

Ответ 25

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

enter image description here

Ответ 26

Поздно к вечеринке, но у меня есть рабочее решение для создания меню по горизонтали с интервалом. Это можно легко сделать с помощью == в 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;
}

Ответ 27

В 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
}

Ответ 28

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