Ответ 1
Чтобы сделать это с помощью автоматического макета, вы должны создать дополнительные представления, чтобы заполнить пробелы между текстовыми полями.
Вспомним, что ограничение автоматической компоновки - это в основном линейное уравнение A = m * B + c
. A
- это атрибут одного представления (например, координата Y нижнего края viewA
), а B
- атрибут другого представления (например, координата Y верхнего края viewB
). m
и c
являются константами. Так, например, чтобы выложить viewA
и viewB
так, чтобы между нижней частью viewA
и вершиной viewB
было 30 точек, мы могли бы создать ограничение, в котором m
равно 1 и c
-30.
Проблема, с которой вы сталкиваетесь, заключается в том, что вы хотите использовать одно и то же значение для c
для 13 различных ограничений, и вы хотите, чтобы автоматический макет вычислил значение c
для вас. Автоматический макет просто не может этого сделать. Не напрямую. Автомакет может вычислять только атрибуты представлений; он не может вычислить константы m
и c
.
Есть способ сделать автоматический макет, поместить взгляды туда, где вы хотите: reify пробелы между текстовыми полями как дополнительные (невидимые) представления. Вот пример с тремя текстовыми полями:
Мы создадим ограничение, чтобы прикрепить каждый верхний край проставки к нижнему краю текстового поля над ним. Мы также создадим ограничение, чтобы прикрепить каждый нижний край проставки к верхнему краю текстового поля под ним. И, наконец, мы создадим ограничение, чтобы заставить каждую прокладку иметь ту же высоту, что и самая верхняя проставка.
Нам понадобятся две переменные экземпляра для настройки: массив текстовых полей (по порядку сверху вниз) и ссылка на самый верхний вид спейсера:
@implementation ViewController {
NSMutableArray *textFields;
UIView *topSpacer;
}
Мы создадим текстовые поля и разделители в коде, так как трудно показать xib в ответе stackoverflow. Мы отбрасываем вещи в viewDidLoad
:
- (void)viewDidLoad {
[super viewDidLoad];
self.view.translatesAutoresizingMaskIntoConstraints = NO;
[self addTextFields];
[self addSpacers];
}
Поскольку мы собираемся использовать автоматическую компоновку, нам нужно отключить translatesAutoresizingMaskIntoConstraints
, чтобы система не создавала дополнительные ограничения.
Мы создаем каждое текстовое поле, придаем ему некоторый фиктивный текст и устанавливаем ограничения для его горизонтального положения и размера:
- (void)addTextFields {
textFields = [NSMutableArray array];
for (int i = 0; i < 12; ++i) {
[self addTextField];
}
}
- (void)addTextField {
UITextField *field = [[UITextField alloc] init];
field.backgroundColor = [UIColor colorWithHue:0.8 saturation:0.1 brightness:0.9 alpha:1];
field.translatesAutoresizingMaskIntoConstraints = NO;
field.text = [field description];
[self.view addSubview:field];
[field setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
[field setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|-[field]-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(field)]];
[textFields addObject:field];
}
Мы также будем использовать петлю для создания прокладок, но мы создаем верхние и нижние прокладки по-разному от средних прокладок, потому что нам нужно прикрепить верхний и нижний распорки к надписи:
- (void)addSpacers {
[self addTopSpacer];
for (int i = 1, count = textFields.count; i < count; ++i) {
[self addSpacerFromBottomOfView:textFields[i - 1]
toTopOfView:textFields[i]];
}
[self addBottomSpacer];
}
Здесь мы создаем верхний разделитель и устанавливаем его ограничения. Его верхний край прикреплен к супервизу, а его нижний край прикреплен к первому (верхнему) текстовому полю. Мы сохраняем верхний разделитель в переменной экземпляра topSpacer
, чтобы мы могли ограничить другие распорки одинаковой высотой, чем верхняя распорка.
- (void)addTopSpacer {
UIView *spacer = [self newSpacer];
UITextField *field = textFields[0];
[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:@"V:|[spacer][field]" options:0 metrics:nil
views:NSDictionaryOfVariableBindings(spacer, field)]];
topSpacer = spacer;
}
Здесь мы фактически создаем спейсер. Это просто скрытый вид. Поскольку мы не заботимся о его горизонтальном размере или позиции, мы просто привязываем его к левому и правому краям супервизора.
- (UIView *)newSpacer {
UIView *spacer = [[UIView alloc] init];
spacer.hidden = YES; // Views participate in layout even when hidden.
spacer.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:spacer];
[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:@"|[spacer]|" options:0 metrics:nil
views:NSDictionaryOfVariableBindings(spacer)]];
return spacer;
}
Чтобы создать "средний" разделитель между двумя текстовыми видами, мы привязываем его к нижнему краю текста поле выше и верхний край текстового поля ниже. Мы также ограничиваем его высоту равной высоте верхней проставки.
- (void)addSpacerFromBottomOfView:(UIView *)overView toTopOfView:(UIView *)underView {
UIView *spacer = [self newSpacer];
[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:@"V:[overView][spacer(==topSpacer)][underView]" options:0 metrics:nil
views:NSDictionaryOfVariableBindings(spacer, overView, underView, topSpacer)]];
}
Чтобы создать нижний разделитель, мы привязываем его к последнему текстовому полю и к супервину. Мы также ограничиваем его высоту равной высоте верхней проставки.
- (void)addBottomSpacer {
UIView *spacer = [self newSpacer];
UITextField *field = textFields.lastObject;
[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:@"V:[field][spacer(==topSpacer)]|" options:0 metrics:nil
views:NSDictionaryOfVariableBindings(spacer, field, topSpacer)]];
}
Если вы все сделаете правильно, вы получите такой результат:
Вы можете найти полный пример проекта в этот репозиторий github.