Для отображения всплывающих сообщений в iOS есть только один компонент — UIAlertView. Им очень удобно пользоваться, ведь создать и отобразить его можно буквально двумя-тремя строчками кода. Но у него есть один изъян: он блокирует интерфейс до того момента, пока пользователь не нажмёт кнопку, чтобы принудительно скрыть его. А иногда программисту всего лишь нужно показать какое-либо информационное сообщение, не требующее от пользователя никаких действий. В Android это решено с помощью компонента Toast, в который передаётся текст сообщения (а при необходимости и координаты, в которых оно должно отобразиться).
В одном из своих проектов мне нужно было отображать сообщения именно таким образом. И под впечатлением от Toast для Android я сделал собственный компонент, выводящий на экран всплывающие уведомления.
Компонент очень простой. Он умеет отображать сообщения в главном окне приложения, то есть мы не будем привязываться к текущему виду на экране. Это позволит избежать обсчёта координат для каждого вида, в котором нам нужно показать сообщение.
Вот задачи, которые я возложил на свой «тостер»:
– отобразить сообщение с анимацией
– подождать 1 секунду
– скрыть сообщение с анимацией
Я сконструировал объект-тостер так, чтобы он создавался и отображался одной строчкой кода:
[WToast showWithText:text];
После вызова этого метода объект сам контролирует своё поведение (появление, ожидание и скрытие). Прежде всего нам надо создать текстовую метку, которая и будет содержать наш текст. Так как текст может быть любой длины, придётся динамически обсчитывать размеры создаваемого компонента UILabel
. Для этого в фреймворке UIKit
есть специальный метод для объектов NSString
, позволяющий узнать размеры текста при заданных шрифте, ширине и типе переноса строк.
Итак, создаём объект UILabel
и вычисляем размеры текста:
UILabel *textLabel = [[UILabel alloc] init];
textLabel.backgroundColor = [UIColor clearColor];
textLabel.textAlignment = UITextAlignmentCenter;
textLabel.font = [UIFont systemFontOfSize:14];
textLabel.textColor = RGB(255, 255, 255);
textLabel.numberOfLines = 0;
textLabel.lineBreakMode = UILineBreakModeWordWrap;
CGSize sz = [text sizeWithFont:textLabel.font constrainedToSize:CGSizeMake(width - 20.0f, 9999.0f) lineBreakMode:textLabel.lineBreakMode];
Свойство numberOfLines
, выставленное в 0, указывает метке, что количество строк в ней не ограничено. Свойство lineBreakMode
сообщает, что при достижении правой границы будет происходить перенос по словам (это нужно, чтобы окончание строки не превратилось в многоточие). В методе -sizeWithFont:constrainedToSize:lineBreakMode:
параметр constrainedToSize
указывает максимальный размер будущей текстовой метки. Передав туда высоту 9999 пикселей, я гарантировано получу метку без многоточий в конце строки (ведь высота экрана iPhone составляет всего 480 пикселей).
Теперь нужно создать контейнер WToast
(который я сделал наследником UIView
), добавить ему полупрозрачный фон (так он смотрится красивее, чем если бы был непрозрачным), скруглить углы, поместить на него текстовую метку и отобразить его на экране.
Создаём контейнер:
CGRect tmpRect;
tmpRect.size.width = width;
tmpRect.size.height = MAX(sz.height + 20.0f, 38.0f);
tmpRect.origin.x = floor((screenWidth - width) / 2.0f);
tmpRect.origin.y = floor(screenHeight - tmpRect.size.height - 15.0f);
WToast *toast = [[WToast alloc] initWithFrame:tmpRect];
toast.backgroundColor = RGBA(0, 0, 0, 0.8f);
Обратите внимание на указание координат x и y для метки. Все координаты в iOS представлены типом float
, который может принимать дробные значения. Если какому-либо элементу интерфейса задать дробные координаты, он отобразится «замыленным» (кстати, некоторые приложения в App Store грешат этим; даже в приложении от Apple для iTunes Connect была такая проблема). Во избежание этого эффекта нужно округлять координаты до целого значения. Функция floor
округлит значение до нижней целой границы — то есть просто отбросит дробную часть.
RGBA — это макрос, который я написал для преобразования параметров RGB и alpha в UIColor. Он делает код компактнее и позволяет работать с RGB в привычном диапазоне 0..255 (а не 0..1, как это сделано в UIColor). Сравните размеры строки с оригинальным вызовом UIColor:
UIColor *blackTransparentColor = RGBA(0, 0, 0, 0.8f);
UIColor *blackTransparentColor = [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.8f];Определение макроса можно будет посмотреть в исходном коде компонента, ссылку на него я дам в конце статьи.
Мы получили наследника UIView
чёрного цвета и с прозрачностью 0.8. Теперь нужно скруглить ему углы (иначе он будет выглядеть очень топорно). Для скругления можно воспользоваться инструментами, доступными в фреймворке QuartzCore. Подключаем заголовочный файл QuartzCore/QuartzCore.h и фреймворк QuartzCore и делаем следующее:
CALayer *layer = toast.layer;
layer.masksToBounds = YES;
layer.cornerRadius = 5.0f;
Параметр cornerRadius
объекта CALayer
задаёт радиус скругления. Попробуйте поиграться с его значением и увидите, как будет изменяться рамка нашего уведомления.
Контейнер сообщения готов, текстовую метку мы создали в самом начале — собираем это всё воедино:
textLabel.text = text;
tmpRect.origin.x = floor((toast.frame.size.width - sz.width) / 2.0f);
tmpRect.origin.y = floor((toast.frame.size.height - sz.height) / 2.0f);
tmpRect.size = sz;
textLabel.frame = tmpRect;
[toast addSubview:textLabel];
[textLabel release];
toast.alpha = 0.0f;
После выполнения этого кода наш новорожденный объект toast
будет полностью прозрачным (toast.alpha = 0.0f
). Это сделано для дальнейшей анимации появления.
Для запуска анимации можно воспользоваться простейшим способом, предусмотренным разработчиками iOS — блоком [UIView beginAnimations:context:] .. [UIView commitAnimations]
. Все действия с объектами интерфейса, помещённые в этот блок, будут анимироваться (точнее, не совсем все; ниже я приведу пример, где анимация работать не будет):
[UIView beginAnimations:@"show" context:nil];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:0.2f];
[UIView setAnimationDidStopSelector:@selector(__animationDidStop:__finished:__context:)];
self.alpha = 1.0f;
[UIView commitAnimations];
Здесь мы задаём делегата, которому будет сообщено об окончании процесса анимации, продолжительность анимации и действия, которые нужно анимировать. В нашем случае это перевод объекта toast
из полностью прозрачного состояния в полностью непрозрачное (self.alpha = 1.0f
).
Вот что мы увидим на экране в результате выполнения нашей программы:
В методе -__animationDidStop:__finished:__context:
, который вызовется по окончании анимации, включается секундный таймер, запускающий скрытие нашего сообщения:
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(__hide) userInfo:nil repeats:NO];
Скрытие сообщения также будет анимированным:
[UIView beginAnimations:@"hide" context:nil];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:0.8f];
[UIView setAnimationDidStopSelector:@selector(__animationDidStop:__finished:__context:)];
self.alpha = 0.0f;
[UIView commitAnimations];
После этого объект можно удалить с экрана, вызвав метод removeFromSuperview
.
Если поместить вызов removeFromSuperview
в блок анимации, то исчезновение не будет анимироваться, объект исчезнет сразу после вызова этого метода. Поэтому сначала делаем «тостер» полностью прозрачным (self.alpha = 0.0f
), а затем, в селекторе окончания анимации, убираем объект с экрана.
Весь цикл работы компонента будет выглядеть так:
Не забывайте о том, что все действия, связанные с элементами пользовательского интерфейса, должны выполняться в главном потоке.
Стандартными средствами, предназначенными для работы с пользовательским интерфейсом, можно создавать множество нестандартных компонентов, которые сделают приложения более интересными и необычными. Поэтому фантазируйте, пробуйте — необычные интерфейсы вполне могут стать конкурентным преимуществом.
Исходные коды компонента WToast и тестовый проект можно загрузить с github. Этот компонент можно свободно использовать в любом вашем проекте без указания авторства. Он распространяется на условиях Public Domain.
🙈 Комментарии 70