Пишем оповещение для iOS

    Доброе утро/день/вечер/ночь %username%!

    Предыстория

    В процессе реализации очередного проекта появилась необходимость реализовать эффективное оповещение пользователя о чём-либо (например, об отсутствии интернет соединения). Так как же это сделать? Стандартный класс UIAlertView оказался для этой цели слишком громоздким и скучным, и, не найдя более ничего подходящего, было решено написать своё казино с блекдже… свой класс оповещения.

    Реализация

    Итак, приступим. Главным критерием для данного класса стал «вызов одной строчкой кода», соответственно я получил метод:

    + (void) showMessage:(NSString *)message;
    


    Исходя из поставленной задачи «очень простого оповещения пользователя» было решено использовать:
    • UILabel — 1шт.
    • UIView — 2шт.


    Для начало мы возьмём UILabel и «заточим» под свои нужды:

    UILabel *messageLabel = [[UILabel alloc] init];
    messageLabel.textAlignment = UITextAlignmentCenter;
    messageLabel.numberOfLines = 0;
    messageLabel.lineBreakMode = UILineBreakModeWordWrap;
    messageLabel.text = message;
    messageLabel.font = [UIFont fontWithName:@”Helvetica-Bold” size:15.0f];
    messageLabel.textColor = [UIColor whiteColor];
    messageLabel.backgroundColor = [UIColor clearColor];
    


    Дальше необходимо узнать – «А сколько же наше сообщение займет места на экране?»

    CGSize messageSize = [message sizeWithFont:messageLabel.font constrainedToSize:CGSizeMake(160.0f, 9999.0f) lineBreakMode:UILineBreakModeWordWrap];
    


    Метод для NSString sizeWithFont:(UIFont *)font constrainedToSize(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode вернет нам в данном случае размеры прямоугольника, в который будет «вписан текст» исходя из условий:
    • многострочность
    • ширина блока равна 160.0f (может быть больше, в случае, если слово не умещается в этот размер)


    Дальше необходимо из полученных размеров сделать сам прямоугольник с учетом 2х дополнительных размеров:
    • Толщина границы родительского элемента (в него мы «положим» UILabel)
    • Отступ от границ родительского элемента


    CGRect messageRect = CGRectMake(offsetSize.width + borderWidth, offsetSize.height + borderWidth, messageSize.width, messageSize.height); 
    messageLabel.frame = messageRect;
    


    Всё, на этом пока что мы закончили с UILabel.
    Дальше на очереди контейнер для UILabel – первый из двух UIView. Для контейнера необходимо было учесть – цвет фона, граница, отступы от границы. Как дополнение к дизайну «оповещалки» — скругленные углы, тень и прозрачность. Ниже приведен код нашего контейнера

    messageSize.width += offsetSize.width*2.0f + borderWidth;
    messageSize.height += offsetSize.height*2.0f + borderWidth;
    contentRect = CGRectMake(0.0f, 0.0f, messageSize.width, messageSize.height);
    UIView *content = [[UIView alloc] init];
    content.frame = contentRect;
    content.backgroundColor = [UIColor colorWithRed:(60.0f/255.0f) green:(60.0f/255.0f) blue:(60.0f/255.0f) alpha:1.0f];
    content.alpha = 0.8f;
    content.layer.cornerRadius = 8.0f;
    content.layer.shadowRadius = 8.0f;
    content.layer.masksToBounds = NO;
    content.layer.shadowOffset = CGSizeMake(0.0f, 4.0f);
    content.layer.shadowOpacity = 1.0f;
    content.layer.borderWidth = 1.0f; 
    content.layer.borderColor = [[UIColor colorWithRed:(128.0f/255.0f) green:(128.0f/255.0f) blue:(128.0f/255.0f) alpha:1.0f] CGColor];
    content.layer.shadowPath = [UIBezierPath bezierPathWithRect:contentRect].CGPath;
    


    На этом всё. Теперь нам остается задействовать лишь последний оставшийся UIView – rootView. Его мы будем использовать для анимации.

    UIView *rootView = [[UIView alloc] init];
    rootView.tag = 2000;
    rootView.frame = contentRect;
    rootView.alpha = 0.0f;
    


    Всё на этом этапе у нас есть подготовленные для дальнейшего использования UILabel с сообщением и 2 UIView контейнера. Осталось только собрать из них матрешку и установить позицию в центре экрана:

    messageLabel.center = CGPointMake(contentRect.size.width/2.0f, contentRect.size.height/2.0f);
    [content addSubview:messageLabel];
    [messageLabel release];
    [rootView addSubview:content];
    [content release];
    rootView.center = [[UIApplication sharedApplication] keyWindow].center;
    


    Анимация

    В нашем случае анимация была разбита на 3 составляющие:
    • Появление
    • Имитация bounce-эффекта
    • Исчезнование


    Bounce-эффект достигался простой анимацией увеличения rootView с размеров с коэффициентом 0.8 до размеров с коэффициентом 1.1 и немедленным уменьшением до естественного размера – коэффициент 1.0. Появление и исчезновение – изменение rootView.alpha с 0.0f до 1.0f и наоборот. Исчезновение так же сопровождается изменением размеров до коэффициента 1.2. Для появления и достижения «пиковой» точки bounce-эффекта (а именно когда будет достигнут коэффициент 1.2) было решено отвести 2/3 общего времени запланированного на анимацию. Остаток времени был использован на переход к нормальному отображению. Дальше пользователю отведено время для возможности прочесть сообщение и в последствии исчезновение сообщения
    Начиная с iOS версии 4.0 анимация реализовывается через блоки. Поэтому требуемая версия iOS не ниже 4.0 (в дальнейшем планируется добавить поддержку iOS 3).

    + (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion
    


    метод класса UIView позволяет нам установить:
    • продолжительность анимации
    • продолжительность паузы перед анимацией
    • настройки анимации
    • непосредственно саму анимацию
    • и код, который будет выполнен по завершению анимации


    Код реализации анимации приведен ниже:

    rootView.transform = CGAffineTransformMakeScale(0.8f, 0.8f);
    [[[UIApplication sharedApplication] keyWindow] addSubview:rootView];
    [UIView animateWithDuration:0.35*0.66 
    		delay:0.0 
    		options:UIViewAnimationCurveEaseOut
    		animations:^ {
    			rootView.alpha = 0.66f;
    			rootView.transform = CGAffineTransformMakeScale(1.1f, 1.1f);
    		} 
    		completion:^(BOOL completed) {
    			[UIView animateWithDuration:0.35*0.33 
    				delay:0.0 
    				options:UIViewAnimationCurveLinear 
    				animations:^{
    					rootView.alpha = 1.0f;
    					rootView.transform = CGAffineTransformIdentity;
    				} 
    				completion:^(BOOL completed) {
    					[UIView animateWithDuration:0.35
    					delay:0.9											options:UIViewAnimationCurveEaseIn
    					animations:^{
    						rootView.alpha = 0.0f;
    						rootView.transform = CGAffineTransformMakeScale(1.2f, 1.2f);
    					}
    					completion:^(BOOL completed) {
    						[rootView removeFromSuperview];
    						[rootView release];
    					}];
    			}];
    }];
    


    Итог
    В результате написания данного класса мы получили очень удобную “оповещалку”, которую можно вызвать одной строчкой кода:

    [FHNotification showMessage:@”Very simple inApp-notification”];
    


    Выводы

    Плюсы данного класса:
    • Очень и очень простой класс в использовании

    Минусы:
    • Поддержка только iOS 4.0+
    • Возможность только текстового оповещения


    Для работы данного класса так же потребуется QuartzCore.framework
    Исходный код можно скачать на github, немного дополненный и расширенный
    На этом всё. Спасибо за внимание
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 11

      +4
      Спасибо за статью.
      Есть замечательный фреймворк для отображения подобных уведомлений github.com/jdg/MBProgressHUD, очень советую.
        0
        что-то я его поиспользовал у себя, а потом обнаружил, что он глючит (несколько UIActivityIndicatorView разом показывает, один где надо, а один в верхнем левом углу). Может я просто как-то не так использую конечно…
          +2
          у меня многое глючит из стороннего, потому часто приходится писать свое :(
        +1
        Если ограничение ios >= 4.0 вызвано использованием блоков, то рекомендую глянуть на ESBlockRuntime и PLBlocks :)
          0
          «Создаем всплывающую подсказку» — описания-объяснения лучше этого я не видел нигде. На русском. Исходники проекта ищите в конце статьи.
            0
            А как у него обстоят дела с переключением Portrait / Landscape режима?
              +2
              Прикручивается. В ближайшее время будет.
                0
                это радует. ждём обновление статьи )
                  0
                  Обновились
              +1
              Теперь есть поддержка iOS 3.0+ и смены ориентации девайса, все на github
                +1
                Маленький совет
                Вот эту строку
                messageLabel.center = CGPointMake(contentRect.size.width/2.0f, contentRect.size.height/2.0f);
                заменить на

                messageLabel.center = CGPointMake((int)(contentRect.size.width/2.0f), (int)(contentRect.size.height/2.0f));

                При нечетных значениях width или height, ну к примеру 101, позиция будет установлена в значение 50,5. Из-за этого возможно расплывчатое отображение надписи

                Only users with full accounts can post comments. Log in, please.