Войти
ПрограммированиеСтатьиОбщее

Делаем приложение для 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):

1 | Делаем приложение для iOS

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

2 | Делаем приложение для iOS

Основной функционал

Итак, у нас все готово для того, чтобы начать писать наш простенький графический редактор. Функционал его будет заключатся в следующем: пользователь нажимает на экран и ведет палец, рисуя линию. Для этого, очевидно, нам нужно обрабатывать события нажатия, перемещения и отпускания пальца. Для этого, нам нужно переопределить методы 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.

3 | Делаем приложение для iOS

Вся отрисовка 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)

4 | Делаем приложение для iOS

Теперь, когда у нас есть делегат для 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 и накидаем туда элементов управления (полная структура и свойства - см. пример):

5 | Делаем приложение для iOS

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

int numPropertyPages = 2;

- (void)viewDidLoad
{
 [_propertiesScrollView setContentSize:CGSizeMake(320 * numPropertyPages, _propertiesScrollView.frame.size.height)];
 [_paintView becomeFirstResponder];
}

Затем реализуем метода протокола PaintViewDelegate - анимированное отображение и скрытие панели настроек. Для этого будем использовать методы begin/commit animation у класса UIView:

CGPoint paintViewNormalCenter = { 160.0f, 240.0f };
CGPoint paintViewTopCenter = { 160.0f, 160.0f };

- (void)displayDrawingProperties
{
 [UIView beginAnimations:nil context:nil];
 [UIView setAnimationDuration:0.3f];
 _paintView.center = paintViewTopCenter;
 [UIView commitAnimations];
}

- (void)hideDrawingProperties
{
 [UIView beginAnimations:nil context:nil];
 [UIView setAnimationDuration:0.3f];
 _paintView.center = paintViewNormalCenter;
 [UIView commitAnimations];
}

После чего напишем обработчики событий для слайдера и SegmentedControl:

- (IBAction)lineWidthChanged:(UISlider*)sender
{
 [_paintView setLineWidth:sender.value];
}

- (IBAction)paintColorChanged:(UISegmentedControl*)sender
{
 switch (sender.selectedSegmentIndex)
 {
 ... // выставляем нужный цвет, в зависимости от выбранного элемента

Вот собственно и все. Компилируем наше приложение и запускаем его под iOS Simulator:

6 | Делаем приложение для iOS

Заключение

Как видим, процесс разработки приложений для iOS достаточно прост и удобен. Дополнительные удобства заключаются в использовании Interface Builder'a - в этом случае нет необходимости писать вручную код создания и форматирования элементов управления. По возможности следует всегда пользоваться именно таким способом.
Использование свойст избавляет нас от необходимости вручную управлять счетчиком ссылок, ими так же следует пользоваться.
В статье были упомянуты два паттерна проектирования, применяемые в iOS SKD - это "model-view-controller" и паттерн делегирования. Подробнее о этих паттернах см. [3]

Исходный код примера


sample

Ссылки


[1] Протокол UIApplicationDelegate
[2] IBOutlet и IBAction
[3] Паттерны проектирования в iOS SDK

#iOS, #iPad, #iPhone, #macOS, #XCode

22 марта 2011 (Обновление: 25 апр. 2011)

Комментарии [8]