Недавно обнаружил, что на хабре нет статей по работе с Core Graphics в iOS, также как не нашел подобных статей на русском языке. На сайте Apple для разработчиков есть документация по работе с 2D графикой в iOS — Core Graphics Framework Reference, включающее 400 страниц документации, полезной в качестве справки, но не дающей начального представления о работе с видами и рисованием. Поэтому я решил написать вводную статью по работе с 2D графикой в iOS.
Известно, что Cocoa Touch не включает AppKit, предоставляющий классы и методы для работы интерфейсом в Mac OS X. Вместо AppKit на платформе iOS для работы с 2D используется низкоуровневый Cи-фреймворк Core Graphics, который также является частью Mac OS X SDK, но используется реже, так как Си-функции и работа с памятью не настолько удобные и гибкие, как классы Objective-C в AppKit. Кроме того, очевидно, что при работе с Core Graphics приходится писать больше кода, который сложнее расширять и поддерживать. В этой статье мы создадим Custom View и познакомимся с возможностями фреймворка.
Для начала нам нужно создать приложение и добавить в него Custom View, на котором далее мы будем рисовать наши объекты.
Создадим в Xcode View-based Application и назовем его Graphics.

Далее добавим наш View (Cmd+N) и назовем его MyCanvas,

который будет классом Objective-C и сабклассом UIView.
Всё «рисование» происходит в методе:
Следует помнить, что мы никогда не вызываем напрямую метод -drawRect:. Мы только реализуем данный метод, который выполняется при инициализации View.
Для обновления содержимого, когда нам нужно обновить данный View или какую-то его часть (aRect), мы используем методы UIView
соответственно.
Нарисуем простой квадрат с помощью метода drawRect:. Все рисование происходит в контексте (context), который определяет, где происходит рисование. Чтобы получить context используется Си-функция UIGraphicsGetCurrentContext. В iOS центр координат при рисовании находится в левом верхнем углу, а не в левом нижнем, как у Mac OS X.
Очищать контекст необходимости в данном случае нет, так как мы не будем перерисовывать нашу графику, однако это хорошая практика очищать графическ��е элементы перед рисованием. Следует учесть, что после очистки View станет чёрным.
Функция CGContextSetRGBFillColor устанавливает цвет заливки для данного контекста, а функция CGContextFillRect рисует квадрат.
Теперь запустим наше приложение в симуляторе (Cmd+R), и как мы видим, ничего не произошло. Дело в том, что мы не создали наш View и не добавили его в иерархию.
Существует два способа создания View, в том числе и Custom View — Interface Builder и непосредственно с помощью кода. Что касается IB, всё просто, добавляем элемент UIView перетаскиванием и устанавливаем ему Custom Class: MyView в Identity Inspector.
Для того, чтобы создать наш View вручную создадим его в GraphicsViewController.m:
Для добавления View в иерархию используется addSubview:, для удаления из иерархии removeFromSuperview:. При этом первый метод отправяется супервиду, а второй виду, который нужно удалить.
Подробнее о работе с памятью будет рассказано в конце статьи, здесь только отмечу, что необходимо иметь в виду, когда мы освобождаем из верхний view в иерархии, мы также освобождаем все его subviews, и если потом обратимся к ним, приложение упадет.
Теперь, если мы запустим наше приложение, то увидим красный квадрат с отступами в 20 поинтов.
При рисовании мы используем поинты (pt), а не пиксели (px), для того, чтобы размеры были одинаковы для Retina и обычного дисплея. Работа с пикселями также возможна. Для определения типа экрана у конкретного устройства используется свойство @property CGFloat contentScaleFactor; которое возвращает «масштаб» (сколько пикселей в поинте для данного view на экране). Значение может быть 2.0 для retina или 1.0 для обычного экрана.

Рассмотрим работу с другими примитивами:
1) Окружность c синей заливкой, создается методом CGContextFillEllipseInRect, и вписывается в прямоугольник, таким образом можно создавать эллипсы.
2) Окружность без заливки с зеленый контуром и толщиной линии 3, толщина линии задается методом CGContextSetLineWidth, а окружность без заливки рисуется методом CGContextStrokeEllipseInRect.
3) Треугольник, вписанный в красный квадрат, рисуется по точкам из массива.
4) Безье сплайн. В данном случае нужно задать начальную и конечную точки, а также координаты точек, задающих кривизну. Для тех, кто не знаком с кривыми Безье, краткий краш-курс:

Зададим точки отдельными CGPoint’ами, чтобы не путаться:
Нарисуем более сложный элемент, например, синусойду:
В 5й строке мы добавили 380 поинтов, чтобы сместить фукнцию по вертикали вниз.
В итоге экран нашего айфона после запуска приложения будет выглядеть так:

Наиболее удобный способ, для того, чтобы вывести текст на экран — использовать объект UILabel, который имеет множество методов преобразования текста. Однако, встречаются ситуации, когда нужно нарисовать текст в методе -drawRect, например, если мы хотим повернуть текст.
Рисование с помощью Core Graphics:
Для выбора шрифта, размера, стиля отображения, метрики можно использовать объект UIFont. А с помощью метода [UIFont familyNames] можно получить массив доступных шрифтов.
Если мы хотим использовать UILabel и при этом создать его программно, а не в Interface Builder, добавим следующий код в наш GraphicsViewController.m:
Для освобождения из памяти views, не используют [myView release]; Вместо этого создадим метод releaseOutlets:
И вызовем данный метод из viewDidUnload и dealloc:
Немного странное выражение self.myView = nil; на самом деле легко объясняется, если взглянуть на сеттер, который синтезирует для нас комманда synthesize myView:
Если anObject = nil, то метод последняя строка в методе присваивает myView nil, а предыдущая освобождает из памяти текущий View — то, что мы и хотели сделать.
На этом краткое введение в CoreGraphics закончено, надеюсь оно окажется кому-нибудь полезным.
Ссылки для дальнейшего изучения:
View Programming Guide for iOS
Core Graphics Framework Reference
Введение
Известно, что Cocoa Touch не включает AppKit, предоставляющий классы и методы для работы интерфейсом в Mac OS X. Вместо AppKit на платформе iOS для работы с 2D используется низкоуровневый Cи-фреймворк Core Graphics, который также является частью Mac OS X SDK, но используется реже, так как Си-функции и работа с памятью не настолько удобные и гибкие, как классы Objective-C в AppKit. Кроме того, очевидно, что при работе с Core Graphics приходится писать больше кода, который сложнее расширять и поддерживать. В этой статье мы создадим Custom View и познакомимся с возможностями фреймворка.
Начало работы
Для начала нам нужно создать приложение и добавить в него Custom View, на котором далее мы будем рисовать наши объекты.
Создадим в Xcode View-based Application и назовем его Graphics.

Далее добавим наш View (Cmd+N) и назовем его MyCanvas,

который будет классом Objective-C и сабклассом UIView.
Всё «рисование» происходит в методе:
- (void)drawRect:(CGRect)rect
{
// Рисуем тут
}Следует помнить, что мы никогда не вызываем напрямую метод -drawRect:. Мы только реализуем данный метод, который выполняется при инициализации View.
Для обновления содержимого, когда нам нужно обновить данный View или какую-то его часть (aRect), мы используем методы UIView
-(void)setNeedsDisplay;
-(void)setNeedsDisplayInRect:(CGRect)aRect; соответственно.
Нарисуем простой квадрат с помощью метода drawRect:. Все рисование происходит в контексте (context), который определяет, где происходит рисование. Чтобы получить context используется Си-функция UIGraphicsGetCurrentContext. В iOS центр координат при рисовании находится в левом верхнем углу, а не в левом нижнем, как у Mac OS X.
- (void)drawRect:(CGRect)rect
{
// Получим context
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, rect); // Очистим context
CGContextSetRGBFillColor(context, 255, 0, 0, 1);
CGContextFillRect(context, CGRectMake(20, 20, 100, 100));
}Очищать контекст необходимости в данном случае нет, так как мы не будем перерисовывать нашу графику, однако это хорошая практика очищать графическ��е элементы перед рисованием. Следует учесть, что после очистки View станет чёрным.
Функция CGContextSetRGBFillColor устанавливает цвет заливки для данного контекста, а функция CGContextFillRect рисует квадрат.
Теперь запустим наше приложение в симуляторе (Cmd+R), и как мы видим, ничего не произошло. Дело в том, что мы не создали наш View и не добавили его в иерархию.
Существует два способа создания View, в том числе и Custom View — Interface Builder и непосредственно с помощью кода. Что касается IB, всё просто, добавляем элемент UIView перетаскиванием и устанавливаем ему Custom Class: MyView в Identity Inspector.
Для того, чтобы создать наш View вручную создадим его в GraphicsViewController.m:
- (void)viewDidLoad
{
[super viewDidLoad];
MyCanvas *myView = [[MyCanvas alloc] initWithFrame:self.view.bounds];
[self.view addSubview:myView];
}Для добавления View в иерархию используется addSubview:, для удаления из иерархии removeFromSuperview:. При этом первый метод отправяется супервиду, а второй виду, который нужно удалить.
Подробнее о работе с памятью будет рассказано в конце статьи, здесь только отмечу, что необходимо иметь в виду, когда мы освобождаем из верхний view в иерархии, мы также освобождаем все его subviews, и если потом обратимся к ним, приложение упадет.
Теперь, если мы запустим наше приложение, то увидим красный квадрат с отступами в 20 поинтов.
При рисовании мы используем поинты (pt), а не пиксели (px), для того, чтобы размеры были одинаковы для Retina и обычного дисплея. Работа с пикселями также возможна. Для определения типа экрана у конкретного устройства используется свойство @property CGFloat contentScaleFactor; которое возвращает «масштаб» (сколько пикселей в поинте для данного view на экране). Значение может быть 2.0 для retina или 1.0 для обычного экрана.

Рассмотрим работу с другими примитивами:
1) Окружность c синей заливкой, создается методом CGContextFillEllipseInRect, и вписывается в прямоугольник, таким образом можно создавать эллипсы.
CGContextSetRGBFillColor(context, 0, 0, 255, 1);
CGContextFillEllipseInRect(context, CGRectMake(30, 140, 80, 80));2) Окружность без заливки с зеленый контуром и толщиной линии 3, толщина линии задается методом CGContextSetLineWidth, а окружность без заливки рисуется методом CGContextStrokeEllipseInRect.
CGContextSetRGBStrokeColor(context, 0, 255, 0, 1);
CGContextSetLineWidth(context, 3.0);
CGRect circleRect = CGRectMake(140, 20, 100, 100);
CGContextStrokeEllipseInRect(context, circleRect);3) Треугольник, вписанный в красный квадрат, рисуется по точкам из массива.
CGContextSetRGBStrokeColor(context, 255, 0, 255, 1);
CGPoint points[6] = {CGPointMake(70, 20), CGPointMake(120, 120),
CGPointMake(120, 120), CGPointMake(20, 120),
CGPointMake(20, 120), CGPointMake(70, 20)};
CGContextStrokeLineSegments(context, points, 6);4) Безье сплайн. В данном случае нужно задать начальную и конечную точки, а также координаты точек, задающих кривизну. Для тех, кто не знаком с кривыми Безье, краткий краш-курс:

Зададим точки отдельными CGPoint’ами, чтобы не путаться:
CGPoint bezierStart = {20, 260};
CGPoint bezierEnd = {300, 260};
CGPoint bezierHelper1 = {80, 320};
CGPoint bezierHelper2 = {240, 320};
CGContextBeginPath(context);
CGContextMoveToPoint(context, bezierStart.x, bezierStart.y);
CGContextAddCurveToPoint(context,
bezierHelper1.x, bezierHelper1.y,
bezierHelper2.x, bezierHelper2.y,
bezierEnd.x, bezierEnd.y);
CGContextStrokePath(context);Нарисуем более сложный элемент, например, синусойду:
CGContextSetRGBStrokeColor(context, 255, 255, 255, 1);
int y;
for(int x=rect.origin.x; x < rect.size.width; x++)
{
y = ((rect.size.height/6) * sin(((x*4) % 360) * M_PI/180)) + 380;
if (x == 0) CGContextMoveToPoint(context, x, y);
else CGContextAddLineToPoint(context, x, y);
}
CGContextStrokePath(context);В 5й строке мы добавили 380 поинтов, чтобы сместить фукнцию по вертикали вниз.
В итоге экран нашего айфона после запуска приложения будет выглядеть так:

Работа с текстом
Наиболее удобный способ, для того, чтобы вывести текст на экран — использовать объект UILabel, который имеет множество методов преобразования текста. Однако, встречаются ситуации, когда нужно нарисовать текст в методе -drawRect, например, если мы хотим повернуть текст.
Рисование с помощью Core Graphics:
char *txt = "My CG text"; // создаем символьный массив, который выведем на экран
CGContextSelectFont(context, "Helvetica", 18.0, kCGEncodingMacRoman); // выбираем шрифт
CGContextSetTextDrawingMode(context, kCGTextFill); // выбираем вариант отображения текста: kCGTextFill (заливка) или kCGTextStroke (контур)
CGContextShowTextAtPoint(context, 20, 280, txt, strlen(txt)); // выводим текст на экранДля выбора шрифта, размера, стиля отображения, метрики можно использовать объект UIFont. А с помощью метода [UIFont familyNames] можно получить массив доступных шрифтов.
Если мы хотим использовать UILabel и при этом создать его программно, а не в Interface Builder, добавим следующий код в наш GraphicsViewController.m:
UILabel *scaleNumber = [[UILabel alloc] initWithFrame:CGRectMake(160, 140, 140, 21)]; //создаем объект, который будет являться нашим View
scaleNumber.textColor = [UIColor yellowColor]; //задаем цвет текста
scaleNumber.backgroundColor = [UIColor colorWithWhite:0 alpha:0]; //создаем прозрачный фон, чтобы наш текст не был в белом (или другого цвета) прямоугольнике
scaleNumber.text = @"Vitaly Ishkulov"; //какой-нибудь текст
scaleNumber.adjustsFontSizeToFitWidth = YES; //можно сделать, чтобы текст автоматически уменьшался, если не помещается, при этом увеличиваться больше заданного размера (или системного, если размер не задан) текст не будет
[myView addSubview:scaleNumber]; //добавляем наш текст в иерархию View
[scaleNumber release]; //освобождаем из памяти, так как мы передали управление/владение текстом нашему виду myViewЗаключение и немного слов о работе с памятью
Для освобождения из памяти views, не используют [myView release]; Вместо этого создадим метод releaseOutlets:
- (void)releaseOutlets {
self.myView = nil;
}И вызовем данный метод из viewDidUnload и dealloc:
- (void)viewDidUnload
{
[super viewDidUnload];
[self releaseOutlets];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}- (void)dealloc
{
[self releaseOutlets];
[super dealloc];
}Немного странное выражение self.myView = nil; на самом деле легко объясняется, если взглянуть на сеттер, который синтезирует для нас комманда synthesize myView:
- (void)setMyView:(MyCanvas *)anObject
{
if (anObject != myView) {
[myView release];
myView = [anObject retain];
}
}Если anObject = nil, то метод последняя строка в методе присваивает myView nil, а предыдущая освобождает из памяти текущий View — то, что мы и хотели сделать.
На этом краткое введение в CoreGraphics закончено, надеюсь оно окажется кому-нибудь полезным.
Ссылки для дальнейшего изучения:
View Programming Guide for iOS
Core Graphics Framework Reference
