Текст книги "iOS. Приемы программирования"
Автор книги: Вандад Нахавандипур
Жанр: Зарубежная компьютерная литература, Зарубежная литература
сообщить о неприемлемом содержимом
Текущая страница: 16 (всего у книги 59 страниц)
Разделы 3.0 и 3.2.
3.2. Определение горизонтальных и вертикальных ограничений на языке визуального форматирования
Постановка задачиТребуется возможность определять ограничения, изменяющие способ выравнивания элемента пользовательского интерфейса по горизонтали или по вертикали в его вышестоящем виде.
РешениеВ строке форматирования ограничения пользуйтесь указателем ориентации H:, чтобы задать выравнивание по горизонтали, и указателем V: для выравнивания по вертикали.
ОбсуждениеЯ не буду утверждать, что язык визуального форматирования прост для понимания, – напротив, он довольно запутан. Поэтому приведу несколько примеров работы с ним, которые, надеюсь, прояснят ситуацию. Во всех этих примерах мы будем изменять горизонтальное выравнивание кнопки на экране.
1. Кнопка должна находиться на расстоянии 100 точек от каждого из краев ее вышестоящего вида:
H:|-100-[_button]-100-|
2. Кнопка должна находиться на расстоянии 100 точек или менее от левого края вышестоящего вида. Кроме того, ее ширина должна быть не меньше 50 точек, а расстояние между кнопкой и правым краем вышестоящего вида должно составлять 100 точек или менее:
H:|-(<=100)-[_button(>=50)]-(<=100)-|
3. Кнопка должна находиться на стандартном расстоянии от левого края вышестоящего вида (стандартные расстояния определяются Apple) и иметь ширину не менее 100, но не более 200 точек:
H:|-[_button(>=100,<=200)]
Как видите, может понадобиться некоторое время, чтобы привыкнуть к правилам форматирования. Но, как только вы усвоите основы этого процесса, он постепенно начнет укладываться у вас в голове. Аналогичные правила применяются и к выравниванию по вертикали, при котором используется указатель ориентации V:, например:
V:[_button]-(>=100)-|
При таком ограничении кнопка «прилипнет» к верхнему краю вышестоящего вида (не забывайте, что это ограничение действует по вертикали, так как начинается с V) и будет находиться на расстоянии не менее 100 точек от его нижнего края.
Итак, опробуем изученный материал на практике. Напишем на языке визуального форматирования ограничения, позволяющие сделать примерно такой же интерфейс, как на рис. 3.4.
Рис. 3.4. Интерфейс, который мы хотим получить, опираясь на наши ограничения и пользуясь языком визуального форматирования
Чтобы дизайнерам было проще принимать решения, а сами приложения выглядели более единообразно, Apple регламентирует стандартные расстояния (пробелы), которые следует оставлять между компонентами пользовательского интерфейса. Эти стандарты описаны в документе iOS Human Interface Guidelines.
Прежде чем вплотную заняться написанием кода, опишем ограничения, которые необходимо реализовать в соответствии с предыдущим рисунком.
• Поле для адреса электронной почты имеет стандартное расстояние по вертикали до верхней границы вида.
• Поле для подтверждения адреса электронной почты имеет стандартное расстояние по вертикали до поля с адресом электронной почты.
• Кнопка Register (Зарегистрировать) имеет стандартное расстояние по вертикали до поля для подтверждения адреса электронной почты.
• Все компоненты центрированы по горизонтали относительно родительского (вышестоящего) вида.
• Поля для адреса электронной почты и подтверждения этого адреса имеют стандартное расстояние по горизонтали от левого и правого краев вышестоящего вида.
• Ширина кнопки является фиксированной и составляет 128 точек.
Рассмотрим код, необходимый для реализации всех этих требований. Для начала просто определим все ограничения на языке визуального форматирования и поместим эти определения выше вида с контроллером:
/* Ограничения для поля с адресом электронной почты */
NSString *const kEmailTextFieldHorizontal = @"H:|-[_textFieldEmail]-|";
NSString *const kEmailTextFieldVertical = @"V:|-[_textFieldEmail]";
/* Ограничения для поля, в котором подтверждается адрес электронной почты */
NSString *const kConfirmEmailHorizontal = @"H:|-[_textFieldConfirmEmail]-|";
NSString *const kConfirmEmailVertical =
@"V:[_textFieldEmail]-[_textFieldConfirmEmail]";
/* Ограничение для регистрационной кнопки */
NSString *const kRegisterVertical =
@"V:[_textFieldConfirmEmail]-[_registerButton]";
Здесь мы видим, что оба текстовых поля сопровождаются применяемыми к ним по горизонтали и вертикали ограничениями, описанными на языке визуального форматирования. Кнопка регистрации, в свою очередь, имеет только ограничение по вертикали, также описанное на языке визуального форматирования. Почему? Оказывается, что на языке визуального форматирования невозможно выразить центрирование компонента пользовательского интерфейса по горизонтали. Для решения этой задачи воспользуемся приемом, изученным в разделе 3.1. Но пусть это вас не смущает – все равно стоит пользоваться языком визуального форматирования и наслаждаться его потенциалом. Да, он несовершенен, но это не повод от него отказываться.
Теперь определим компоненты пользовательского интерфейса как закрытые (приватные) свойства в файле реализации контроллера вида:
@interface ViewController ()
@property (nonatomic, strong) UITextField *textFieldEmail;
@property (nonatomic, strong) UITextField *textFieldConfirmEmail;
@property (nonatomic, strong) UIButton *registerButton;
@end
@implementation ViewController
<# Оставшаяся часть вашего кода находится здесь #>
Что дальше? Теперь нужно сконструировать сами компоненты пользовательского интерфейса в файле реализации контроллера вида. Итак, напишем два удобных метода, которые нам в этом помогут. Опять же не забывайте: мы не собираемся здесь задавать фреймы этих компонентов. Позже нам в этом поможет автоматическая компоновка:
– (UITextField *) textFieldWithPlaceholder:(NSString *)paramPlaceholder{
UITextField *result = [[UITextField alloc] init];
result.translatesAutoresizingMaskIntoConstraints = NO;
result.borderStyle = UITextBorderStyleRoundedRect;
result.placeholder = paramPlaceholder;
return result;
}
– (void) constructUIComponents{
self.textFieldEmail =
[self textFieldWithPlaceholder:@"Email"];
self.textFieldConfirmEmail =
[self textFieldWithPlaceholder:@"Confirm Email"];
self.registerButton = [UIButton buttonWithType: UIButtonTypeSystem];
self.registerButton.translatesAutoresizingMaskIntoConstraints = NO;
[self.registerButton setTitle:@"Register" forState: UIControlStateNormal];
}
Метод textFieldWithPlaceholder: просто создает текстовые поля, содержащие заданный подстановочный текст, а метод constructUIComponents, в свою очередь, создает два текстовых поля, пользуясь вышеупомянутым методом и кнопкой. Вы, вероятно, заметили, что мы присвоили свойству translatesAutoresizingMaskIntoConstraints всех наших компонентов пользовательского интерфейса значение NO. Так мы помогаем UIKit не перепутать маски автоматической подгонки размеров с ограничениями автоматической компоновки. Как вы знаете, можно задавать маски автоматической подгонки размеров для компонентов пользовательского интерфейса и контроллеров видов как в коде, так и в конструкторе интерфейсов. Об этом мы говорили в главе 1. Устанавливая здесь значение NO, мы гарантируем, что UIKit ничего не перепутает и не будет автоматически преобразовывать маски автоматической подгонки размера в ограничения автоматической компоновки. Эту функцию необходимо задавать, если вы смешиваете свойства автоматической компоновки компонентов с ограничениями макета. Как правило, следует устанавливать это значение у всех компонентов пользовательского интерфейса в NO всякий раз, когда вы работаете с ограничениями автоматической компоновки. Исключение составляют случаи, в которых вы специально приказываете UIKit преобразовать маски автоматической подгонки размеров в ограничения автоматической компоновки.
Мы создаем компоненты пользовательского интерфейса, но вполне очевидно, что методу viewDidLoad контроллера вида необходимо добавить к виду все три компонента пользовательского интерфейса. Почему бы не написать еще один небольшой метод, который будет заниматься именно этим?
– (void) addUIComponentsToView:(UIView *)paramView{
[paramView addSubview: self.textFieldEmail];
[paramView addSubview: self.textFieldConfirmEmail];
[paramView addSubview: self.registerButton];
}
Итак, почти все готово. Следующая крупная задача – создать методы, которые позволят сконструировать и собрать все ограничения в массив. У нас также есть удобный четвертый метод, который собирает все ограничения от всех трех компонентов пользовательского интерфейса и объединяет их в общий большой массив. Вот как мы его реализуем:
– (NSArray *) emailTextFieldConstraints{
NSMutableArray *result = [[NSMutableArray alloc] init];
NSDictionary *viewsDictionary =
NSDictionaryOfVariableBindings(_textFieldEmail);
[result addObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat: kEmailTextFieldHorizontal
options:0
metrics: nil
views: viewsDictionary]
];
[result addObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat: kEmailTextFieldVertical
options:0
metrics: nil
views: viewsDictionary]
];
return [NSArray arrayWithArray: result];
}
– (NSArray *) confirmEmailTextFieldConstraints{
NSMutableArray *result = [[NSMutableArray alloc] init];
NSDictionary *viewsDictionary =
NSDictionaryOfVariableBindings(_textFieldConfirmEmail, _textFieldEmail);
[result addObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat: kConfirmEmailHorizontal
options:0
metrics: nil
views: viewsDictionary]
];
[result addObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat: kConfirmEmailVertical
options:0
metrics: nil
views: viewsDictionary]
];
return [NSArray arrayWithArray: result];
}
– (NSArray *) registerButtonConstraints{
NSMutableArray *result = [[NSMutableArray alloc] init];
NSDictionary *viewsDictionary =
NSDictionaryOfVariableBindings(_registerButton, _textFieldConfirmEmail);
[result addObject:
[NSLayoutConstraint constraintWithItem: self.registerButton
attribute: NSLayoutAttributeCenterX
relatedBy: NSLayoutRelationEqual
toItem: self.view
attribute: NSLayoutAttributeCenterX
multiplier:1.0f
constant:0.0f]
];
[result addObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat: kRegisterVertical
options:0
metrics: nil
views: viewsDictionary]
];
return [NSArray arrayWithArray: result];
}
– (NSArray *)constraints{
NSMutableArray *result = [[NSMutableArray alloc] init];
[result addObjectsFromArray: [self emailTextFieldConstraints]];
[result addObjectsFromArray: [self confirmEmailTextFieldConstraints]];
[result addObjectsFromArray: [self registerButtonConstraints]];
return [NSArray arrayWithArray: result];
}
Фактически здесь мы имеем метод экземпляра constraints, относящийся к контроллеру вида; этот метод собирает ограничения от всех трех компонентов пользовательского интерфейса, а потом возвращает их все как один большой массив. Теперь переходим к основной части контроллера – методу viewDidLoad:
– (void)viewDidLoad{
[super viewDidLoad];
[self constructUIComponents];
[self addUIComponentsToView: self.view];
[self.view addConstraints: [self constraints]];
}
Этот метод просто собирает пользовательский интерфейс, добавляя сам к себе все компоненты пользовательского интерфейса и связанные с ними ограничения. При этом он использует методы, написанные нами ранее. Отлично, но что мы увидим на экране, когда запустим эту программу? Мы уже видели, как этот интерфейс выглядит на устройстве, работающем в книжной ориентации (см. рис. 3.4). А теперь повернем устройство и посмотрим, что получится при альбомной ориентации (рис. 3.5).
Рис. 3.5. Ограничения функционируют в альбомном режиме не хуже, чем в книжном
См. такжеРазделы 3.0 и 3.1.
3.3. Применение ограничений при работе с перекрестными видами
Постановка задачиТребуется выровнять компонент пользовательского интерфейса относительно другого компонента пользовательского интерфейса, притом что родительские элементы у этих компонентов разные.
РешениеОриентируясь на рис. 3.1, убедитесь, что вам удалось найти ближайший общий вышестоящий вид, являющийся родителем для интересующих вас компонентов пользовательского интерфейса. Затем добавьте ограничения к этому вышестоящему виду.
ОбсуждениеПрежде чем углубляться в детали, разберемся, в чем же заключаются ограничения перекрестных видов. Мне кажется, что суть проблемы удобнее изобразить на картинке, а не описывать словами, – предлагаю вашему вниманию рис. 3.6.
Рис. 3.6. Важные ограничения, налагаемые перекрестными видами на две кнопки
На этом рисунке к видам применяется немало ограничений. Разберем их по порядку, разложив все по полочкам.
• Есть основной вид с контроллером, в этом виде расположены еще два серых вида. Оба они должны отстоять от левой и правой границ вида с контроллером на стандартные расстояния. В частности, должно сохраняться стандартное расстояние между верхним серым видом и верхней границей вышестоящего вида. Между двумя серыми видами по вертикали также должно сохраняться стандартное пространство.
• Нужна кнопка, которая будет вертикально центрирована относительно обоих серых видов.
• Кнопка, расположенная в верхнем сером виде, слева должна быть удалена от края своего вышестоящего вида на стандартное расстояние.
• Левая сторона кнопки, находящейся в нижнем сером виде, должна быть выровнена по правой стороне той кнопки, что находится в верхнем сером виде. Это и есть ограничение для перекрестных видов, которое интересует нас в данном разделе.
• Серые виды должны автоматически изменять размер по мере того, как меняется ориентация вида с контроллером.
• Высота обоих серых видов должна составлять по 100 точек.
Итак, начнем. Чтобы выполнить все перечисленные задачи, вначале обратимся к методу viewDidLoad контроллера вида. Всегда стоит продумывать максимально чистый способ объединения методов. Конечно, в данном примере мы оперируем довольно большим количеством ограничений и видов. Как же нам не захламлять метод viewDidLoad контроллера вида? Вот так:
– (void)viewDidLoad{
[super viewDidLoad];
[self createGrayViews];
[self createButtons];
[self applyConstraintsToTopGrayView];
[self applyConstraintsToButtonOnTopGrayView];
[self applyConstraintsToBottomGrayView];
[self applyConstraintsToButtonOnBottomGrayView];
}
Мы просто распределили стоящие перед нами задачи по разным методам, которые вскоре реализуем. Продолжим – определим виды в файле реализации контроллера вида как расширение интерфейса:
#import «ViewController.h»
@interface ViewController ()
@property (nonatomic, strong) UIView *topGrayView;
@property (nonatomic, strong) UIButton *topButton;
@property (nonatomic, strong) UIView *bottomGrayView;
@property (nonatomic, strong) UIButton *bottomButton;
@end
@implementation ViewController
<# Оставшаяся часть вашего кода находится здесь #>
Далее следует реализовать метод createGrayViews. Как понятно из названия, этот метод отвечает за создание серых видов:
– (UIView *) newGrayView{
UIView *result = [[UIView alloc] init];
result.backgroundColor = [UIColor lightGrayColor];
result.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview: result];
return result;
}
– (void) createGrayViews{
self.topGrayView = [self newGrayView];
self.bottomGrayView = [self newGrayView];
}
Пока несложно? Оба серых вида добавляются к контроллеру нашего вида. Отлично. Что дальше? Теперь нужно реализовать метод createButtons, поскольку он вызывается в методе viewDidLoad контроллера вида. Этот метод должен просто создать кнопки и поместить каждую в ассоциированном с ней сером виде:
– (UIButton *) newButtonPlacedOnView:(UIView *)paramView{
UIButton *result = [UIButton buttonWithType: UIButtonTypeSystem];
result.translatesAutoresizingMaskIntoConstraints = NO;
[result setTitle:@"Button" forState: UIControlStateNormal];
[paramView addSubview: result];
return result;
}
– (void) createButtons{
self.topButton = [self newButtonPlacedOnView: self.topGrayView];
self.bottomButton = [self newButtonPlacedOnView: self.bottomGrayView];
}
Опять же в методе createButtons мы видим, что после создания серых видов и кнопок нужно применить ограничения к этим видам и кнопкам. Начнем с применения ограничений к верхнему серому виду. Эти ограничения должны обеспечивать соблюдение следующих условий:
• верхний вид должен находиться на стандартном расстоянии от вида с контроллером по левому и верхнему краю;
• высота этого серого вида должна составлять 100 точек.
– (void) applyConstraintsToTopGrayView{
NSDictionary *views =
NSDictionaryOfVariableBindings(_topGrayView);
NSMutableArray *constraints = [[NSMutableArray alloc] init];
NSString *const kHConstraint = @"H:|-[_topGrayView]-|";
NSString *const kVConstraint = @"V:|-[_topGrayView(==100)]";
/* Горизонтальные ограничения */
[constraints addObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat: kHConstraint
options:0
metrics: nil
views: views]
];
/* Вертикальные ограничения */
[constraints addObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat: kVConstraint
options:0
metrics: nil
views: views]
];
[self.topGrayView.superview addConstraints: constraints];
}
Здесь следует остановиться на том, как создается вертикальное ограничение верхнего серого вида. Как видите, мы задаем высоту верхнего вида равной 100 точкам и записываем эту информацию в формате (==100). Среда времени исполнения интерпретирует это значение именно как высоту, поскольку здесь есть указатель V:. Он сообщает среде времени исполнения о следующем: те числа, которые мы сообщаем системе, как-то связаны с высотой и вертикальным выравниванием целевого вида, а не с его шириной и горизонтальным выравниванием.
Далее займемся установкой ограничений для кнопки, находящейся в верхнем сером виде. Это делается с помощью метода applyConstraintsToButtonOnTopGrayView. Кнопка должна будет соответствовать перечисленным далее ограничениям:
• она должна быть вертикально центрирована в верхнем сером виде;
• она должна быть удалена на стандартное расстояние от левого и верхнего края этого серого вида.
У нее не должно быть жестко заданных высоты и ширины; эти значения будут зависеть от содержимого кнопки, в данном случае – от текста Button, который мы решили на ней написать:
– (void) applyConstraintsToButtonOnTopGrayView{
NSDictionary *views = NSDictionaryOfVariableBindings(_topButton);
NSMutableArray *constraints = [[NSMutableArray alloc] init];
NSString *const kHConstraint = @"H:|-[_topButton]";
/* Горизонтальные ограничения */
[constraints addObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat: kHConstraint
options:0
metrics: nil
views: views]
];
/* Вертикальные ограничения */
[constraints addObject:
[NSLayoutConstraint constraintWithItem: self.topButton
attribute: NSLayoutAttributeCenterY
relatedBy: NSLayoutRelationEqual
toItem: self.topGrayView
attribute: NSLayoutAttributeCenterY
multiplier:1.0f
constant:0.0f]
];
[self.topButton.superview addConstraints: constraints];
}
Итак, работа с верхним серым видом и находящейся в нем кнопкой завершена. Переходим к нижнему серому виду и его кнопке. Сейчас начнем работать с методом ConstraintsToBottomGrayView. Он будет задавать ограничения для нижнего серого вида. Просто напомню, что для этого вида нам требуется создать следующие ограничения:
• вид удален на стандартное расстояние от верхнего и левого края вышестоящего вида с контроллером;
• вид удален на стандартное расстояние от нижней границы верхнего серого вида;
• высота нижнего серого вида составляет 100 точек.
– (void) applyConstraintsToBottomGrayView{
NSDictionary *views =
NSDictionaryOfVariableBindings(_topGrayView,
_bottomGrayView);
NSMutableArray *constraints = [[NSMutableArray alloc] init];
NSString *const kHConstraint = @"H:|-[_bottomGrayView]-|";
NSString *const kVConstraint =
@"V:|-[_topGrayView]-[_bottomGrayView(==100)]";
/* Горизонтальные ограничения */
[constraints addObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat: kHConstraint
options:0
metrics: nil
views: views]
];
/* Вертикальные ограничения */
[constraints addObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat: kVConstraint
options:0
metrics: nil
views: views]
];
[self.bottomGrayView.superview addConstraints: constraints];
}
Вертикальные ограничения для нижнего серого вида, выраженные на языке визуального форматирования, выглядят длинновато, но, в сущности, они тривиальны. Приглядевшись к ним повнимательнее, вы заметите, что эти ограничения просто выравнивают верхний и нижний серые виды по сторонам их общего вышестоящего вида с контроллером. При этом используются указатели стандартного расстояния и постоянная высота, равная 100 точкам.
Следующий и, пожалуй, последний компонент пользовательского интерфейса, для которого мы собираемся написать ограничения, – это кнопка, расположенная в нижнем сером виде. Метод, который будет заниматься ее ограничениями, называется applyConstraintsToButtonOnBottomGrayView
. Перед тем как его реализовать, обсудим требования, которым должны соответствовать ограничения для нижней кнопки:
• кнопка должна быть вертикально центрирована в нижнем сером виде;
• ее левый край должен быть выровнен по правому краю кнопки, находящейся в верхнем сером виде;
• с ней не должны применяться строго определенные значения высоты и ширины; ее высота и ширина должны зависеть от содержимого – в данном случае от текста Button, который мы на ней записываем.
– (void) applyConstraintsToButtonOnBottomGrayView{
NSDictionary *views = NSDictionaryOfVariableBindings(_topButton,
_bottomButton);
NSString *const kHConstraint = @"H:[_topButton][_bottomButton]";
/* Горизонтальные ограничения */
[self.bottomGrayView.superview addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat: kHConstraint
options:0
metrics: nil
views: views]
];
/* Вертикальные ограничения */
[self.bottomButton.superview addConstraint:
[NSLayoutConstraint constraintWithItem: self.bottomButton
attribute: NSLayoutAttributeCenterY
relatedBy: NSLayoutRelationEqual
toItem: self.bottomGrayView
attribute: NSLayoutAttributeCenterY
multiplier:1.0f
constant:0.0f]
];
}
Наконец, мы должны удостовериться в том, что контроллер вида сообщает среде времени исполнения, что он может обрабатывать любые варианты ориентации. Ведь именно этот аспект наиболее сильно интересовал нас в данном разделе. Поэтому мы переопределим метод supportedInterfaceOrientations
в виде UIViewController
:
– (NSUInteger) supportedInterfaceOrientations{
return UIInterfaceOrientationMaskAll;
}
Итак, работа с этим контроллером вида завершена. Запустим приложение и посмотрим, как оно работает при книжной ориентации (рис. 3.7).
Рис. 3.7. Приложение отображает компоненты пользовательского интерфейса в книжной ориентации согласно требованиям, которые мы предъявили
А теперь момент истины! Будет ли оно работать в альбомном режиме? Попробуем (рис. 3.8).
Рис. 3.8. Как и ожидалось, тот же самый код отлично работает и при альбомной ориентации экрана
Отлично! Все получилось.
Правообладателям!
Это произведение, предположительно, находится в статусе 'public domain'. Если это не так и размещение материала нарушает чьи-либо права, то сообщите нам об этом.