Простое введение в компонентно-ориентированное программирование под iPhone
Часть 1
Interface Builder обладает мощными средствами для дизайна вашего приложения на уровне компонентов. Не только визуальных. Компонент вообще – это законченный узел, который умеет хорошо и чётко выполнять возложенные на него задачи. Будь то отрисовка чего-бы то ни было на экран, либо запись информации в файл в определенном компактном формате, адаптированном под наше приложение.
Любой такой компонент может быть представлен в виде жёлтого кубика в Interface Builder и интегрирован с остальными компонентами приложения. Не обязательно даже, чтобы они находились в одном XIB файле. У IB есть все необходимые примитивы для стыковки компонентов по слоям. Стыковка происходит весьма просто. Для этого вводится понятие “розеток” – IBOutlet-ов. Идеологическая нагрузка при таком подходе ложится на паттерн Dependency Injection.
Заинтересовавшиеся читатели могут подробнее почитать об этом паттерне в блоге Мартина Фаулера (на английском языке) или тут (на русском). Но давайте, чтобы вы пока не отвлекались, я дам вам простое определение на пальцах, что-же такое Dependency Injection. Представьте себе материнскую плату. Видите, там сбоку примостились разъёмы PCI, а где-то в противоположном углу виднеется гнездо под процессор? Плата наша относительно универсальна. В процессорное гнездо можно вставлять любой процессор, который имеет столько-же ножек, сколько контактов предусмотрено в гнезде, а в разъёмы PCI можно вставлять любые карты с этой шиной совместимые. Перестал вас устраивать звук вашего старенького Вортекса – пожалуйста, вставьте вместо него карточку МАудио. Место двухядерного процессора может с лёгкостью занять четырёхядерный, подарив вам меньшее время компиляции ваших программ.
Именно такую парадигму разыгрывают и программы, построенные по принципу Dependency Injection. Естественно? Несомненно. Поэтому, если подходить в разработке с головой, у вас и так получится Dependency Injection (вы можете об этом даже не догадываться). Но у многих программистов и раньше, и сейчас получаются программы, где, проводя аналогию с материнской платой, процессор и звуковая плата – не предоставляя никаких путей к модификации – распаяны прямо на плате. Причём даже не как отдельные компоненты, а с аккуратно рассредоточенной по всему текстолиту элементной базой. Если вдруг вы захотите модернизировать компьютер, то вам будет проще выкинуть эту материнскую плату вместе со всеми компонентами (переписать код) и докупить новую, расширяемую тем железом, что можно без труда найти на прилавках магазинов.
В терминах разработки под iPhone наш XIB как раз и является такой материнской платой. Через “розетки” к нему подключаются другие XIB-ы, а к ним – тоже через “розетки” – подключаются уже компоненты, выполняющие свою специфичную задачу. На примере разработки одного такого компонента мы сегодня и потренируемся в компонентно-ориентированном подходе в программировании.
В моих программах последнее время часто стали встречаться случаи, когда text-field при вводе текста перекрывался клавиатурой. Android в таком случае заботится о вас самостоятельно (обеспечивая тем самым обратную совместимость с программами, написанными под Android SDK 1.1, который не знал об экранной клавиатуре), а вот под iPhone спасение утопающих – дело рук самих утопающих.
Разумеется, я написал метод, который “слайдил” экран вверх и вниз за меня, анимируя процесс для “сексуальности”. А потом копи-пастил его в разные проекты, немного изменяя значения констант. Сегодня, вновь попав в тот-же самый кейс, я решил, что пора-бы выделить этот функционал в отдельный компонент, который будет автоматически разруливать такие ситуации за меня. Лишь благодаря копи-пасту и мелким адаптациям, которым подвергался этот метод в различных проектах, я уже представлял, как он должен выглядеть, чтобы покрыть как можно более широкий спектр случаев.
Добавьте новый класс к проекту. Я назову его S7ViewSlider. Что он будет уметь? Являясь делегатом для UITextField, он будет автоматически “слайдить” вверх либо вниз связанный с компонентом view. Можно также будет сказать ему, чтобы он автоматически закрывал клавиатуру при щелчке по кнопке return (удобно, когда это не return, а Done).
В хедере напишем вот что:
Тут _viewToSlide – это тот view, который и будет слайдиться. В большинстве случаев им будет родительский вид text-field-a. _ySlideDistance – это то расстояние, на которое нужно поднять view вверх. А _heightSlideDistance – это то значение, на которое нужно уменьшить высоту view, чтобы была возможность при поднятой клавиатуре, прокручивать view вверх-вниз. Если такая возможность вам кажется излишней, можете просто оставить это свойство по нулям. _slideDuration – это то время, в течение которого view будет изменять свои координаты. Опытным путём было выяснено, что клавиатура поднимается за 0.3f секунды, и именно такое значение стоит по умолчанию. _hideKeyboardOnReturn – это как-раз то, о чём я уже говорил. Если YES, то при щелчке по Done, клавиатура сама закроется, а view съедет вниз.
Реализация довольно-таки тривиальная. Синтезируем свойства, обрабатываем события text-field-a…
Обратите внимание, что viewToSlide объявлен у нас как IBOutlet (“розетка”). Сделано это для гладкой интеграции компонента в графической среде Interface Builder. Скомпилируйте проект, а затем откройте XIB с view и text-field-ом, который закрывается клавиатурой. Бросьте из палитры новый жёлтый кубик в окно Document. В Identity Inspector-e выставьте Class кубика в S7ViewSlider. Теперь нажмите правой кнопкой мыши по text-field-у. Появится серенькой окошко со всеми возможными для него “розетками”. Найдите свойство delegate. Теперь от кружочка справа от свойства тяните “провод” до только что добавленного жёлтого кубика. Как только среда подсветила его рамкой, отпускайте кнопку мыши. S7ViewSlider стал делегатом для этого text-field-a.
Теперь в окне Document щёлкните правой кнопкой мыши уже по S7ViewSlider. Привяжите свойство viewToSlide к одному из UIView – тому, который будет уезжать вверх при появлении клавиатуры. Скорее всего им окажется родительский view для text-field-a.
Всё бы могло уже работать, но компоненту S7ViewSlider нужно знать, на сколько поднимать view. Сделать это средствами Interface Builder не получится, поэтому нам понадобится дополнительно доконфигурировать компонент из кода. Что-ж. В контроллере, который по совместительству и File Owner нашего XIB-a, объявляем новое свойство:
Именно к этому свойству нужно будет прицепить жёлтый кубик компонента S7SliderView. Как это сделать вы уже знаете. Реализация MainController-a будет выглядеть следующим образом:
Тут мы говорим компоненту, что хотим автоматически убирать клавиатуру при щелчке по кнопке Done, а поднимать view хотим на 70 пикселей вверх (потом он на столько-же плавно и спустится). Запускайте программу, пробуйте себя в новой роли компонентно-ориентированного программиста. :) Именно из большого количества таких блочков, соединенных в визуальном редакторе, строятся все мои программы под iPhone. Все архитектурные решения элементарны. Дизайн программы, как визуальный, так и уровня кода, прозрачен и исследуется через Interface Builder-a, который представляет собой еще и средство самодокументации вашего кода. Ничего подобного под тот-же .NET вы не найдёте. Просто Interface Builder изначально писался с dependency injection in mind. В следующих статьях из серии мы разберем более сложные случаи превращения копи-паста в аккуратный компонентно-ориентированный дизайн. Будет показана формализация работы с сетью в компоненты-кубики.
P.S. Кстати, именно таким образом происходит и интеграция в Interface Builder 3rd party компонентов, только вместо жёлтого кубика вы кидаете UIView, а затем в поле Class указываете непосредственный класс компонента. Все остальные операции полностью аналогичны тем, что вы совершаете со стоковыми эппловскими компонентами.
Interface Builder обладает мощными средствами для дизайна вашего приложения на уровне компонентов. Не только визуальных. Компонент вообще – это законченный узел, который умеет хорошо и чётко выполнять возложенные на него задачи. Будь то отрисовка чего-бы то ни было на экран, либо запись информации в файл в определенном компактном формате, адаптированном под наше приложение.
Любой такой компонент может быть представлен в виде жёлтого кубика в Interface Builder и интегрирован с остальными компонентами приложения. Не обязательно даже, чтобы они находились в одном XIB файле. У IB есть все необходимые примитивы для стыковки компонентов по слоям. Стыковка происходит весьма просто. Для этого вводится понятие “розеток” – IBOutlet-ов. Идеологическая нагрузка при таком подходе ложится на паттерн Dependency Injection.
Заинтересовавшиеся читатели могут подробнее почитать об этом паттерне в блоге Мартина Фаулера (на английском языке) или тут (на русском). Но давайте, чтобы вы пока не отвлекались, я дам вам простое определение на пальцах, что-же такое Dependency Injection. Представьте себе материнскую плату. Видите, там сбоку примостились разъёмы PCI, а где-то в противоположном углу виднеется гнездо под процессор? Плата наша относительно универсальна. В процессорное гнездо можно вставлять любой процессор, который имеет столько-же ножек, сколько контактов предусмотрено в гнезде, а в разъёмы PCI можно вставлять любые карты с этой шиной совместимые. Перестал вас устраивать звук вашего старенького Вортекса – пожалуйста, вставьте вместо него карточку МАудио. Место двухядерного процессора может с лёгкостью занять четырёхядерный, подарив вам меньшее время компиляции ваших программ.
Именно такую парадигму разыгрывают и программы, построенные по принципу Dependency Injection. Естественно? Несомненно. Поэтому, если подходить в разработке с головой, у вас и так получится Dependency Injection (вы можете об этом даже не догадываться). Но у многих программистов и раньше, и сейчас получаются программы, где, проводя аналогию с материнской платой, процессор и звуковая плата – не предоставляя никаких путей к модификации – распаяны прямо на плате. Причём даже не как отдельные компоненты, а с аккуратно рассредоточенной по всему текстолиту элементной базой. Если вдруг вы захотите модернизировать компьютер, то вам будет проще выкинуть эту материнскую плату вместе со всеми компонентами (переписать код) и докупить новую, расширяемую тем железом, что можно без труда найти на прилавках магазинов.
В терминах разработки под iPhone наш XIB как раз и является такой материнской платой. Через “розетки” к нему подключаются другие XIB-ы, а к ним – тоже через “розетки” – подключаются уже компоненты, выполняющие свою специфичную задачу. На примере разработки одного такого компонента мы сегодня и потренируемся в компонентно-ориентированном подходе в программировании.
В моих программах последнее время часто стали встречаться случаи, когда text-field при вводе текста перекрывался клавиатурой. Android в таком случае заботится о вас самостоятельно (обеспечивая тем самым обратную совместимость с программами, написанными под Android SDK 1.1, который не знал об экранной клавиатуре), а вот под iPhone спасение утопающих – дело рук самих утопающих.
Разумеется, я написал метод, который “слайдил” экран вверх и вниз за меня, анимируя процесс для “сексуальности”. А потом копи-пастил его в разные проекты, немного изменяя значения констант. Сегодня, вновь попав в тот-же самый кейс, я решил, что пора-бы выделить этот функционал в отдельный компонент, который будет автоматически разруливать такие ситуации за меня. Лишь благодаря копи-пасту и мелким адаптациям, которым подвергался этот метод в различных проектах, я уже представлял, как он должен выглядеть, чтобы покрыть как можно более широкий спектр случаев.
Добавьте новый класс к проекту. Я назову его S7ViewSlider. Что он будет уметь? Являясь делегатом для UITextField, он будет автоматически “слайдить” вверх либо вниз связанный с компонентом view. Можно также будет сказать ему, чтобы он автоматически закрывал клавиатуру при щелчке по кнопке return (удобно, когда это не return, а Done).
В хедере напишем вот что:
#import <Foundation/Foundation.h>
typedef enum {
S7SlideDirectionUp,
S7SlideDirectionDown
} S7SlideDirection;
@interface S7ViewSlider : NSObject<UITextFieldDelegate> {
@private
UIView *_viewToSlide;
CGFloat _ySlideDistance;
CGFloat _heightSlideDistance;
CGFloat _slideDuration;
BOOL _hideKeyboardOnReturn;
}
@property (nonatomic, retain) IBOutlet UIView *viewToSlide;
@property (nonatomic, assign) CGFloat ySlideDistance;
@property (nonatomic, assign) CGFloat heightSlideDistance;
@property (nonatomic, assign) CGFloat slideDuration;
@property (nonatomic, assign) BOOL hideKeyboardOnReturn;
@end
* This source code was highlighted with Source Code Highlighter.Тут _viewToSlide – это тот view, который и будет слайдиться. В большинстве случаев им будет родительский вид text-field-a. _ySlideDistance – это то расстояние, на которое нужно поднять view вверх. А _heightSlideDistance – это то значение, на которое нужно уменьшить высоту view, чтобы была возможность при поднятой клавиатуре, прокручивать view вверх-вниз. Если такая возможность вам кажется излишней, можете просто оставить это свойство по нулям. _slideDuration – это то время, в течение которого view будет изменять свои координаты. Опытным путём было выяснено, что клавиатура поднимается за 0.3f секунды, и именно такое значение стоит по умолчанию. _hideKeyboardOnReturn – это как-раз то, о чём я уже говорил. Если YES, то при щелчке по Done, клавиатура сама закроется, а view съедет вниз.
Реализация довольно-таки тривиальная. Синтезируем свойства, обрабатываем события text-field-a…
#import "S7ViewSlider.h"
@interface S7ViewSlider (PrivateMethods)
- (void)slideTableView:(S7SlideDirection)direction;
@end
@implementation S7ViewSlider
@synthesize viewToSlide = _viewToSlide;
@synthesize ySlideDistance = _ySlideDistance, heightSlideDistance = _heightSlideDistance, slideDuration = _slideDuration;
@synthesize hideKeyboardOnReturn = _hideKeyboardOnReturn;
- (id)init {
if (self = [super init]) {
_slideDuration = 0.3f;
}
return self;
}
- (void)dealloc {
[_viewToSlide release];
[super dealloc];
}
#pragma mark TextField Callbacks
- (void)textFieldDidBeginEditing:(UITextField *)textField {
[self slideTableView:S7SlideDirectionUp];
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
[self slideTableView:S7SlideDirectionDown];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
if (self.hideKeyboardOnReturn) {
[textField resignFirstResponder];
}
return YES;
}
#pragma mark Private Methods
- (void)slideTableView:(S7SlideDirection)direction {
CGFloat y = S7SlideDirectionUp == direction ? -self.ySlideDistance : self.ySlideDistance;
CGFloat height = S7SlideDirectionUp == direction ? -self.heightSlideDistance : self.heightSlideDistance;
CGRect rect = self.viewToSlide.frame;
rect = CGRectMake(rect.origin.x, rect.origin.y + y, rect.size.width, rect.size.height + height);
[UIView beginAnimations:@"S7ViewSlider::slideView" context:nil];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:self.slideDuration];
self.viewToSlide.frame = rect;
[UIView commitAnimations];
}
@end
* This source code was highlighted with Source Code Highlighter.Обратите внимание, что viewToSlide объявлен у нас как IBOutlet (“розетка”). Сделано это для гладкой интеграции компонента в графической среде Interface Builder. Скомпилируйте проект, а затем откройте XIB с view и text-field-ом, который закрывается клавиатурой. Бросьте из палитры новый жёлтый кубик в окно Document. В Identity Inspector-e выставьте Class кубика в S7ViewSlider. Теперь нажмите правой кнопкой мыши по text-field-у. Появится серенькой окошко со всеми возможными для него “розетками”. Найдите свойство delegate. Теперь от кружочка справа от свойства тяните “провод” до только что добавленного жёлтого кубика. Как только среда подсветила его рамкой, отпускайте кнопку мыши. S7ViewSlider стал делегатом для этого text-field-a.
Теперь в окне Document щёлкните правой кнопкой мыши уже по S7ViewSlider. Привяжите свойство viewToSlide к одному из UIView – тому, который будет уезжать вверх при появлении клавиатуры. Скорее всего им окажется родительский view для text-field-a.
Всё бы могло уже работать, но компоненту S7ViewSlider нужно знать, на сколько поднимать view. Сделать это средствами Interface Builder не получится, поэтому нам понадобится дополнительно доконфигурировать компонент из кода. Что-ж. В контроллере, который по совместительству и File Owner нашего XIB-a, объявляем новое свойство:
@class S7ViewSlider;
@interface MainController : UIViewController {
@private
S7ViewSlider *_viewSlider;
}
@property (nonatomic, retain) IBOutlet S7ViewSlider *viewSlider;
@end
* This source code was highlighted with Source Code Highlighter.Именно к этому свойству нужно будет прицепить жёлтый кубик компонента S7SliderView. Как это сделать вы уже знаете. Реализация MainController-a будет выглядеть следующим образом:
@implementation MainController
@synthesize viewSlider = _viewSlider;
- (void)viewDidLoad {
[super viewDidLoad];
self.viewSlider.hideKeyboardOnReturn = YES;
self.viewSlider.ySlideDistance = 70.0f;
}
- (void)dealloc {
[_viewSlider release];
[super dealloc];
}
@end
* This source code was highlighted with Source Code Highlighter.Тут мы говорим компоненту, что хотим автоматически убирать клавиатуру при щелчке по кнопке Done, а поднимать view хотим на 70 пикселей вверх (потом он на столько-же плавно и спустится). Запускайте программу, пробуйте себя в новой роли компонентно-ориентированного программиста. :) Именно из большого количества таких блочков, соединенных в визуальном редакторе, строятся все мои программы под iPhone. Все архитектурные решения элементарны. Дизайн программы, как визуальный, так и уровня кода, прозрачен и исследуется через Interface Builder-a, который представляет собой еще и средство самодокументации вашего кода. Ничего подобного под тот-же .NET вы не найдёте. Просто Interface Builder изначально писался с dependency injection in mind. В следующих статьях из серии мы разберем более сложные случаи превращения копи-паста в аккуратный компонентно-ориентированный дизайн. Будет показана формализация работы с сетью в компоненты-кубики.
P.S. Кстати, именно таким образом происходит и интеграция в Interface Builder 3rd party компонентов, только вместо жёлтого кубика вы кидаете UIView, а затем в поле Class указываете непосредственный класс компонента. Все остальные операции полностью аналогичны тем, что вы совершаете со стоковыми эппловскими компонентами.



комментарии (7)