Вандад Нахавандипур - iOS. Приемы программирования
— (void) viewWillAppear:(BOOL)paramAnimated{
[super viewWillAppear: paramAnimated];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver: self selector:@selector(handleKeyboardWillShow:)
name: UIKeyboardWillShowNotification object: nil];
[center addObserver: self selector:@selector(handleKeyboardWillHide:)
name: UIKeyboardWillHideNotification object: nil];
}
— (void)viewWillDisappear:(BOOL)paramAnimated{
[super viewWillDisappear: paramAnimated];
[[NSNotificationCenter defaultCenter] removeObserver: self];
}
Многие программисты допускают распространенную ошибку: продолжают слушать клавиатурные уведомления, когда контроллер вида не отображается на экране. Они начинают слушать уведомления в методе viewDidLoad, а удаляют элементы, действовавшие в качестве наблюдателей, только в методе dealloc. Такой подход проблематичен, так как, когда наш вид не отображается на экране, а клавиатура вновь открывается в каком-то другом виде, вы не должны корректировать положение каких-либо других компонентов контроллера вида. Учтите, что клавиатурные уведомления, как и любые другие, широковещательно передаются всем объектам-наблюдателям в контексте вашего приложения. Поэтому придется принимать дополнительные меры, чтобы программа реагировала не на все клавиатурные уведомления. Если клавиатурные уведомления поступают от вида, не отображаемого на экране в данный момент, они должны игнорироваться.
В предыдущем фрагменте кода мы начали слушать уведомления типа «клавиатура будет отображена» в методе экземпляра handleKeyboardWillShow: контроллера нашего вида. Уведомления типа «клавиатура будет скрыта» мы ожидаем в методе handleKeyboardWillHide:. Пока эти методы еще не написаны. Начнем с первого метода, handleKeyboardWillShow:. В этом методе нам требуется определить высоту клавиатуры, воспользовавшись ключом UIKeyboardFrameEndUserInfoKey из словаря с пользовательской информацией, сопровождающего уведомление. Это значение мы используем, чтобы переместить содержимое вида вверх — так, чтобы все необходимые элементы оказались над клавиатурой. Здесь приятно вспомнить, что мы поместили все нужное содержимое в прокручивающемся виде. Соответственно, потребуется всего лишь откорректировать краевые отступы прокручивающегося вида:
— (void) handleKeyboardWillShow:(NSNotification *)paramNotification{
NSDictionary *userInfo = paramNotification.userInfo;
/* Получаем длительность клавиатурной анимации — время, за которое
клавиатура успеет отобразиться на экране. При анимировании и перемещении
содержимого вида мы будем применять такое же значение длительности.
*/
NSValue *animationDurationObject =
userInfo[UIKeyboardAnimationDurationUserInfoKey];
NSValue *keyboardEndRectObject = userInfo[UIKeyboardFrameEndUserInfoKey];
double animationDuration = 0.0;
CGRect keyboardEndRect = CGRectMake(0.0f, 0.0f, 0.0f, 0.0f);
[animationDurationObject getValue:&animationDuration];
[keyboardEndRectObject getValue:&keyboardEndRect];
UIWindow *window = [UIApplication sharedApplication].keyWindow;
/* Переводим размеры контура клавиатуры из координатной системы окна
в координатную систему нашего вида. */
keyboardEndRect = [self.view convertRect: keyboardEndRect
fromView: window];
/* Определяем, в какой степени наш вид накрыт клавиатурой */
CGRect intersectionOfKeyboardRectAndWindowRect =
CGRectIntersection(self.view.frame, keyboardEndRect);
/* Прокручиваем прокручивающийся вид таким образом, чтобы содержимое
нашего вида отображалось полностью */
[UIView animateWithDuration: animationDuration animations: ^{
self.scrollView.contentInset =
UIEdgeInsetsMake(0.0f,
0.0f,
intersectionOfKeyboardRectAndWindowRect.size.height,
0.0f);
[self.scrollView scrollRectToVisible: self.textField.frame animated: NO];
}];
}
У нас получился довольно интересный и прямолинейный код. Единственная деталь, возможно требующая дополнительного разъяснения, — это функция CGRectIntersection. В ней мы получаем информацию о прямоугольном контуре клавиатуры (о верхней границе, левой границе, ширине и высоте). Это параметры клавиатуры в момент завершения анимации, когда она полностью отобразится на экране. Теперь, зная параметры клавиатуры, можем воспользоваться функцией CGRectIntersection и определить, какая часть нашего вида накрыта клавиатурой. Итак, берем контур клавиатуры, контур вида, а затем определяем, какая часть контура вида накрыта контуром клавиатуры. В результате получаем структуру типа CGRect, соответствующую той прямоугольной области вида, которая накрыта клавиатурой. Известно, что клавиатура появляется на нижней границе экрана и в ходе анимации выплывает вверх. Поэтому нас интересует вертикаль этой области. Итак, мы получаем высоту области пересечения контура клавиатуры и контура вида, а затем поднимаем на эту высоту содержимое вида. Длительность анимации перемещения задаем равной длительности анимации выдвижения клавиатуры. Таким образом, движения клавиатуры и поднимающихся экранных элементов синхронизируются.
Далее нужно написать метод handleKeyboardWillHide:. В нем мы будем скрывать клавиатуру — соответственно, она больше не будет закрывать наш вид. Итак, в этом методе всего лишь требуется сбросить размеры краевых отступов прокручивающегося вида к начальным значениям, перенести все элементы обратно вниз, чтобы вид выглядел точно так же, как было до появления клавиатуры:
— (void) handleKeyboardWillHide:(NSNotification *)paramSender{
NSDictionary *userInfo = [paramSender userInfo];
NSValue *animationDurationObject =
[userInfo valueForKey: UIKeyboardAnimationDurationUserInfoKey];
double animationDuration = 0.0;
[animationDurationObject getValue:&animationDuration];
[UIView animateWithDuration: animationDuration animations: ^{
self.scrollView.contentInset = UIEdgeInsetsZero;
}];
}
И последний важный момент. Поскольку наш контроллер вида является делегатом текстового поля, необходимо обеспечить уход клавиатуры с экрана, если пользователь нажимает клавишу Return (Ввод) после ввода той или иной информации в текстовое поле:
— (BOOL) textFieldShouldReturn:(UITextField *)paramTextField{
[paramTextField resignFirstResponder];
return YES;
}
См. также
Разделы 1.19 и 1.20.
15.4. Планирование локальных уведомлений
Постановка задачи
Вы разрабатываете приложение, оперирующее данными о времени, например программу-будильник или программу-календарь. Это приложение должно информировать пользователя о событии в определенный момент времени, даже если в данный момент это приложение работает в фоновом режиме или вообще не запущено.
Решение
Инстанцируйте объект типа UILocalNotification, сконфигурируйте его (далее будет рассказано о том, как это делается) и запланируйте с помощью метода экземпляра scheduleLocalNotification:, относящегося к классу UIApplication. Чтобы получить экземпляр объекта вашего приложения, воспользуйтесь методом класса sharedApplication, относящимся к классу UIApplication.
Обсуждение
Если в данный момент приложение работает в фоновом режиме или не работает вообще, система выдает пользователю так называемое локальное уведомление. Чтобы запланировать доставку локального уведомления, используется метод экземпляра scheduleLocalNotification:, относящийся к классу UIApplication. Если приложение работает в приоритетном режиме и в это время срабатывает запланированное локальное уведомление, пользователь не получает никакого оповещения об этом. Вместо этого iOS бесшумно дает вам знать о том, что было выдано уведомление, — это делается через делегат приложения. Пока не будем вдаваться в детали этого процесса, рассмотрим его чуть позже.
Можно приказать iOS доставить локальное уведомление пользователю когда-нибудь в будущем, когда ваше приложение даже не будет работать. Кроме того, такие уведомления могут быть периодическими, например запускаться каждую неделю в определенное время. При этом необходимо особенно внимательно указывать дату запуска (Fire Date) ваших уведомлений.
Метод экземпляра cancelAllLocalNotifications отменяет доставку всех стоящих в очереди локальных уведомлений, поступивших от вашего приложения.
Уведомление типа UILocalNotification имеет много свойств. Наиболее важными из них являются следующие:
• fireDate — это свойство типа NSDate, сообщающее iOS, когда должен быть запущен экземпляр локального уведомления. Данное свойство является обязательным;
• timeZone — это свойство типа NSTimeZone сообщает iOS, к какому часовому поясу относится конкретная дата запуска. Для получения актуального часового пояса используется метод экземпляра timeZone, относящийся к классу NSCalendar. Вы можете получить актуальный календарь, воспользовавшись методом класса currentCalendar, относящимся к вышеупомянутому классу;