Делаем приложение для iOS
Внимание! Этот документ ещё не опубликован.
Автор: Сергей Резник
В этой статье пойдет речь и создании простого графического редактора для iOS.
Введение
Создание каркаса приложения
Основной функционал
Дополнительный функционал
Заключение
Исходный код примера
Ссылки
Введение
Целью данной статьи является рассмотрение процесса создания простого приложения для iOS, обзор основых компонентов приложения и взаимодействия с пользователем. В статье будут рассмотрены основные этапы создания приложения для iOS на основе view-based приложения, основы использования Interface Builder'a и основые способы взаимодействия приложения с пользователем. Использовать будем Xcode 3.2.5 и iOS 4.2.1 SDK.
Создание каркаса приложения
Как вы уже поняли, мы будем создавать view-based приложение, то есть приложение, которое будет содержать одно основное окно, в котором пользователь и будет работать. Для этого создадим View-based Application (пункт меню File->New Project):

Видим каркас приложения - два основных класса PaintAppDelegate и PaintViewController. PaintAppDelegate - это класс, реализующий протокол UIApplicationDelegate [1]. С помощью него можно обрабатывать события от класса UIApplication (например реакцию на запуск приложения, на его сворачивание, на сообщение о нехватке памяти итд). Класс PainViewController - это класс, наследник от UIViewController, часть архитектуры model-view-controller. С помощью этого класса мы будем управлять поведением нашего вида (view).

Основной функционал
Итак, у нас все готово для того, чтобы начать писать наш простенький графический редактор. Функционал его будет заключатся в следующем: пользователь нажимает на экран и ведет палец, рисуя линию. Для этого, очевидно, нам нужно обрабатывать события нажатия, перемещения и отпускания пальца. Для этого, нам нужно переопределить методы UIView
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
Создадим новый класс, наследник от UIView, в котором и будем переопределять их:
@interface PaintView : UIView
{
...
}
@endТеперь нам необходимо открыть PaintViewController.xib в Interface Builder'e, выделить UIView и изменить его класс на PaintView.

Вся отрисовка UIView происходит в методе
- (void)drawRect:(CGRect)rect
Поэтому организуем работу приложения следующим образом: будем хранить все точки на экране, которые нужно соединить линиями во время отрисовки и рисовать их непосредственно в указанном выше методе. По нажатию пальца будем очищать список этих точек и устанавливать некий флаг, определяющий рисуем ли мы в данный момент (это нам пригодится немного дальше). По мере движения пальца будем добавлять новые точки в этот список и перерисовывать наш view, путем вызова метода setNeedsDisplay.
Отрисовка же будет происходить следующим образом: в начале будем рисовать то, что уже было нарисованно раньше (для этого нам, очевидно, придется хранить отдельный буфер - объект UIImage), а затем, поверх этого будем рисовать текущий набор точек. При отпускании пальца мы просто нарисуем текущий набор в буфер.
Для удобства хранения набора точек для отрисовки - воспользуемся встроенным контейнером NSMutableArray. Но так как в нем храняться объекты, унаследованные от NSObject, то создадим свой класс точки (по сути - объект-обертка над CGPoint):
@interface DrawPoint : NSObject
{
float x;
float y;
}
+ (DrawPoint*)drawPointWithCGPoint:(CGPoint)p;
- (CGPoint)getCGPoint;
@endреализация этого класса выглядит следующим образом:
+ (DrawPoint*)drawPointWithCGPoint:(CGPoint)p
{
DrawPoint* result = [[[DrawPoint alloc] init] autorelease];
result->x = p.x;
result->y = p.y;
return result;
}
- (CGPoint)getCGPoint
{
return CGPointMake(x, y);
}Теперь объявим в нашем PaintView все необходимые переменные:
CGPoint _pressPoint; // точка нажатия CGPoint _prevPoint; // точка, в которой был палец до перемещения NSMutableArray* _points; // текущий набор точек UIImage* _canvas; // буфер для хранения уже нарисованного BOOL _drawing; // флаг состояния
Реализацию методов отрисовки - смотрите в прикрепленном примере приолжения.
Дополнительный функционал
В текущей реализации все, что умеет наш "графический редактор" - это рисование черной линией, толщиной в один пиксель. Давайте разнообразим это и добавим настройки для толщины и цвета. Возникает вопрос - где же показывать эти настройки, если все окно у нас занимает "рабочая поверхность"? Предлагаю реализовать слежение за распространенным жестом "встряхивание" и показывать панель настроек под холстом. Сделаем это на подобии показывания области многозадачности (рабочая поверхность будет съезжать вверх, открывая под собой панель настроек).
Для того, чтобы отследить жест "встряхивание", нам необходимо чтобы наш PaintView был так называемым FirstResponder'ом (то есть непосредственно он ловил все сообщения от системы). Для этого необходимо переопределить у него метод:
- (BOOL)canBecomeFirstResponder
{
return YES;
}который будет сообщать о том, что PaintView может стать FirstResponder'ом. Далее, мы просто переопределяем метод motionBegan:
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (motion == UIEventSubtypeMotionShake)
{
// встряхивание!
}
}Теперь перейдем к обработке этого события. Все управление будем выполнять через PaintViewController. Для этого, воспользуемся стандартным (для iOS SDK) паттерном делегироввания. Создадим протокол для делегата PaintView, в котором опишем два метода:
@protocol PaintViewDelegate - (void)displayDrawingProperties; - (void)hideDrawingProperties; @end
а также заведем дополнительный флажок - индикатор того, что панель настроек видна. Теперь необходимо назначить нашему PaintView делегата. Для удобства воспользуемся Interface Builder'ом. Добавим в поля PaintView следующую строчку:
IBOutlet id<PaintViewDelegate> _delegate;
(об IBOutlet см. [2])
после чего откроем Interface Builder'ом файл PainViewController.xib, выберем в нем наш PaintView и во вкладке Referencing Outlets увидим наше поле _delegate. Соединим его с File Owner (то есть с классом PainViewController)

Теперь, когда у нас есть делегат для PaintView остается лишь вызывать его методы в правильном месте. Метод displayDrawingProperties будем вызывать, когда был обнаружен жест встряхивания, а метод hideDrawingProperties будем вызывать, когда у нас открыта панель настроек и пользователь коснулся пальцем холста:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if (_propertiesVisible)
{
_propertiesVisible = NO;
[_delegate hideDrawingProperties];
return;
}
...
}Теперь добавим в наш PaintView настройки для толщины линии и цвета кисти. Для этого будем использовать свойства. Обратите внимание - толщина линии - это float, поэтому это свойство будет просто присваиваться (assign), в то время как цвет кисти - это объект UIColor и при присвоении свойства его счетчик ссылок должен быть увеличен, поэтому будем использовать (retain).
@property (assign) float lineWidth; @property (retain) UIColor* paintColor;
В реализации класса PaintView используем ключевое слово synthesize для того, чтобы Xcode нам сгенерировал необходимые методы для указанных свойств и переменные для них:
@implementation PaintView @synthesize lineWidth = _lineWidth; @synthesize paintColor = _paintColor;
(использование см. в примере).
По сути работа с классом PaintView закончена, переходим к PaintViewController. Очень удобно пользоваться тем же IBOutlet для связи объектов. Создадим один для PaintView и второй для UIScrollView, который наи пригодится дальше:
@interface PaintViewController : UIViewController <PaintViewDelegate>
{
IBOutlet PaintView* _paintView;
IBOutlet UIScrollView* _propertiesScrollView;
}
- (IBAction)lineWidthChanged:(UISlider*)sender;
- (IBAction)paintColorChanged:(UISegmentedControl*)sender;
@endТип IBAction (по сути void), возвращаемый методами, означает, что данные методы будут использоваться для свзяи Interface Builder'ом [2]. Обратите внимание, что наш PaintViewController реализует протокол PaintVviewDelegate: UIViewController <PaintViewDelegate>.
Теперь откроем PaintViewController.xib и накидаем туда элементов управления (полная структура и свойства - см. пример):

Будем использовать слайдер для регулирования толщины линии и UISegmentedControl для выбора цвета (пока предоставим пользователю выбирать между 5 цветами). Теперь настало время перейти к реализации PaintViewController. В первую очередь, после загрузки его из .xib'a необходимо выставить размер контента для UIScollView и сделать наш PaintView FirstResponder'ом:
