- PVSM.RU - https://www.pvsm.ru -
Здравствуйте, дорогие читатели !
Эта статья — вторая часть серии «Записки iOS разработчика».
Содержание:
Разберемся с кастомизируемыми всплывающими окнами в конце статьи, так как это довольно обширная тема. А пока что — все остальное.
Update: Господа минусующие! Пожалуйста, напишите в комментарии к статье, за что пошел минус.
И так, перед вами стоит проблема: клиенту не нравится стандартный переход Navigation Controller'а — «нахлест справа» — и он хочет, чтобы новый экран, например, появлялся «поворотом экрана снизу». Сразу видим вариант решения задачи: поменять вид Segue на Modal. Все было бы хорошо, да вот только логика Navigation Controller'a с его иерархией экранов нарушается; и как следствие, приложение вылетает. То есть теперь нам нужна Modal Segue, но с функциями Push Segue. Подозреваю, что есть способы попроще решить эту задачу, но я предлагаю просто написать подкласс UIStoryboardSegue. Единственное, что нам предстоит поменять, это .m файл нашего класса. А точнее, метод perform:
- (void) perform{
// Получаем экраны, с которыми будем работать
UIViewController *src = (UIViewController *) self.sourceViewController;
UIViewController *dst = (UIViewController *) self.destinationViewController;
// Осуществляем простой переход
[UIView transitionFromView:src.view
toView:dst.view
duration:1
options:UIViewAnimationOptionTransitionFlipFromBottom
completion:nil];
// Осуществляем переход для Navigation Controller'a
[UIView transitionFromView:src.navigationItem.titleView
toView:dst.navigationItem.titleView
duration:1
options:UIViewAnimationOptionTransitionFlipFromBottom
completion:nil];
// Добавляем Push нашей Segue
[src.navigationController pushViewController:dst animated:NO];
}
Вместо UIViewAnimationOptionTransitionFlipFromBottom можно поставить любой близкий сердцу вашего клиента стиль перехода.
Вот и все! Как просто, скажете вы. Теперь мы можем указать для любой Storyboard Segue стиль Custom, указать наш новый класс и заполучить Navigation Controller Segue со своим типом перехода.
В один прекрасный момент вашему клиенту надоело пинать дизайнера каждый раз, когда нужно изменить ширину кнопки на 5 пикселов (ведь для каждой кнопочки дизайнер нарисовал отдельную картинку). Сделаем собственную кнопку с закругленными краями и и рамкой вокруг при помощи QuartzCore.framework. Опять же, как и с синглтоном, это скорее сниппет, ускоряющий работу над проектом.
Вообще-то данный подход можно использовать с любым подклассом UIView (UIButton как-раз им и является). Мы переписываем метод awakeFromNib у нашего UIView:
- (void)awakeFromNib {
[super awakeFromNib];
self.layer.cornerRadius = 5.0f;
self.layer.masksToBounds = YES;
self.layer.borderColor = [UIColor whiteColor].CGColor;
self.layer.borderWidth = 1.0f;
}
Все просто. Во-первых, при наследовании нам нужно выполнить наш код после того, как суперкласс закончит свою работу. Так что мы вызываем тот же метод, но у суперкласса. Во-вторых, мы задаем радиус закругления углов у слоя нашего вида и заставляем слой подчиняться указанной маске. В-третьих, мы задаем цвет рамки (CGColor, конечно) и ее толщину.
Я знаю, что некоторые ожидали глубокой работы с графическим контекстом, но на то они и быстрые шпаргалки фрилансера — когда появится новая задача с Core Graphics, тогда и будем писать статьи.
У нас уже есть UIWebView, давайте подгрузим в него контент:
NSString *htmlString;
NSString *cssString;
<...Инициализируем строки...>
htmlString = [NSString stringWithFormat:@"<style>%@</style>%@", cssString, htmlString];
NSURL *url = [[NSURL alloc] initFileURLWithPath:pathToApplicationDirectory];
[webView loadHTMLString:htmlString baseURL:url];
Просто получили веб страничку с нашим стилем и html. Но какая неприятность! При попытке прокрутить страницу вниз, если включено «отпрыгивание», то при слишком сильном скроллинге, мы увидим серые (очень некрасивые!) поля оверскролла. Я предлагаю унаследовать оригинальный UIWebView и слегка изменить методы инициализации:
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self hideOverscrollShadowsForWebView:self];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self hideOverscrollShadowsForWebView:self];
}
return self;
}
Нужно переписать оба метода инициализации, потому что в своем коде вы скорее будете использовать метод initWithFrame:; в то время, как UIWebView из Stroryboard будет вызывать initWithCoder:.
И, конечно же, сам метод, прячущий неприятные глазу серые тени:
/*!
Метод, прячущий невероятно ужасные и некрасивые тени у данного UIWebView
param webView Собственно сам WebView, который нужно модифицировать
*/
- (void)hideOverscrollShadowsForWebView:(UIWebView *)webView {
id scrollview = [webView.subviews objectAtIndex:0];
for (UIView *subview in [scrollview subviews])
if ([subview isKindOfClass:[UIImageView class]])
subview.hidden = YES;
webView.backgroundColor = [UIColor clearColor];
}
Мы прячем все дочерние виды у Scrollview, принадлежащего нашему UIWebView. А так же устанавливаем прозрачный задний фон.
О UIScrollView можно сказать только одно: никогда не забывайте устанавливать свойство contentSize, и будет вам счастье.
Пришло время поговорить о гиганте этой статьи. В этот раз клиенту потребовалась возможность быстро и без вашего участия менять вид всплывающих окон. Что же, за дело!
Давайте поговорим немного о теории, о том, как мы все провернем.
В Storyboard у нас вряд ли получится реализовать полноценное всплывающее окно, а вот создать отдельный файл PopupView.xib мы можем!
А в качестве модели мы создадим три класса PopupView, PopupController, PopupControllerDelegate.
Сначала соберите макет своего всплывающего окна как на скриншоте. Учтите, что File's Owner в нашем случае будет объектом класса PopupController, а сам View будет объектом класса PopupView. Background View — это полупрозрачный светло-серый UIView.
Посмотрим сначала на реализацию PopupView.h:
#import <UIKit/UIKit.h>
@interface PopupView : UIView
@property (strong, nonatomic) IBOutlet UIView *backgroundView;
@property (strong, nonatomic) IBOutlet UIView *innerPopupView;
@property (strong, nonatomic) IBOutlet UILabel *popupTitleLabel;
@property (strong, nonatomic) IBOutlet UILabel *popupTextLabel;
@property (strong, nonatomic) IBOutlet UIButton *popupButton;
@end
Мы просто зацепили все элементы пользовательского интерфейса в код; PopupView.m мы не изменяли, оставили стандартный сгенерированный код.
У нас есть шаблон всплывающего окна, теперь нам нужно умело его использовать. Создаем PopupController.
PopupController.h:
#import <Foundation/Foundation.h>
#import "PopupControllerDelegate.h"
#import "PopupView.h"
@interface PopupController : NSObject
// UIViewController, который и будет разбираться с действиями в PopupView
@property (strong, nonatomic) UIViewController<PopupControllerDelegate> *delegate;
// Массив активных всплывающих окон. Их может быть несколько сразу!
@property (strong, nonatomic) NSMutableArray *activePopups;
- (IBAction)touchedButton:(UIButton *)sender;
- (id)initWithDelegate:(UIViewController<PopupControllerDelegate> *)delegate;
- (void)showHelloWorldPopup;
- (void)dismissAllPopups;
@end
Все что мы сделали — это установили нужные нам свойства и публичные методы инициализации, показа простенького всплывающего окна, закрытия всех всплывающих окон. А еще добавили небольшой Action для кнопочки внутри всплывающего окна в PopupController.
PopupController.m:
#import "PopupController.h"
@implementation PopupController
- (id)initWithDelegate:(UIViewController<PopupControllerDelegate> *)delegate {
self = [super init];
if (self) {
// Инициализируем массив всплывающих окон
self.activePopups = [NSMutableArray array];
// Установим себе делегата
self.delegate = delegate;
}
return self;
}
- (void)showHelloWorldPopup {
PopupView *popup = [self popupFromRestorationID:@"text"];
[self configurePopup:popup];
[self showPopup:popup];
}
- (IBAction)touchedButton:(UIButton *)sender {
[self.delegate touchedPopupButton:sender];
}
- (void)dismissAllPopups {
for (UIView *popup in activePopups) {
[self hidePopup:popup];
}
}
<...>
Во время инициализации мы сразу устанавливаем делегат нашего класса.
Код метода showHelloWorldPopup я разделил на три других метода для облегчения чтения: мы инициализируем всплывающее окно, настраиваем его и показываем.
Когда пользователь дотрагивается до кнопки на всплывающем окне, мы уведомляем об этом наш делегат.
Метод dismissAllPopups просто проходится по всем всплывающим окнам в общем массиве и закрывает каждое.
Допищем недостающие методы:
<...>
- (PopupView *)popupFromRestorationID:(NSString *)restorationID {
// Заполучаем все виды из нашего .xib файла
NSArray *allViews = [[NSBundle mainBundle] loadNibNamed:@"PopupView.xib" owner:self options:nil];
// Пройдемся по всем видам
for (PopupView *view in allViews) {
// Если restorationIdentifier тот, что нам нужен, то возвращаем окно, делаем его прозрачным и добавляем к делегату
if ([view.restorationIdentifier isEqualToString:restorationID]) {
view.alpha = 0.0f;
[self.delegate.view addSubview:view];
return view;
}
}
// Не нашли окно! Вернем пустоту
return nil;
}
- (void)showPopup:(PopupView *)popup {
// Уменьшим innerPopupView до 50%
[popup.innerPopupView setTransform:CGAffineTransformMakeScale(0.5, 0.5)];
// Начинаем анимацию
[UIView animateWithDuration:0.2f
animations:^{
// Возвращаем видимость всплывающего окна
popup.alpha = 1.0f;
// Возвращаем размер всплывающему окну до 100%
[popup.innerPopupView setTransform:CGAffineTransformMakeScale(1.0, 1.0)];
}];
// Добавим селектор нажатию на задний фон
[popup.backgroundView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissAllPopups)]];
// Добавим всплывающее окно в массив всплывающих окон
[activePopups addObject:popup];
}
- (void)hidePopup:(UIView *)popup {
// Начинаем анимацию
[UIView animateWithDuration:0.2f
animations:^{
// Делаем всплывающее окно прозрачным
popup.alpha = 0.0f;
}
completion:^(BOOL finished){
// Удаляем всплывающее окно из нашего ViewController'a
[popup removeFromSuperview];
}];
// Удаляем последний указатель на всплывающее окно из массива, автоматически выгружаем из памяти
[activePopups removeObject:popup];
}
- (void)configurePopup:(PopupView *)popup forName:(NSString *)name {
// Установим заголовок
popup.popupTitleLabel.text = @"Popup Title";
// Вставим текст
popup.popupTextLabel.text = @"Hello World!";
// Установим текст для кнопки
[self setTitle:@"Okay"];
}
- (void)setTitle:(NSString *)title forButton:(UIButton *)button {
[button setTitle:title forState:UIControlStateNormal];
[button setTitle:title forState:UIControlStateSelected];
[button setTitle:title forState:UIControlStateHighlighted];
[button setTitle:title forState:UIControlStateDisabled];
}
@end
Все хитрости поочередно:
Создав подобную архитектуру всплывающих окон, мы даем возможность клиенту менять внешний вид окон без нашего участия. Пускай клиент меняет цвет, размер, расположение элементов — пока он не трогает связи с кодом, все будет работать, как надо.
Ну, и на последок, покажу, как работает наш класс. Допишем PopupControllerDelegate.h:
#import <Foundation/Foundation.h>
@protocol PopupControllerDelegate
@required
- (void)touchedPopupButton:(UIButton *)sender;
@end
Любой UIViewController, на котором может появиться всплывающее окно, должен иметь возможность обрабатывать события этого окна, отвечать на протокол PopupControllerDelegate.
Например, мы хотим показать HelloWorld окошко после загрузки нашего вида, который отвечает нужному протоколу и у которого уже есть инициализированный объект popupController. Добавим следующий код в viewDidAppear:
[popupController showHelloWorldPopup];
И добавим обработчик события всплывающего окна:
- (void)touchedPopupButton:(UIButton *)sender {
// Просто закроем все всплывающие окна
[popupController dismissAllPopups];
}
Спасибо за то, что дочитали до конца!
Вторую часть статьи написал в аэропорту Домодедово перед вылетом в Ванкувер. Уже поздняя ночь, у меня кончается кофе. Прошу понять простить, и если нашли какую-либо опечаткунеточность в текстекоде, пожалуйста, сообщите в мой хабрацентр [3], не спешите ставить минус или писать гневный комментарий.
В следующих статьях будут темы еще интереснее: расшаривание контента в социальных сетях, PSPDFKit, Push Notifications.
Автор: backmeupplz
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/shpargalka/42251
Ссылки в тексте:
[1] Часть 1: Работа с Файлами; Шаблон Singleton; Работа с Аудио; Работа с Видео; In-App Purchases: http://habrahabr.ru/post/192062/
[2] Часть 2: Собственные всплывающие окна (Popups); Как использовать Modal Segue в Navigation Controller; Core Graphics; Работа с UIWebView и ScrollView: http://habrahabr.ru/post/192100/
[3] мой хабрацентр: http://habrahabr.ru/users/backmeupplz/
Нажмите здесь для печати.