iOS. Приемы программирования Нахавандипур Вандад
• UIKeyboardDidHideNotification — система выдает такое уведомление после того, как клавиатура полностью скроется из вида.
Уведомления клавиатуры содержат словарь, доступный с помощью свойства userInfo. Он указывает границы клавиатуры на экране и относится к типу NSDictionary. В словаре среди прочего имеется ключ UIKeyboardFrameEndUserInfoKey, содержащий объект типа NSValue. В свою очередь, этот объект содержит прямоугольник, ограничивающий размеры клавиатуры, когда она полностью отображена на экране. Эта прямоугольная область обозначается как CGRect.
Наша стратегия такова: нужно узнать, когда клавиатура полностью отобразится, а потом каким-то способом пересчитать размеры нашего текстового вида. Для этого воспользуемся свойством contentInset класса UITextView, чтобы задать границы контента, содержащегося в текстовом поле, — верхнюю, нижнюю, правую и левую:
— (void) handleKeyboardDidShow:(NSNotification *)paramNotification{
/* Получаем контур клавиатуры. */
NSValue *keyboardRectAsObject =
[[paramNotification userInfo]
objectForKey: UIKeyboardFrameEndUserInfoKey];
/* Помещаем эту информацию в CGRect. */
CGRect keyboardRect;
[keyboardRectAsObject getValue:&keyboardRect];
/* Задаем нижнюю границу нашего текстового вида так, чтобы он доходил ровно до верхней границы клавиатуры. */
self.myTextView.contentInset =
UIEdgeInsetsMake(0.0f,
0.0f,
keyboardRect.size.height,
0.0f);
}
— (void) handleKeyboardWillHide:(NSNotification *)paramNotification{
/* Делаем текстовый вид таким же по размеру, как и вид, содержащий его. */
self.myTextView.contentInset = UIEdgeInsetsZero;
}
— (void) viewWillAppear:(BOOL)paramAnimated{
[super viewWillAppear: paramAnimated];
[[NSNotificationCenter defaultCenter]
addObserver: self
selector:@selector(handleKeyboardDidShow:)
name: UIKeyboardDidShowNotification
object: nil];
[[NSNotificationCenter defaultCenter]
addObserver: self
selector:@selector(handleKeyboardWillHide:)
name: UIKeyboardWillHideNotification
object: nil];
self.myTextView = [[UITextView alloc] initWithFrame: self.view.bounds];
self.myTextView.text = @"Some text here…";
self.myTextView.font = [UIFont systemFontOfSize:16.0f];
[self.view addSubview: self.myTextView];
}
— (void) viewWillDisappear:(BOOL)paramAnimated{
[super viewWillDisappear: paramAnimated];
[[NSNotificationCenter defaultCenter] removeObserver: self];
}
В этом коде начинаем наблюдать за клавиатурными уведомлениями в методе viewWillAppear: и прекращаем слушать их в методе viewWillDisappear:. Важно убрать контроллер вида из списка слушателей, так как вы, вероятно, не хотите получать клавиатурные уведомления, инициируемые контроллером другого вида. Случается, что и при работе в фоновом режиме контроллер вида должен получать уведомления, но это бывает редко. Как правило, нужно прекращать слушание уведомлений в методе viewWillDisappear:. Мне не раз доводилось видеть, как программисты портят хорошие приложения, пренебрегая этой простой логикой.
Если вы намереваетесь изменять структуру пользовательского интерфейса, когда клавиатура выводится на экран и когда она с него убирается, то вам никак не обойтись без слушания клавиатурных уведомлений. Сообщения делегата UITextField запускаются всякий раз, когда начинается редактирование текстового поля, независимо от того, есть ли в этот момент на экране клавиатура. Не забывайте, что пользователь может подключить к устройству iOS беспроводную клавиатуру (с помощью Bluetooth). С этой клавиатуры он сможет редактировать содержимое текстовых полей, а также любых других информационных объектов вашего приложения. При подключении клавиатуры по Bluetooth виртуальная клавиатура на экране отображаться не будет. И если в вашем приложении пользовательский интерфейс станет обязательно перестраиваться, как только начинается ввод данных с клавиатуры, то при подключении беспроводной клавиатуры по Bluetooth такая перестройка окажется ненужной.
Теперь, если пользователь попытается ввести какой-либо текст в текстовый вид, клавиатура «выплывет» на экран снизу, и мы присвоим значение высоты клавиатуры в качестве нижней границы содержимого текстового вида. Таким образом, текстовый вид уменьшится в размерах и пользователь сможет вводить в него столько текста, сколько потребуется, — клавиатура не будет заслонять текст.
1.21. Добавление кнопок в пользовательский интерфейс с помощью UIButton
Постановка задачи
Необходимо отобразить в пользовательском интерфейсе кнопку и обрабатывать события касания, связанные с этой кнопкой.
Решение
Воспользуйтесь классом UIButton.
Обсуждение
Кнопки позволяют пользователям инициировать в приложениях те или иные действия. Например, пакет настроек iCloud в приложении Settings (Настройки) содержит кнопку Delete Account (Удалить учетную запись) (рис. 1.57). Если нажать эту кнопку, в приложении iCloud произойдет действие. Оно зависит от конкретного приложения. Не все приложения действуют одинаково, если пользователь нажимает в них кнопку Delete (Удалить). Как мы вскоре увидим, на кнопках могут присутствовать как изображения, так и текст.
Рис. 1.57. Кнопка Delete Account (Удалить учетную запись)
Кнопка может присваивать действия различным инициаторам (триггерам). Например, кнопка может производить одно действие, когда пользователь нажимает ее пальцем, и другое — когда убирает с нее палец. Эти движения становятся действиями, а объекты, реализующие действия, — их целями. Определим кнопку в файле реализации контроллера нашего вида:
#import «ViewController.h»
@interface ViewController ()
@property (nonatomic, strong) UIButton *myButton;
@end
@implementation ViewController
По умолчанию высота UIButton в iOS 7 указывается как 44.0f пункта.
Теперь переходим к реализации кнопки (рис. 1.58):
— (void) buttonIsPressed:(UIButton *)paramSender{
NSLog(@"Button is pressed.");
}
— (void) buttonIsTapped:(UIButton *)paramSender{
NSLog(@"Button is tapped.");
}
— (void)viewDidLoad{
[super viewDidLoad];
self.myButton = [UIButton buttonWithType: UIButtonTypeRoundedRect];
self.myButton.frame = CGRectMake(110.0f,
200.0f,
100.0f,
44.0f);
[self.myButton setTitle:@"Press Me"
forState: UIControlStateNormal];
[self.myButton setTitle:@"I'm Pressed"
forState: UIControlStateHighlighted];
[self.myButton addTarget: self
action:@selector(buttonIsPressed:)
forControlEvents: UIControlEventTouchDown];
[self.myButton addTarget: self
action:@selector(buttonIsTapped:)
forControlEvents: UIControlEventTouchUpInside];
[self.view addSubview: self.myButton];
}
Рис. 1.58. В центре экрана находится системная кнопка
В коде из данного примера мы применяем метод setTitle: forState: кнопки, задавая для нее два разных заголовка. Заголовок — это надпись на кнопке. В разное время кнопка может находиться в различных состояниях: обычном и утопленном (нажатом). В каждом из состояний надпись на ней может меняться. Например, в данном случае, когда пользователь впервые видит кнопку, на ней будет написано Press Me (Нажми меня). А когда он нажмет ее, надпись на кнопке изменится на I'm Pressed (Я нажата).
Аналогичная ситуация складывается и с действиями, инициируемыми кнопкой. Мы используем метод addTarget: action: forControlEvents:, чтобы указать для нашей кнопки два действия:
• действие, инициируемое, когда пользователь нажимает кнопку;
• другое действие, происходящее, когда пользователь уже нажал кнопку и убирает палец с экрана. Такое событие называется окончанием нажатия кнопки (touch-inside-up).
Еще одна вещь, которую необходимо знать о UIButton, заключается в том, что кнопке обязательно должен быть присвоен тип. Присваивание выполняется путем вызова метода класса buttonWithType на этапе инициализации, как показано в приведенном коде-примере. В качестве параметра этого метода передайте значение типа UIButtonType:
typedef NS_ENUM(NSInteger, UIButtonType) {
UIButtonTypeCustom = 0,
UIButtonTypeSystem NS_ENUM_AVAILABLE_IOS(7_0),
UIButtonTypeRoundedRect,
UIButtonTypeDetailDisclosure,
UIButtonTypeInfoLight,
UIButtonTypeInfoDark,
UIButtonTypeContactAdd,
UIButtonTypeRoundedRect = UIButtonTypeSystem,
}
Кроме того, на кнопке может находиться изображение, которое заменяет ее стандартный внешний вид. Если у вас есть изображение или серия изображений, которые вы хотите присвоить различным состояниям кнопки, убедитесь, что кнопка относится к типу UIButtonTypeCustom. Здесь я подготовил два изображения: одно для обычного состояния кнопки, а другое — для нажатого (утопленного). Сейчас я создам кнопку и присвою ей два этих изображения:
UIImage *normalImage = [UIImage iNamed:@"NormalBlueButton.png"];
UIImage *highlightedImage = [UIImage iNamed:@"HighlightedBlueButton"];
self.myButton = [UIButton buttonWithType: UIButtonTypeCustom];
self.myButton.frame = CGRectMake(110.0f,
200.0f,
100.0f,
44.0f);
[self.myButton setBackgroundImage: normalImage
forState: UIControlStateNormal];
[self.myButton setTitle:@"Normal"
forState: UIControlStateNormal];
[self.myButton setBackgroundImage: highlightedImage
forState: UIControlStateHighlighted];
[self.myButton setTitle:@"Pressed"
forState: UIControlStateHighlighted];
На рис. 1.59 показано, как выглядит приложение, если его запустить в эмуляторе iOS. Чтобы задать фоновое изображение, мы используем относящийся к кнопке метод setBackgroundImage: forState:. Работая с фоновым изображением, мы можем пользоваться методами setTitle: forState: для отображения текста поверх фонового изображения. Если ваше изображение содержит текст и, таким образом, никакой надписи на кнопке не требуется, можете воспользоваться методом setImage: forState: или просто удалить заголовки с кнопки.
Рис. 1.59. Кнопка с фоновым изображением
1.22. Показ изображений с помощью UIImageView
Постановка задачи
Требуется демонстрировать пользователям изображения в графическом интерфейсе программы.
Решение
Воспользуйтесь классом UIImageView.
Обсуждение
Класс UIImageView — один из наименее сложных в iOS SDK. Как вы знаете, существует особый вид, в котором демонстрируются изображения. Чтобы демонстрировать изображения, нужно всего лишь инстанцировать объект типа UIImageView и добавлять его к вашим видам. Например, у меня есть картинка Apple MacBook Air и я хочу показать ее в виде для изображений. Начнем с файла реализации контроллера:
#import «ViewController.h»
@interface ViewController ()
@property (nonatomic, strong) UIImageView *myImageView;
@end
@implementation ViewController
Инстанцируем вид для изображений и разместим в нем изображение:
— (void)viewDidLoad{
[super viewDidLoad];
UIImage *macBookAir = [UIImage iNamed:@"MacBookAir"];
self.myImageView = [[UIImageView alloc] initWithImage: macBookAir];
self.myImageView.center = self.view.center;
[self.view addSubview: self.myImageView];
}
Теперь, запустив программу, мы увидим такую картинку, как на рис. 1.60.
Рис. 1.60. Вид с изображением, которое довольно велико и не умещается на экране
Отмечу, что картинка Apple MacBook Air, которую я загружаю в этот вид, имеет разрешение 980 519 пикселов и, конечно же, не умещается на экране iPhone. Как решить эту проблему? Для начала нужно убедиться в том, что мы инициализируем наш вид для изображений с помощью метода initWithFrame:, а не initWithImage:, поскольку второй метод задает высоту и ширину вида с изображением равными высоте и ширине самого изображения. Итак, сначала решим эту проблему:
— (void)viewDidLoad{
[super viewDidLoad];
UIImage *macBookAir = [UIImage iNamed:@"MacBookAir"];
self.myImageView = [[UIImageView alloc] initWithFrame: self.view.bounds];
self.myImageView.i = macBookAir;
self.myImageView.center = self.view.center;
[self.view addSubview: self.myImageView];
}
Как теперь будет выглядеть наше приложение? Рассмотрим рис. 1.61.
Рис. 1.61. Изображение, которое умещается по ширине на экране устройства
Но мы не этого хотели добиться, правда? Действительно, контуры вида с изображением нам теперь подходят, но сама картинка стала отображаться неправильно. Что же можно сделать? Можно решить возникшую проблему, задав для вида с изображением свойство contentMode. Это свойство типа UIContentMode:
typedef NS_ENUM(NSInteger, UIViewContentMode) {
UIViewContentModeScaleToFill,
UIViewContentModeScaleAspectFit,
UIViewContentModeScaleAspectFill,
UIViewContentModeRedraw,
UIViewContentModeCenter,
UIViewContentModeTop,
UIViewContentModeBottom,
UIViewContentModeLeft,
UIViewContentModeRight,
UIViewContentModeTopLeft,
UIViewContentModeTopRight,
UIViewContentModeBottomLeft,
UIViewContentModeBottomRight,
}
Вот описание некоторых наиболее полезных значений из перечня UIViewContentMode:
• UIViewContentModeScaleToFill — позволяет масштабировать картинку в виде для изображения так, что она целиком заполнит вид в его границах;
• UIViewContentModeScaleAspectFit — позволяет гарантировать, что картинка внутри вида с изображением будет иметь правильное соотношение сторон (характеристическое отношение) и будет вписываться в границы вида с изображением;
• UIViewContentModeScaleAspectFill — позволяет гарантировать, что картинка внутри вида с изображением будет иметь правильное соотношение сторон и будет вписываться в границы вида с изображением. Чтобы данное значение действовало как следует, необходимо присвоить свойству clipsToBounds вида с изображением значение YES.
Свойство clipsToBounds вида UIView определяет, должны ли «подокна» этого вида обрезаться, если они выходят за границы содержащего их вида. Можно пользоваться этим свойством, если вы хотите с абсолютной точностью гарантировать, что «подокна» конкретного вида не будут отображаться вне границ содержащего их вида (или что они при необходимости непременно будут выходить за его границы — в зависимости от того, что именно вам требуется).
Итак, чтобы гарантировать, что определенная картинка целиком останется в границах вида с изображением и соотношение сторон этой картинки окажется правильным, нужно применять режим отображения содержимого UIViewContentModeScaleAspectFit:
— (void)viewDidLoad{
[super viewDidLoad];
UIImage *macBookAir = [UIImage iNamed:@"MacBookAir"];
self.myImageView = [[UIImageView alloc] initWithFrame: self.view.bounds];
self.myImageView.contentMode = UIViewContentModeScaleAspectFit;
self.myImageView.i = macBookAir;
self.myImageView.center = self.view.center;
[self.view addSubview: self.mImageView];
}
Получается как раз такой результат, которого мы добивались (рис. 1.62).
Рис. 1.62. Такое соотношение сторон картинки нам подходит
1.23. Создание прокручиваемого контента с помощью UIScrollView
Постановка задачи
Имеется контент, который необходимо отобразить на экране, но вся эта информация занимает больше экранного пространства, чем позволяет одновременно отобразить дисплей нашего устройства.
Решение
Воспользуйтесь классом UIScrollView.
Обсуждение
Прокручиваемый вид (Scroll View) — одно из очевидных достоинств, которые и делают операционную систему iOS такой удобной. Подобные виды встречаются практически в любых программах. Мы уже познакомились с приложениями Clock (Часы) и Contacts (Контакты). Вы заметили, что их содержимое можно прокручивать вверх и вниз? Да, в этом и заключается магия, присущая видам, о которых пойдет речь в этом разделе.
В сущности, есть всего одна базовая концепция, которую необходимо усвоить в связи с видами, чье содержимое можно прокручивать, — это размер содержимого. Учитывая размер содержимого, прокручиваемый вид может адаптироваться к размеру контента, который в нем находится. Размер содержимого — это значение типа CGSize, которое указывает высоту и ширину того материала, который наполняет вид с прокручиваемым контентом. Вид с прокручиваемым контентом, как следует из его названия, является подклассом UIView. Поэтому вы можете просто добавлять ваши виды к видам с прокручиваемым контентом, пользуясь методом addSubview:. Правда, нужно убедиться в том, что размер содержимого для прокручиваемого вида задан правильно. В противном случае эта информация прокручиваться не будет.
Найдем для примера большую картинку и загрузим ее в вид с изображением. Я воспользуюсь той самой картинкой, с которой мы работали в разделе 1.22: MacBook Air. Добавлю ее в вид с изображением, который помещу в вид с прокручиваемым контентом. Потом воспользуюсь свойством contentSize прокручиваемого вида, чтобы убедиться в том, что размеры этого материала равны размерам изображения (высоте и ширине). Начнем работу с файла реализации контроллера нашего вида:
#import «ViewController.h»
@interface ViewController ()
@property (nonatomic, strong) UIScrollView *myScrollView;
@property (nonatomic, strong) UIImageView *myImageView;