iOS. Приемы программирования

Нахавандипур Вандад

Глава 17. Графика и анимация

 

 

17.0. Введение

Не сомневаюсь, что вам доводилось видеть программы для iPhone и iPad с очень красивой графикой. Кроме того, вы, наверное, встречали забавную анимацию в играх и других программах. При совместном использовании среды времени исполнения iOS и фреймворков программирования Cocoa Touch можно создавать самые разнообразные графические и анимационные эффекты с помощью сравнительно простого кода. Разумеется, качество этой графики и анимации частично зависит от эстетического вкуса программиста и его коллег-художников. Но в этой главе вы увидите, как много можно сделать в области графики и анимации, обладая весьма скромными навыками программирования.

Я не буду углубляться здесь в концептуальные базовые вопросы и расскажу о таких понятиях, как цветовые пространства, преобразования и графический контекст по ходу дела. Мы быстро рассмотрим некоторые фундаментальные вещи и почти сразу перейдем к коду.

В Cocoa Touch приложение состоит из окон (Window) и видов (View). Если у приложения есть пользовательский интерфейс, то в нем присутствует как минимум одно окно. Окно, в свою очередь, может содержать один или несколько видов. В Cocoa Touch окно является экземпляром класса UIWindow. Обычно в приложении открывается главное окно, и программист добавляет в это окно виды, представляющие разные компоненты пользовательского интерфейса. Видами являются, в частности, кнопки, подписи, изображения и специальные элементы управления, создаваемые самим программистом (Custom Controls). Отрисовка всех этих элементов пользовательского интерфейса и управление ими обеспечиваются во фреймворке UIKit.

Возможно, некоторые из этих вещей сложно понять сразу, но не волнуйтесь — по мере чтения главы вы постепенно разберетесь во всем. Особенно после знакомства с примерами, которые ждут нас впереди.

Apple предоставляет разработчикам мощные фреймворки, предназначенные для управления графикой и анимацией в операционных системах iOS и OS X. Далее перечислены некоторые из этих фреймворков и технологий.

• UIKit — это высокоуровневый фреймворк, позволяющий разработчикам создавать виды, окна, кнопки и другие компоненты пользовательского интерфейса. Кроме того, он включает ряд низкоуровневых API в состав высокоуровневого API, работать с которым довольно несложно.

• Quartz 2D — это основной движок, который работает «под капотом» операционной системы и обеспечивает отрисовку в iOS. Quartz применяется и во фреймворке UIKit.

• Core Graphics — фреймворк, поддерживающий графический контекст (подробнее об этом — в дальнейшем), загружающий изображения, отрисовывающий изображения и т. д.

• Core Animation — как следует из его названия, этот фреймворк обеспечивает применение анимации в iOS.

Приступая к рисованию на экране, исключительно важно усвоить одну концепцию: понять соотношение между точками и пикселами. С пикселами все ясно, но вот что такое точки? Это не зависящий от устройства аналог пикселов. Проще говоря, когда вы пишете приложение для iOS и от вас требуется указать какие-либо параметры, например высоту и ширину, то iOS считывает предоставленные вами значения как точки, а не как пикселы.

Например, если вы хотите заполнить весь экран на iPhone 5, ваш экранный элемент должен иметь ширину 320 и высоту 568. Однако мы знаем, что истинное разрешение экрана у iPhone 5 составляет 640 × 1136. В этом и заключается прелесть точек: оказывается, при работе с ними учитывается коэффициент масштабирования.

Здесь необходимо подробнее объяснить, что такое коэффициент масштабирования. Это обычное число с плавающей точкой, позволяющее iOS определить точное количество пикселов на экране. Для этого проверяется число логических точек, которые можно отобразить на этом экране. На iPhone 5 коэффициент масштабирования равен 2,0. Соответственно, iOS умножает 320 на 2, чтобы получить точное количество пикселов, которое устройство может отобразить по горизонтали, и умножает 568 на 2, чтобы получить количество пикселов по вертикали.

На экране устройства с iOS начало координат расположено в левом верхнем углу. Такие экраны также именуются ULO-экранами (от английского термина Upper Left Origin — «начало в левом верхнем углу»).

Это означает, что точка с координатами (0; 0) — крайняя точка в левом верхнем углу экрана. В таком случае положительные значения по оси X идут от нее направо, а положительные значения по оси Y — вниз. Иными словами, точка с координатой x = 20 находится на экране правее, чем точка с координатой x = 10. По оси Y точка с координатой y = 20 расположена ниже, чем точка y = 10.

В этой главе мы будем использовать объекты-виды типа UIView для отрисовки фигур, строк и любых других элементов, видимых на экране.

Предполагается, что вы работаете с новейшей версией Xcode on Apple. Если нет, откройте OS X, скачайте и установите новейшую версию Xcode.

Чтобы включить некоторые рассматриваемые здесь примеры кода в приложение, я сначала покажу, что нужно сделать для создания нового проекта в Xcode и подкласса от UIView, куда мы сможем поместить наш код.

1. Откройте Xcode.

2. В меню File (Файл) выполните команду New — Project (Новый — Проект).

3. Убедитесь, что в левой части экрана выбрана категория iOS. В этой категории укажите вариант Application (Приложение) (рис. 17.1)

4. В правой части экрана выберите Single View Application (Приложение с единственным видом) и нажмите Next (Далее) (рис. 17.1).

Рис. 17.1. Создание приложения с единственным видом для iOS в Xcode

5. В поле Product Name (Название продукта) (рис. 17.2) наберите имя вашего проекта.

Рис. 17.2. Установка параметров для нового проекта в Xcode

6. В поле Company Identifier (Идентификатор компании) введите префикс, идентифицирующий пакет, который предшествует выбранному вами названию продукта. Обычно идентификатор записывается в формате com.company.

7. Из списка Devices (Устройства) выберите Universal, а затем нажмите кнопку Next (Далее).

8. В следующем окне выберите, где вы хотите сохранить ваш проект, и нажмите Create (Создать).

Теперь проект Xcode открыт. В левой части окна Xcode раскройте группу Graphics (Графика) и просмотрите все файлы, которые Xcode создала для проекта. Теперь потребуется создать объект вида для контроллера вида. Для этого выполните следующие шаги.

1. Щелкните правой кнопкой мыши на корневом каталоге группы вашего проекта в Xcode и выберите New File (Новый файл).

2. Убедитесь, что в диалоговом окне New File (Новый файл) слева в качестве категории указан вариант iOS, и в качестве подкатегории выберите Cocoa Touch (рис. 17.3)

3. В правой части окна выберите класс Objective-C, а потом нажмите Next (Далее) (см. рис. 17.3).

Рис. 17.3. Создание нового класса Objective-C в Xcode

4. Убедитесь, что в следующем окне (рис. 17.4) в поле Subclass of (Подкласс) написано UIView, а потом задайте для вашего класса имя View. Далее сохраните файл на диске и нажмите Next (Далее).

Рис. 17.4. Создание подкласса от UIView

5. Теперь откройте ваш файл раскадровки для iPhone и выберите вид для контроллера вида. Раскройте раздел Utilities (Вспомогательная область) в конструкторе интерфейсов и измените имя класса того вида, в котором находится ваш контроллер вида, на View (рис. 17.5).

Рис. 17.5. Изменение имени класса контроллера вида в раскадровке

Поскольку мы создали универсальное приложение, те же манипуляции понадобится выполнить в файле раскадровки для iPad. Обычно два этих файла называются Main_iPhone.storyboard и Main_iPad.storyboard.

Итак, мы готовы приступить к написанию кода. А ведь сделать пришлось не так уж много — просто создать класс вида, относящегося к типу UIView, чтобы позже можно было изменять код этого класса. Потом мы воспользовались конструктором интерфейсов, чтобы задать в качестве класса вида контроллера вида тот самый объект вида, который мы создали ранее. Это означает, что вид контроллера вида теперь будет экземпляром созданного нами класса View.

Полагаю, вы уже просмотрели содержимое объекта-вида, сгенерированного Xcode. Один из самых важных методов этого объекта — drawRect:. Cocoa Touch автоматически вызывает этот метод всякий раз, когда приходит время отрисовывать вид. Данный метод используется для того, чтобы приказать объекту-виду отрисовать свое содержимое в графическом контексте. В свою очередь, Cocoa Touch автоматически готовит такой контекст для вида. Графический контекст можно сравнить с холстом (Canvas). Он предлагает огромное количество свойств, в частности цвет кисти (Pen Color), ее толщину (Pen Thickness) и т. д. Имея контекст, вы можете начать рисовать прямо внутри метода drawRect:, а Cocoa Touch гарантирует, что атрибуты и свойства контекста будут применены к вашим рисункам. Мы подробнее обсудим эти детали в дальнейшем, а пока перейдем к более интересным темам.

 

17.1. Перечисление и загрузка шрифтов

 

Постановка задачи

Требуется использовать шрифты, предустановленные на устройстве с iOS, чтобы отобразить на экране какой-либо текст.

 

Решение

Воспользуйтесь классом UIFont.

 

Обсуждение

Шрифты имеют фундаментальное значение для отображения текста в графическом пользовательском интерфейсе. Во фреймворке UIKit программисту предоставляются высокоуровневые API, обеспечивающие перечисление, загрузку и использование шрифтов. В Cocoa Touch шрифты заключены в классе UIFont. В каждом устройстве с iOS есть встроенные системные шрифты. Шрифты распределены по семействам (Family), а в каждом семействе есть гарнитуры (Faces). Например, Helvetica — это семейство шрифтов, а Helvetica Bold — одна из гарнитур этого семейства. Чтобы шрифт можно было загрузить, необходимо знать гарнитуру шрифта (фактически его название), а чтобы узнать гарнитуру, нужно знать семейство. Итак, для начала перечислим все семейства шрифтов, которые уже установлены на устройстве, воспользовавшись методом familyNames класса UIFont:

— (void) enumerateFonts{

for (NSString *familyName in [UIFont familyNames]){

NSLog(@"Font Family = %@", familyName);

}

}

Запустив эту программу в симуляторе, я получил примерно такие результаты:

Font Family = Thonburi

Font Family = Academy Engraved LE

Font Family = Snell Roundhand

Font Family = Avenir

Font Family = Marker Felt

Font Family = Geeza Pro

Font Family = Arial Rounded MT Bo Font Family = Trebuchet MS

Выстроив такой список семейств шрифтов, мы можем перечислить гарнитуры в каждом семействе. Будем пользоваться методом fontNamesForFamilyName: класса UIFont, а в ответ получим массив названий гарнитур из того семейства шрифтов, которое мы указали как параметр:

— (void) enumerateFonts{

for (NSString *familyName in [UIFont familyNames]){

NSLog(@"Font Family = %@", familyName);

for (NSString *fontName in

[UIFont fontNamesForFamilyName: familyName]){

NSLog(@"\t%@", fontName);

}

}

}

Запустив этот код в симуляторе iOS, получим:

Font Family = Thonburi Thonburi-Bold Thonburi

Font Family = Academy Eng AcademyEngravedLetPla

Font Family = Snell Round SnellRoundhand-Bold SnellRoundhand-Black SnellRoundhand

Итак, мы видим, что Thonburi — это семейство шрифтов, а Thonburi-Bold — одна из гарнитур этого семейства. Теперь, зная имя шрифта, мы можем загружать шрифты в объекты типа UIFont с помощью метода класса fontWithName: size:, относящегося к классу UIFont:

__unused UIFont *font = [UIFont fontWithName:@"Thonburi-Bold"

size:12.0f];

Если в результате работы метода класса fontWithName: size:, относящегося к классу UIFont, имеем nil, это означает, что найти шрифт с указанным именем не удалось. Убедитесь, что шрифт с заданным вами именем присутствует в системе. Для этого сначала перечислите все семейства шрифтов, а потом все названия гарнитур из каждого семейства.

Кроме того, можно воспользоваться методом экземпляра systemFontOfSize:, относящимся к классу UIFont (или его «жирным» аналогом, boldSystemFontOfSize:), для загрузки локальных системных шрифтов — где бы они ни находились — прямо на устройстве, где работает ваш код. Стандартный системный шрифт в iOS — Helvetica.

Загрузив шрифты, можете переходить к разделу 17.2. Там мы воспользуемся загруженными шрифтами для отрисовки текста в графическом контексте.

 

См. также

Раздел 17.2.

 

17.2. Отрисовка текста

 

Постановка задачи

Требуется рисовать текст на экране устройства с iOS.

 

Решение

Воспользуйтесь методом drawAtPoint: withFont: класса NSString.

 

Обсуждение

Для отрисовки текста можно воспользоваться очень удобными методами, входящими в состав класса NSString. Один из таких методов — drawAtPoint: withFont:. Но прежде чем продолжить работу, еще раз удостоверьтесь в том, что выполнили все инструкции из введения к этой главе. Теперь у вас должен быть объект-вид, являющийся подклассом от UIView. Он должен называться GraphicsViewControllerView. Откройте этот файл. Если закомментирован метод экземпляра drawRect:, относящийся к объекту-виду, то раскомментируйте его, чтобы включить этот метод в объект:

#import «View.h»

@implementation View

— (id)initWithFrame:(CGRect)frame{

self = [super initWithFrame: frame];

if (self) {

// Код инициализации

}

return self;

}

— (void)drawRect:(CGRect)rect{

}

@end

Именно в методе drawRect: будет происходить все рисование, как мы указывали ранее. Здесь мы можем приступать к загрузке шрифта, а потом нарисовать на экране простую текстовую строку, которая будет начинаться на уровне 40 по оси X и на уровне 180 по оси Y (рис. 17.6):

Рис. 17.6. Произвольная строка, нарисованная в графическом контексте вида

— (void)drawRect:(CGRect)rect{

UIFont *helveticaBold = [UIFont fontWithName:@"HelveticaNeue-Bold

size:40.0f];

NSString *myString = @"Some String";

[myString drawAtPoint: CGPointMake(40, 180)

withFont: helveticaBold];

}

В этом коде мы просто загружаем жирный шрифт Helvetica (кегль 40) и рисуем с его помощью текст Some String, который начинается в точке (40; 180).

 

17.3. Создание, установка и использование цветов

 

Постановка задачи

Требуется иметь возможность получать ссылки на цветовые объекты с последующим использованием этих объектов при рисовании различных форм в виде. К числу форм можно отнести текст, прямоугольники, треугольники и сегменты линий.

 

Решение

Воспользуйтесь классом UIColor.

 

Обсуждение

Во фреймворке UIKit программисту предоставляются высокоуровневые абстракции цветов, инкапсулированные в объекте UIColor. В этом классе имеются очень удобные методы класса, в частности redColor, blueColor, brownColor и yellowColor. Тем не менее, если вас интересует иной цвет, кроме тех, чьи параметры явно задаются как параметры этого метода класса UIColor, можно воспользоваться методом класса colorWithRed: green: blue: alpha:, относящимся к классу UIColor, и загрузить искомое цветовое значение. Возвращаемое значение этого метода относится к типу UIColor. Данный метод имеет следующие параметры:

• red — доля красного в конкретном оттенке. Это значение может находиться в диапазоне от 0.0f до 1.0f, где 0.0f полностью исключает красный компонент, а 1.0f дает максимально насыщенный темно-красный цвет;

• green — доля зеленого, смешиваемая с красным в цвете. Это значение также может находиться в диапазоне от 0.0f до 1.0f;

• blue — доля голубого, смешиваемая с красным и зеленым в цвете. Это значение также может находиться в диапазоне от 0.0f до 1.0f;

• alpha — матовость (непрозрачность) цвета. Это значение может находиться в диапазоне от 0.0f до 1.0f, где 1.0f делает цвет полностью матовым, а 0.0f — полностью прозрачным (иными словами, невидимым).

Имея объект типа UIColor, вы можете воспользоваться его методом экземпляра set, чтобы в текущем графическом контексте этот цвет использовался для рисования.

Можно применять метод класса colorWithRed: green: blue: alpha:, относящийся к классу UIColor, для загрузки основных цветов, например красного. Для этого параметру red просто сообщается значение 1.0f, а параметрам green и blue — значение 0.0f. Значение параметра alpha выбираете сами.

Взглянув на рис. 17.1, мы видим, что заданный по умолчанию цвет фона для созданного нами объекта-вида — серый, довольно некрасивый. Исправим это. Просто найдем метод экземпляра viewDidLoad контроллера вида GraphicsViewController и изменим фоновый цвет вида на белый, как показано здесь:

— (void)viewDidLoad{

[super viewDidLoad];

self.view.backgroundColor = [UIColor whiteColor];

}

Для отрисовки текста в текущем графическом контексте будем пользоваться методами экземпляра класса NSString, подробнее об этом — чуть позже.

Теперь загрузим в объект типа UIColor пурпурный цвет, а потом нарисуем в графическом контексте вида текст I Learn Really Fast, использовав для этого жирный шрифт Helvetica кегля 30 (о загрузке шрифтов рассказано в разделе 17.1):

— (void)drawRect:(CGRect)rect{

/* Загружаем цвет. */

UIColor *magentaColor =[UIColor colorWithRed:0.5f

green:0.0f

blue:0.5f

alpha:1.0f];

/* Задаем цвет в графическом контексте. */

[magentaColor set];

/* Загружаем шрифт. */

UIFont *helveticaBold = [UIFont fontWithName:@"HelveticaNeue-Bold"

size:30.0f];

/* Строка, которую требуется отрисовать. */

NSString *myString = @"I Learn Really Fast";

/* Рисуем строку выбранным шрифтом.

Цвет мы уже установили. */

[myString drawAtPoint: CGPointMake(25, 190)

withAttributes:@{

NSFontAttributeName: helveticaBold

}];}

Результат показан на рис. 17.7.

Рис. 17.7. Строка, отрисованная выбранным цветом в графическом контексте

Кроме того, мы можем воспользоваться методом экземпляра drawInRect: withFont:, относящимся к классу NSString, чтобы нарисовать текст внутри прямоугольной области. Текст будет растянут, чтобы он полностью занял отведенное пространство. Фреймворк UIKit даже позволяет переносить часть текста на следующую строку, если он не будет умещаться в отведенном прямоугольнике по горизонтали. Границы прямоугольной области инкапсулированы в структурах CGRect. Для создания границ прямоугольника можно использовать функцию CGRectMake:

— (void)drawRect:(CGRect)rect{

/* Загружаем цвет. */

UIColor *magentaColor = [UIColor colorWithRed:0.5f

green:0.0f

blue:0.5f

alpha:1.0f];

/* Задаем цвет в графическом контексте. */

[magentaColor set];

/* Загружаем шрифт. */

UIFont *helveticaBold = [UIFont boldSystemFontOfSize:30];

/* Строка, которую требуется отрисовать. */

NSString *myString = @"I Learn Really Fast";

/* Рисуем строку выбранным шрифтом.

Цвет мы уже установили. */

[myString drawInRect: CGRectMake(100, /* x */

120, /* y */

100, /* ширина */

200) /* высота */

options: NSStringDrawingUsesLineFragmentOrigin

attributes:@{

NSFontAttributeName: helveticaBold

}

context: nil];

}

Функция CGRectMake принимает четыре параметра:

• x — положение начала координат прямоугольника по оси X относительно графического контекста. В iOS это значение соответствует количеству точек, отсчитанному вправо от левой стороны прямоугольника;

• y — положение начала координат прямоугольника по оси Y относительно графического контекста. В iOS это значение соответствует количеству точек, отсчитанному вниз от верхней стороны прямоугольника;

• width — ширина прямоугольника в точках;

• height — высота прямоугольника в точках.

Результат выполнения кода показан на рис. 17.8.

Рис. 17.8. Отрисовка строки в прямоугольном пространстве

UIColor — это, в сущности, предоставляемая в UIKit оболочка для класса CGColor из фреймворка Core Graphics. Переходя к довольно низкоуровневому программированию — то есть на уровне Core Graphics, — мы неожиданно обретаем значительно более полный контроль над использованием цветовых объектов и даже можем определить цветовые компоненты, из которых состоит конкретный оттенок. Допустим, в каком-то другом коде вы получили объект типа UIColor и хотите определить, каковы содержащиеся в нем доли компонентов red, green, blue и alpha. Чтобы получить компоненты, из которых состоит объект UIColor, выполните следующие шаги.

1. Используйте метод экземпляра CGColor, относящийся к классу UIColor. В результате вы получите цветовой объект типа CGColorRef, представляющий собой ссылку на цветовой объект (Color Reference) — объект из фреймворка Core Graphics.

2. Применяйте функцию CGColorGetComponents для получения компонентов, составляющих цветовой объект.

3. При необходимости пользуйтесь функцией CGColorGetNumberOfComponents, чтобы определить количество компонентов, примененных для создания данного оттенка (красный + зеленый и т. д.).

Вот пример:

— (void) drawRect:(CGRect)rect{

/* Загрузка цвета */

UIColor *steelBlueColor = [UIColor colorWithRed:0.3f

green:0.4f

blue:0.6f

alpha:1.0f];

CGColorRef colorRef = [steelBlueColor CGColor];

const CGFloat *components = CGColorGetComponents(colorRef);

NSUInteger componentsCount = CGColorGetNumberOfComponents(colorRef);

NSUInteger counter = 0;

for (counter = 0;

counter < componentsCount;

counter++){

NSLog(@"Component %lu = %.02f",

(unsigned long)counter + 1,

components[counter]);

}

}

После запуска кода получим в окне консоли следующий вывод:

Component 1 = 0.30

Component 2 = 0.40

Component 3 = 0.60

Component 4 = 1.00

 

См. также

Раздел 17.1.

 

17.4. Отрисовка изображений

 

Постановка задачи

Требуется возможность отрисовывать изображения на экране устройства с iOS.

 

Решение

Используйте класс UIImage для загрузки изображения и относящийся к изображению метод drawInRect: для отрисовки картинки в графическом контексте.

 

Обсуждение

Фреймворк UIKit очень упрощает задачи, связанные с рисованием. Все, что от вас требуется, — загрузить ваши изображения в экземпляры типа UIImage. В классе UIImage содержатся разнообразные методы класса и экземпляра, предназначенные для загрузки изображений. Вот некоторые из наиболее важных:

• imageNamed: (метод класса) — загружает изображение (если его удалось правильно загрузить, то и кэширует). Параметр этого метода — имя изображения в пакете, например Tree Texture.png;

• imageWithData: (метод класса) — загружает изображение из данных, инкапсулированных в экземпляре объекта NSData, который был передан данному методу в качестве параметра;

• initWithContentsOfFile: (метод экземпляра (для инициализации)) — использует указанный параметр как путь к изображению, которое должно быть загружено. Применяется для инициализации объекта изображения;

В данном случае подразумевается полный путь, указывающий на изображение в пакете приложения.

• initWithData: (метод экземпляра (для инициализации)) — использует полученный параметр типа NSData для инициализации изображения. Эти данные должны относиться к валидному изображению.

Чтобы добавить изображение в ваш проект в Xcode, выполните следующие шаги.

1. Найдите, где именно расположено изображение на вашем компьютере.

2. Перетащите это изображение в категорию изображений, которая обычно называется images.xcassets. Всю остальную работу Xcode выполнит за вас.

Для того чтобы получить ярлык Xcode, выполните следующие шаги:

1) найдите приложение Xcode в обозревателе;

2) находясь в обозревателе (Finder), нажмите на Xcode сочетание Command+I, чтобы получить информацию о приложении;

3) щелкните на ярлыке в верхнем левом углу окна справки Xcode;

4) нажмите Command+C, чтобы скопировать ярлык;

5) откройте приложение для предварительного просмотра (Preview);

6) нажмите сочетание клавиш Command+V, чтобы вставить ярлык Xcode в новое изображение;

7) полученный файл ICNS с пятью отдельными страницами сохраните в формате PDF, а потом удалите все, кроме ярлыка с наиболее высоким разрешением (страница 1 файла ICNS).

В этом разделе книги мы нарисуем изображение в графическом контексте, чтобы продемонстрировать общий принцип отрисовки изображений. Я уже нашел нужный файл и перетащил это изображение в мою программу для iOS. Теперь в пакете приложения есть изображение под названием Xcode.png (рис. 17.9).

Рис. 17.9. Ярлык Xcode, находящийся в приложении Xcode

Вот код для отрисовки изображения:

— (void)drawRect:(CGRect)rect{

UIImage *image = [UIImage imageNamed:@"Xcode.png"];

if (image!= nil){

NSLog(@"Successfully loaded the image.");

} else {

NSLog(@"Failed to load the image.");

}

}

Если в пакете вашего приложения есть изображение Xcode.png, то после запуска этого кода на консоли появится надпись Successfully loaded the image (Изображение успешно загружено). Если изображения нет — будет написано Failed to load the image (Не удалось загрузить изображение). В оставшейся части данного раздела предполагается, что у вас в пакете приложения есть нужное изображение. Можете смело помещать в пакет приложения и другие картинки, а потом ставить ссылки именно на них, а не на Xcode.png, которым я буду пользоваться в примерах кода.

Два самых простых способа отрисовки изображения типа UIImage в графическом контексте таковы:

• воспользоваться методом экземпляра drawAtPoint:, относящимся к классу UIImage. Таким образом в указанной точке отрисовывается изображение оригинального размера. Для создания этой точки используется функция CGPointMake;

• воспользоваться методом экземпляра drawInRect:, относящимся к классу UIImage. Изображение отрисовывается в заданной прямоугольной области. Для создания этой прямоугольной области используется функция CGRectMake:

— (void)drawRect:(CGRect)rect{

/* Предполагается, что нужное изображение есть в пакете вашего приложения

и его можно загрузить. */

UIImage *xcodeIcon = [UIImage imageNamed:@"Xcode.png"];

[xcodeIcon drawAtPoint: CGPointMake(0.0f,

20.0f)];

[xcodeIcon drawInRect: CGRectMake(50.0f,

10.0f,

40.0f,

35.0f)];

}

При показанном ранее вызове drawAtPoint: будет отрисовано изображение оригинальных размеров с центром в точке (0; 20). При вызове drawInRect: будет отрисовано изображение с центром в точке (50; 10) размером 40 × 35 точек. Результаты показаны на рис. 17.10.

Рис. 17.10. Отрисовку изображения в графическом контексте можно выполнить с помощью двух различных методов

Соотношение сторон (Aspect Ratio) — это отношение между шириной и высотой изображения на экране компьютера. Предположим, у нас есть изображение размером 100 × 100 пикселов. Если нарисовать это изображение с началом координат в точке (0; 0) и задать для него размеры (100; 200), то вы сразу же заметите на экране, что картинка вытянулась по высоте (было 100 пикселов, стало 200). Метод экземпляра drawInRect:, относящийся к классу UIImage, оставляет на ваш выбор решение о том, как именно вы будете отрисовывать изображения. Иными словами, именно вы будете указывать значения x, y, ширины и высоты вашего изображения, определяя, как именно оно будет выглядеть на экране.

 

См. также

Раздел 13.6.

 

17.5. Создание адаптивных изображений

 

Постановка задачи

Требуется экономить память и дисковое пространство, создавая для компонентов пользовательского интерфейса адаптивные изображения. Возможно, потребуется создать несколько вариантов одного и того же графического элемента, имеющих разные размеры. Например, это может быть несколько подобных кнопок, на каждой из которых используется одно и то же фоновое изображение.

Адаптивные изображения — это просто картинки в формате JPEG или PNG, которые можно загружать в экземпляры UIImage.

 

Решение

Создайте адаптивное изображение, воспользовавшись методом экземпляра resizableImageWithCapInsets:, относящимся к классу UIImage.

 

Обсуждение

На первый взгляд термин «адаптивное изображение» может показаться странным, но все становится на свои места, если учесть, что ваше приложение будет отображаться в довольно разных условиях, в зависимости от ситуации. Например, у вас может быть приложение для iOS, в котором все кнопки имеют фоновые изображения. Чем крупнее текст на кнопке, тем шире должна быть сама кнопка. Итак, есть два способа, которыми можно создать подходящие фоновые изображения для кнопок.

• Создать по одному изображению для каждого из размеров кнопки. В результате пакет приложения увеличится, возрастет потребление памяти, а вам придется выполнять больше работы. Кроме того, при изменении текста вновь потребуется подгонять изображение под размеры кнопки.

• Создать всего одно адаптивное изображение и использовать его во всем приложении для всех кнопок.

Несомненно, второй вариант кажется гораздо более привлекательным. Итак, что же представляют собой адаптивные изображения? Это просто изображения, состоящие из двух виртуальных областей:

• области, размер которой не меняется;

• области, размер которой свободно меняется и принимает нужные значения.

Как показано на рис. 17.11, мы создали изображение для кнопки. Внимательно рассмотрев это изображение, вы замечаете, что оно состоит из градиента. Область, которую я отрисовал вокруг прямоугольника, не может быть вырезана из приложения. Возникает вопрос: а почему? Смотрим еще внимательнее! Если я вырежу эту область и задам для нее значения высоты и ширины всего по 1 пикселу (как сейчас), то в приложении я смогу объединить сколько угодно таких однопиксельных полосок и сделать точно такую же область, какая выделена на этом рисунке (рис. 17.12).

Рис. 17.11. Изображение, в котором есть избыточная область, наиболее целесообразно сделать адаптивным

Рис. 17.12. Все отдельные срезы центральной секции изображения совершенно одинаковы

Итак, как нам уменьшить изображение, но по-прежнему иметь возможность создать из него кнопку? Ответ прост. В данном случае, когда изображение является совершенно одинаковым по всей длине, мы просто вырежем по центру изображения очень узкий фрагмент. Его ширина составит 1 пиксел, а высота не изменится. На рис. 17.13 показано, как изображение будет выглядеть после этой операции.

Рис. 17.13. Область изображения, размер которой можно изменять, теперь равна по ширине одной точке

И вот начинается самое интересное. Как мы можем сообщить iOS SDK, какие части изображения оставить нетронутыми, а какую часть растягивать? Оказывается, в iOS SDK уже предусмотрена такая возможность. Сначала загрузите ваше изображение в память с помощью API UIImage, изученных в этой главе. Создав экземпляр UIImage с таким изображением, которое гарантированно можно растягивать, преобразуйте этот экземпляр в адаптивное изображение. Это делается с помощью метода экземпляра resizableImageWithCapInsets:, относящегося как раз к рассматриваемому экземпляру. Параметр, принимаемый этим методом, относится к типу UIEdgeInsets, который, в свою очередь, определяется вот так:

typedef struct UIEdgeInsets {

CGFloat top, left, bottom, right;

} UIEdgeInsets;

Краевые отступы нужны для того, чтобы создавать так называемые девятичастные изображения (nine-part images в терминологии Apple). Имеются в виду изображения, включающие в себя следующие девять компонентов:

• верхний левый угол;

• верхний край;

• верхний правый угол;

• правый край;

• нижний правый угол;

• нижний край;

• нижний левый угол;

• левый край;

• центр.

На рис. 17.14 все проиллюстрировано максимально наглядно.

Рис. 17.14. Девятичастное изображение

Изображения сохраняются в девятичастном виде для того, чтобы программист мог адаптировать его размер по горизонтали и вертикали практически как угодно. Когда программисту требуется адаптировать изображение, некоторые из этих компонентов изменяют размер, а другие остаются нетронутыми. Никогда не меняются величины углов. Адаптация размеров других компонентов происходит следующим образом:

• верхний край — размер этого компонента изображения может изменяться по ширине, но не по высоте;

 правый край — размер этого компонента изображения может изменяться по высоте, но не по ширине;

 нижний край — размер этого компонента изображения, так же как и верхнего края, может изменяться по ширине, но не по высоте;

 левый край — размер этого компонента изображения, так же как и правого края, может изменяться по высоте, но не по ширине;

 центр — размеры центра могут меняться как по высоте, так и по ширине.

Значения отступов по верхнему, левому, нижнему и правому краям задают размеры той области, которую вы не хотите растягивать. Например, вы задали для левого края значение 10, для верхнего края — значение 11, для правого края — значение 14 и для нижнего — 5. Так вы приказываете iOS провести через изображение вертикальную линию в 10 точках от левого края, горизонтальную линию в 11 точках от верхнего края, еще одну вертикальную линию в 14 точках от правого края и, наконец, горизонтальную линию в 5 точках от нижнего края. Прямоугольная область, заключенная между этими линиями, может изменять размер (является адаптивной), а область вне этого контура — не может. Если все кажется немного запутанным, представьте себе прямоугольник (ваше изображение), а затем начертите внутри него другой прямоугольник. Размеры внутреннего прямоугольника могут меняться, размеры внешнего — нет. Предлагаю вновь рассмотреть ситуацию на картинке (рис. 17.15).

Рис. 17.15. Размеры изменяемой части изображения определяются по величине отступов от краев

На самом деле отступы слева и справа на рис. 17.15 одинаковы. Отступы от нижнего и верхнего краев также одинаковы. Я указал для них разные значения лишь для того, чтобы процесс создания отступов был более понятным и логичным. Если бы все значения отступов были одинаковы, то позже мы могли бы запутаться: а о каком из отступов сейчас идет речь?

Для такого изображения, как показано на рис. 17.15, краевые отступы создаются следующим образом:

UIEdgeInsets edgeInsets;

edgeInsets.left = 20.0f;

edgeInsets.top = 10.0f;

edgeInsets.right = 24.0f;

edgeInsets.bottom = 14.0f;

А теперь возвращаемся к учебному коду. Здесь мы попытаемся использовать адаптивное изображение, показанное на рис. 17.13, в реальном приложении. Мы создадим кнопку и поместим ее в центре единственного вида, находящегося в нашем контроллере вида. На кнопке будет написано Stretched Image on Button (Адаптивное изображение на кнопке). Кнопка будет иметь 200 точек в ширину и 44 точки в высоту. Вот наш код:

#import «ViewController.h»

@interface ViewController ()

@property (nonatomic, strong) UIButton *button;

@end

@implementation ViewController

— (void)viewDidLoad{

[super viewDidLoad];

/* Инстанцируем кнопку */

self.button = [UIButton buttonWithType: UIButtonTypeCustom];

[self.button setFrame: CGRectMake(0.0f, 0.0f, 200.0f, 44.0f)];

/* Задаем надпись для кнопки */

[self.button setTitle:@"Stretched Image on Button"

forState: UIControlStateNormal];

/* Корректируем шрифт для текста */

self.button.titleLabel.font = [UIFont systemFontOfSize:15.0f];

/* Создаем адаптивное изображение */

UIImage *image = [UIImage imageNamed:@"Button"];

UIEdgeInsets edgeInsets;

edgeInsets.left = 14.0f;

edgeInsets.top = 0.0f;

edgeInsets.right = 14.0f;

edgeInsets.bottom = 0.0f;

image = [image resizableImageWithCapInsets: edgeInsets];

/* Задаем фоновое изображение для кнопки */

[self.button setBackgroundImage: image forState: UIControlStateNormal];

[self.view addSubview: self.button];

self.button.center = self.view.center;

}

@end

Теперь, запустив приложение, вы увидите примерно такую картинку, как на рис. 17.16.

Рис. 17.16. На экране находится кнопка с адаптивным фоновым изображением

 

См. также

Раздел 17.4.

 

17.6. Отрисовка линий

 

Постановка задачи

Требуется просто рисовать линии в графическом контексте.

 

Решение

Получите описатель для вашего графического контекста, а потом пользуйтесь функциями CGContextMoveToPoint и CGContextAddLineToPoint для отрисовки линии.

 

Обсуждение

Когда мы говорим о рисовании фигур в iOS или OS X, мы подразумеваем пути (paths). Что такое путь в данном случае? Путь возникает между одной или несколькими сериями точек, расположенными на экране. Между путями и линиями существует значительная разница. Путь может содержать несколько линий, но линия не может содержать несколько путей. Считайте, что путь — это просто серия точек.

Линии нужно рисовать, пользуясь путями. Укажите начальную и конечную точки пути, а потом прикажите Core Graphics заполнить этот путь за вас. Core Graphics считает, что вы создали линию вдоль этого пути, и нарисует его указанным вами цветом (см. раздел 17.3).

Более подробно мы рассмотрим пути в дальнейшем (в разделе 17.7), а пока сосредоточимся на том, как создавать с помощью путей прямые линии. Для этого нужно выполнить следующие шаги.

1. Выбрать цвет в вашем графическом контексте (см. раздел 17.3).

2. Получить описатель графического контекста — это делается с помощью функции UIGraphicsGetCurrentContext.

3. Задать начальную точку для линии, воспользовавшись процедурой CGContextMoveToPoint.

4. Переместить перо в графическом контексте, воспользовавшись процедурой CGContextAddLineToPoint, и указать конечную точку линии.

5. Создать намеченный путь с помощью процедуры CGContextStrokePath. Эта процедура отрисует путь в графическом контексте, использовав указанный вами цвет.

Кроме того, можно воспользоваться процедурой CGContextSetLineWidth, которая задает толщину линий, отрисовываемых в заданном графическом контексте. Первый параметр этой процедуры — графический контекст, на котором вы рисуете, а второй параметр — толщина линии, выражаемая числом с плавающей точкой (CGFloat).

В iOS толщина линии измеряется в логических точках.

Вот пример:

— (void)drawRect:(CGRect)rect{

/* Задаем цвет, которым собираемся отрисовывать линию. */

[[UIColor brownColor] set];

/* Получаем актуальный графический контекст. */

CGContextRef currentContext = UIGraphicsGetCurrentContext();

/* Задаем толщину линии. */

CGContextSetLineWidth(currentContext,

5.0f);

/* В этой точке будет начинаться линия. */

CGContextMoveToPoint(currentContext,

50.0f,

10.0f);

/* В этой точке линия будет заканчиваться. */

CGContextAddLineToPoint(currentContext,

100.0f,

200.0f);

/* Для отрисовки линии используем цвет, заданный в контексте в настоящий

момент. */

CGContextStrokePath(currentContext);

}

Запустив этот код в симуляторе iOS, вы получите примерно такие результаты, как на рис. 17.17.

Рис. 17.17. Рисование линии в текущем графическом контексте

Приведу еще один пример. Как было упомянуто ранее, процедура CGContextAddLineToPoint указывает конечную точку данной линии. А что делать, если мы уже провели линию из точки (20; 20) в точку (100; 100), а теперь хотим провести линию из точки (100; 100) в точку (300; 100)? Может возникнуть версия, что, нарисовав первую линию, мы должны переместить перо в точку (100; 100) с помощью процедуры CGContextMoveToPoint, а потом провести линию в точку (300; 100), используя процедуру CGContextAddLineToPoint. Да, это сработает, но задачу можно решить более эффективным способом. После того как вы вызовете процедуру CGContextAddLineToPoint для указания конечной точки отрисовываемой в данный момент линии, положение вашего пера изменится на значение, которое будет передано этому методу. Иными словами, после того, как вы выпустите метод, воспользовавшись пером, метод поставит перо в конечной точке того объекта, который был отрисован (объект может быть любым). Итак, чтобы нарисовать еще одну линию из актуальной конечной точки в новую точку, нужно просто еще раз вызвать процедуру CGContextAddLineToPoint, сообщив ей новую конечную точку. Вот пример:

— (void)drawRect:(CGRect)rect{

/* Задаем цвет, которым мы собираемся отрисовывать линию. */

[[UIColor brownColor] set];

/* Получаем актуальный графический контекст. */

CGContextRef currentContext = UIGraphicsGetCurrentContext();

/* Задаем толщину линий. */

CGContextSetLineWidth(currentContext,

5.0f);

/* В этой точке будет начинаться линия. */

CGContextMoveToPoint(currentContext,

20.0f,

20.0f);

/* В этой точке линия будет заканчиваться. */

CGContextAddLineToPoint(currentContext,

100.0f,

100.0f);

/* Продолжаем линию до новой точки. */

CGContextAddLineToPoint(currentContext,

300.0f,

100.0f);

/* Для отрисовки линии используем цвет, заданный в контексте в настоящий

момент. */

CGContextStrokePath(currentContext);

}

Результат показан на рис. 17.18. Как видите, удалось успешно отрисовать обе линии, не перемещая перо для отрисовки второй линии.

Точка соединения двух линий называется перемычкой (Join). Работая с Core Graphics, можно указывать тип перемычки, которую вы хотите сделать между линиями, сочлененными друг с другом. Для выбора такого типа используется процедура CGContextSetLineJoin. Она принимает два параметра: во-первых, графический контекст, в котором вы задаете перемычку такого типа, а во-вторых, сам тип перемычки, CGLineJoin. CGLineJoin — это перечень следующих значений:

Рис. 17.18. Одновременно отрисовываем две линии

• kCGLineJoinMiter — на месте перемычки образуется острый угол. Этот тип задается по умолчанию;

• kCGLineJoinBevel — угол на месте перемычки линий будет немного спрямлен, как будто обтесан;

• kCGLineJoinRound — как понятно из названия, такая перемычка — скругленная.

Рассмотрим пример. Допустим, мы хотим написать программу, способную отрисовывать в графическом контексте «скатные крыши», каждая из которых иллюстрировала бы определенный тип перемычки между линиями, а также выводить под «крышей» текст с названием используемой перемычки. В результате получится рисунок, напоминающий рис. 17.19.

Рис. 17.19. Три типа перемычек между линиями, существующие в Core Graphics

Для решения этой задачи я написал метод drawRooftopAtTopPointof: textToDisplay: lineJoin:, принимающий три параметра:

• точку, в которой должна располагаться верхушка «крыши»;

• текст, отображаемый под «крышей»;

• используемый тип перемычки.

Код будет таким:

— (void) drawRooftopAtTopPointof:(CGPoint)paramTopPoint

textToDisplay:(NSString *)paramText

lineJoin:(CGLineJoin)paramLineJoin{

/* Задаем цвет, которым собираемся отрисовывать линию. */

[[UIColor brownColor] set];

/* Получаем актуальный графический контекст. */

CGContextRef currentContext = UIGraphicsGetCurrentContext();

/* Задаем перемычку между линиями. */

CGContextSetLineJoin(currentContext,

paramLineJoin);

/* Задаем толщину линий. */

CGContextSetLineWidth(currentContext,

20.0f);

/* В этой точке будет начинаться линия. */

CGContextMoveToPoint(currentContext,

paramTopPoint.x — 140,

paramTopPoint.y + 100);

/* В этой точке линия будет заканчиваться. */

CGContextAddLineToPoint(currentContext,

paramTopPoint.x,

paramTopPoint.y);

/* Продолжаем линию до новой точки, чтобы получилась фигура,

напоминающая крышу. */

CGContextAddLineToPoint(currentContext,

paramTopPoint.x + 140,

paramTopPoint.y + 100);

/* Для отрисовки линии используем цвет, заданный в контексте в настоящий

момент. */

CGContextStrokePath(currentContext);

/* Рисуем под крышей текст, при этом используется черный цвет. */

[[UIColor blackColor] set];

/* Теперь рисуем текст. */

CGPoint drawingPoint = CGPointMake(paramTopPoint.x — 40.0f,

paramTopPoint.y + 60.0f);

[paramText drawAtPoint: drawingPoint

withFont: [UIFont boldSystemFontOfSize:30.0f]];

}

А теперь вызовем наш метод в методе экземпляра drawRect: объекта-вида, где находится графический контекст:

— (void)drawRect:(CGRect)rect{

[self drawRooftopAtTopPointof: CGPointMake(160.0f, 40.0f)

textToDisplay:@"Miter"

lineJoin: kCGLineJoinMiter];

[self drawRooftopAtTopPointof: CGPointMake(160.0f, 180.0f)

textToDisplay:@"Bevel"

lineJoin: kCGLineJoinBevel];

[self drawRooftopAtTopPointof: CGPointMake(160.0f, 320.0f)

textToDisplay:@"Round"

lineJoin: kCGLineJoinRound];

}

 

См. также

Разделы 17.3 и 17.7.

 

17.7. Создание путей

 

Постановка задачи

Необходимо иметь возможность нарисовать в графическом контексте любой желаемый контур.

 

Решение

Создавайте и отрисовывайте пути.

 

Обсуждение

Если расположить рядом серию точек, они могут образовать фигуру. Серия фигур, составленных вместе, образует путь. Управлять путями в Core Graphics очень удобно. В разделе 17.6 мы опосредованно работали с путями, пользуясь функциями CGContext. Но в Core Graphics есть и такие функции, которые работают с путями напрямую. Вскоре мы с ними познакомимся.

Пути относятся к тому графическому контексту, в котором они нарисованы. У путей нет границ либо конкретных контуров, в отличие от фигур, рисуемых по ним. Однако у путей есть ограничивающие рамки. Не забывайте, что граница и ограничивающая рамка — не тождественные понятия. Границы — это пределы, в которых вы можете рисовать на холсте, а ограничивающая рамка пути — это наименьший прямоугольник, в котором содержатся все фигуры, точки и другие объекты, отрисованные по данному конкретному пути. Пути можно сравнить с марками, а графический контекст — с конвертом для письма. Всякий раз, когда вы решите послать открытку другу, конверты для нее будут одинаковыми, но может различаться количество марок, которые вы наклеите на конверт (в нашем случае могут различаться пути).

Когда вы закончите рисование на определенном пути, можно отрисовать этот путь в графическом контексте. Разработчики, которым доводилось заниматься программированием игр, знакомы с понятием буфера. Буфер отрисовывает закрепленные за ним сцены и в нужный момент сбрасывает это содержимое на экран. Пути — это, в сущности, буферы. Они напоминают невидимые границы, рисуемые на холсте.

Приступая к непосредственной работе с путями, начнем с создания самого пути. Метод, создающий путь, возвращает описатель, которым вы будете пользоваться всякий раз, когда решите нарисовать что-либо на этом пути. Описатель передается Core Graphics для справки. Создав путь, вы сможете добавить к нему различные линии, фигуры и точки и только потом отрисовать его. Путь можно либо заполнить определенным цветом заливки, либо отрисовать штрихами в графическом контексте. Вот методы, с которыми придется работать:

• CGPathCreateMutable (функция) — создает новый изменяемый путь типа CGMutablePathRef и возвращает его описатель. Как только мы закончим работу с этим путем, от него необходимо избавиться — об этом мы вскоре поговорим;

• CGPathMoveToPoint (процедура) — перемещает на путь актуальное положение пера. Перо оказывается в точке, заданной в параметре типа CGPoint;

• CGPathAddLineToPoint (процедура) — отрисовывает сегмент линии от актуальной позиции пера до указанной позиции (которая опять же указывается как значение типа CGPoint);

• CGContextAddPath (процедура) — добавляет заданный путь (на который указывает переданный здесь описатель) в графический контекст. Этот путь готов для рисования;

• CGContextDrawPath (процедура) — отрисовывает заданный путь в графическом контексте;

• CGPathRelease (процедура) — высвобождает память, выделенную для описателя пути;

• CGPathAddRect (процедура) — добавляет к пути прямоугольник. Границы прямоугольника указаны в структуре CGRect.

Существуют три важных рисовальных метода, выполнение которых можно задать процедуре CGContextDrawPath:

• kCGPathStroke — рисует линию (штрих), отмечающий границу или кромку пути. Штрих рисуется актуальным цветом, выбранным в данный момент;

• kCGPathFill — заполняет цветом заливки область, вокруг которой описан путь. Заливка выполняется в актуальном цвете, выбранном в данный момент;

• kCGPathFillStroke — комбинирует штрих и заливку. Для заполнения пути использует актуальный цвет заливки, а выбранный цвет штриха применяет для отрисовки пути. В следующем разделе будет рассмотрен пример использования этого метода.

Рассмотрим пример. Допустим, нам необходимо нарисовать голубую линию, идущую по экрану из верхнего левого угла в нижний правый, и другую линию, идущую из верхнего правого угла в левый нижний. Так мы нарисуем на экране большой крест, напоминающий букву «X».

В этом примере я удалил из приложения в симуляторе iOS статусную панель. Если вы не хотите с этим возиться, то можете сразу переходить к коду, приведенному далее. При наличии статусной панели результат выполнения кода будет лишь незначительно отличаться от того, что показано на моем скриншоте. Чтобы скрыть статусную панель, найдите в вашем проекте Xcode файл Info.plist и добавьте в этот файл ключ UIStatusBarHidden со значением YES (рис. 17.20). В таком случае сразу после открытия приложения статусная панель будет скрыта.

Рис. 17.20. Операция с файлом Info.plist, позволяющая скрыть статусную панель приложения iOS

— (void)drawRect:(CGRect)rect{

/* Создаем путь. */

CGMutablePathRef path = CGPathCreateMutable();

/* Каковы размеры экрана? Мы хотим, чтобы X растянулся на весь экран. */

CGRect screenBounds = [[UIScreen mainScreen] bounds];

/* Начинаем с верхнего левого угла. */

CGPathMoveToPoint(path,

NULL,

screenBounds.origin.x,

screenBounds.origin.y);

/* Проводим линию из верхнего левого в нижний правый угол экрана. */

CGPathAddLineToPoint(path,

NULL,

screenBounds.size.width,

screenBounds.size.height);

/* Начинаем другую линию из верхнего правого угла. */

CGPathMoveToPoint(path,

NULL,

screenBounds.size.width,

screenBounds.origin.y);

/* Проводим линию из верхнего правого в нижний левый угол. */

CGPathAddLineToPoint(path,

NULL,

screenBounds.origin.x,

screenBounds.size.height);

/* Получаем контекст, в котором должен быть отрисован путь. */

CGContextRef currentContext = UIGraphicsGetCurrentContext();

/* Добавляем путь к контексту, чтобы позже его можно было отрисовать. */

CGContextAddPath(currentContext,

path);

/* Задаем для штриха голубой цвет. */

[[UIColor blueColor] setStroke];

/* Отрисовываем путь этим цветом. */

CGContextDrawPath(currentContext,

kCGPathStroke);

/* Наконец, высвобождаем объект пути. */

CGPathRelease(path);

}

Параметр NULL, передаваемый таким процедурам, как CGPathMoveToPoint, представляет возможные преобразования, которые могут быть применены при отрисовке фигур и линий по заданному пути. Подробнее о преобразованиях рассказано в разделах 17.11–17.13.

Итак, нарисовать путь в графическом контексте очень просто. На самом деле следует всего лишь запомнить, как создать новый изменяемый путь (CGPathCreateMutable), добавить этот путь к вашему графическому контексту (CGContextAddPath) и отрисовать путь в графическом контексте (CGContextDrawPath). Запустив этот код, вы получите примерно такой результат, как на рис. 17.21.

Рис. 17.21. Рисование в графическом контексте с использованием путей

 

См. также

Разделы 17.6, 17.11–17.13.

 

17.8. Отрисовка прямоугольников

 

Постановка задачи

Требуется отрисовывать прямоугольники в графическом контексте.

 

Решение

Воспользуйтесь CGPathAddRect для добавления прямоугольника к пути, а потом отрисовывайте этот путь в графическом контексте.

 

Обсуждение

Как мы узнали из раздела 17.7, создавать и использовать пути довольно просто. Одна из процедур, которую Core Graphics позволяет использовать с путями, — CGPathAddRect. Она позволяет отрисовывать прямоугольники как части путей. Вот пример:

— (void)drawRect:(CGRect)rect{

/* Сначала создаем путь. Просто описатель пути. */

CGMutablePathRef path = CGPathCreateMutable();

/* Это границы прямоугольника. */

CGRect rectangle = CGRectMake(10.0f,

10.0f,

200.0f,

300.0f);

/* Добавляем прямоугольник к пути. */

CGPathAddRect(path,

NULL,

rectangle);

/* Получаем описатель текущего контекста. */

CGContextRef currentContext = UIGraphicsGetCurrentContext();

/* Добавляем путь к контексту. */

CGContextAddPath(currentContext,

path);

/* Задаем голубой в качестве цвета заливки. */

[[UIColor colorWithRed:0.20f

green:0.60f

blue:0.80f

alpha:1.0f] setFill];

/* Задаем для обводки коричневый цвет. */

[[UIColor brownColor] setStroke];

/* Задаем для ширины (обводки) значение 5. */

CGContextSetLineWidth(currentContext,

5.0f);

/* Проводим путь в контексте и применяем к нему заливку. */

CGContextDrawPath(currentContext,

kCGPathFillStroke);

/* Избавляемся от пути. */

CGPathRelease(path);

}

Здесь мы рисуем на пути прямоугольник, который впоследствии заполняем голубым цветом, а края прямоугольника отрисовываем коричневым. На рис. 17.22 показано, что мы увидим, запустив эту программу (конечно же, цвета на черно-белой иллюстрации не видны).

Если вы собираетесь отрисовать несколько прямоугольников, то можете передать массив объектов CGRect процедуре CGPathAddRects. Вот пример:

— (void)drawRect:(CGRect)rect{

/* Сначала создаем путь. Просто описатель пути. */

CGMutablePathRef path = CGPathCreateMutable();

/* Это границы первого прямоугольника. */

CGRect rectangle1 = CGRectMake(10.0f,

10.0f,

Рис. 17.22. Отрисовка прямоугольника с помощью путей

200.0f,

300.0f);

/* Это границы второго прямоугольника. */

CGRect rectangle2 = CGRectMake(40.0f,

100.0f,

90.0f,

300.0f);

/* Помещаем оба прямоугольника в массив. */

CGRect rectangles[2]Odd (англ.) — «нечетный», delete (англ.) — «удалить». — Примеч. пер.
 = {

rectangle1, rectangle2

};

/* Добавляем прямоугольники к пути. */

CGPathAddRects(path,

NULL,

(const CGRect *)&rectangles,

2);

/* Получаем описатель текущего контекста. */

CGContextRef currentContext = UIGraphicsGetCurrentContext();

/* Добавляем путь к контексту. */

CGContextAddPath(currentContext,

path);

/* Задаем голубой в качестве цвета заливки. */

[[UIColor colorWithRed:0.20f

green:0.60f

blue:0.80f

alpha:1.0f] setFill];

/* Задаем для обводки черный цвет. */

[[UIColor blackColor] setStroke];

/* Задаем для ширины (обводки) значение. 5 */

CGContextSetLineWidth(currentContext,

5.0f);

/* Проводим путь в контексте и применяем к нему заливку. */

CGContextDrawPath(currentContext,

kCGPathFillStroke);

/* Избавляемся от пути. */

CGPathRelease(path);

}

На рис. 17.23 показано, как результат выполнения этого кода будет выглядеть в симуляторе iOS. Мы передаем процедуре CGPathAddRects следующие параметры (именно в таком порядке).

Рис. 17.23. Одновременная отрисовка нескольких прямоугольников

1. Описатель пути, к которому мы будем добавлять прямоугольники.

2. Преобразование (при его наличии), которое потребуется применить к прямоугольникам. (Подробнее о преобразованиях рассказано в разделах 17.11–17.13.)

3. Ссылку на массив CGRect, в котором содержатся прямоугольники.

4. Количество прямоугольников в массиве, который мы передали в предыдущем параметре. Исключительно важно передать именно столько прямоугольников, сколько содержится в вашем массиве, чтобы избежать непредвиденного поведения этой процедуры.

 

См. также

Разделы 17.7, 17.11–17.13.

 

17.9. Добавление теней к фигурам

 

Постановка задачи

Требуется применять тени к тем фигурам, которые вы отрисовываете в графическом контексте.

 

Решение

Воспользуйтесь процедурой CGContextSetShadow.

 

Обсуждение

В Core Graphics рисовать тени не составляет никакого труда. Графический контекст — это и есть элемент, несущий на себе тень. Это означает, что от вас требуется просто применить тень к контексту, отрисовать для нее необходимые контуры, а потом удалить тень с контекста (или задать новый контекст). Чуть позже мы рассмотрим эти операции на примере.

В Core Graphics для применения тени к графическому контексту могут использоваться две процедуры:

• CGContextSetShadow — создает черные или серые тени, принимает три параметра:

• графический контекст, к которому следует применить тень;

• отступ, указываемый значением типа CGSize, на который тень распространяется вправо и вниз от каждой фигуры. Чем больше значение x данного отступа, тем больше тень будет распространяться вправо. Чем больше значение y, тем ниже будет тень;

• значение размытия, которое следует применить к тени, указывается как число с плавающей точкой (CGFloat). Если задать для данного параметра значение 0.0f, то у тени будут абсолютно четкие контуры. Чем выше это значение, тем более размытой будет становиться тень. Далее будет приведен соответствующий пример;

• CGContextSetShadowWithColor — принимает такие же параметры, как и CGContextSetShadow, плюс еще один. Этот четвертый параметр типа CGColorRef задает цвет тени.

В начале этого подраздела я отмечал, что графический контекст сохраняет свойства расположенных в нем теней, пока мы специально не удалим тень. Хотелось бы дополнительно разъяснить этот момент на примере. Напишем код, позволяющий нам отрисовать два прямоугольника: первый с тенью, второй — без нее. Первый прямоугольник нарисуем так:

— (void) drawRectAtTopOfScreen{

/* Получаем описатель для актуального контекста. */

CGContextRef currentContext = UIGraphicsGetCurrentContext();

CGContextSetShadowWithColor(currentContext,

CGSizeMake(10.0f, 10.0f),

20.0f,

[[UIColor grayColor] CGColor]);

/* Сначала создаем путь. Просто описатель пути. */

CGMutablePathRef path = CGPathCreateMutable();

/* Это границы прямоугольника. */

CGRect firstRect = CGRectMake(55.0f,

60.0f,

150.0f,

150.0f);

/* Добавляем прямоугольник к пути. */

CGPathAddRect(path,

NULL,

firstRect);

/* Добавляем путь к контексту. */

CGContextAddPath(currentContext,

path);

/* Задаем голубой в качестве цвета заливки. */

[[UIColor colorWithRed:0.20f

green:0.60f

blue:0.80f

alpha:1.0f] setFill];

/* Заполняем путь в контексте цветом заливки. */

CGContextDrawPath(currentContext,

kCGPathFill);

/* Избавляемся от пути. */

CGPathRelease(path);

}

— (void) drawRect:(CGRect)rect{

[self drawRectAtTopOfScreen];

}

Если вызвать этот метод в методе экземпляра drawRect: объекта-вида, то на экране появится прямоугольник с красивой тенью, как мы и хотели (рис. 17.24).

Рис. 17.24. Тень, примененная к прямоугольнику

Теперь нарисуем второй прямоугольник. Мы не будем специально запрашивать тень, а оставим свойство тени графического контекста таким же, как и в первом прямоугольнике:

— (void) drawRectAtBottomOfScreen{

/* Получаем описатель текущего контекста. */

CGContextRef currentContext = UIGraphicsGetCurrentContext();

CGMutablePathRef secondPath = CGPathCreateMutable();

CGRect secondRect = CGRectMake(150.0f,

250.0f,

100.0f,

100.0f);

CGPathAddRect(secondPath,

NULL,

secondRect);

CGContextAddPath(currentContext,

secondPath);

[[UIColor purpleColor] setFill];

CGContextDrawPath(currentContext,

kCGPathFill);

CGPathRelease(secondPath);

}

— (void)drawRect:(CGRect)rect{

[self drawRectAtTopOfScreen];

[self drawRectAtBottomOfScreen];

}

Метод drawRect: сначала вызывает метод drawRectAtTopOfScreen, а сразу же после этого — метод drawRectAtBottomOfScreen. Мы не запрашивали создание тени для прямоугольника drawRectAtBottomOfScreen, но после запуска кода вы увидите примерно такой результат, как на рис. 17.25.

Рис. 17.25. Мы не собирались применять тень ко второму прямоугольнику, но она есть

Сразу заметно, что тень применена и ко второму прямоугольнику, расположенному в нижней части экрана. Чтобы избежать этого, мы сохраним графический контекст еще до применения к нему тени, а потом, когда захотим удалить теневой эффект, восстановим это состояние.

В широком смысле прием сохранения и последующего восстановления графического контекста работает не только с тенями. В ходе такой операции восстанавливаются все данные, связанные с графическим контекстом (цвет заливки, шрифт, толщина линий и т. д.), — они возвращаются к установленным ранее значениям. Так, например, если до восстановления графического контекста вы работали с иными цветами заливки и обводки, чем те, что заданы в нем, то эти цвета будут сброшены.

Можно сохранять состояние графического контекста с помощью процедуры CGContextSaveGState и восстанавливать его прежнее состояние, используя процедуру CGContextRestoreGState. Так, если мы изменим процедуру drawRectAtTopOfScreen, сохранив состояние графического контекста до применения тени, а потом восстановим это состояние после того, как отрисуем путь, то результаты у нас получатся иные (рис. 17.26):

— (void) drawRectAtTopOfScreen{

/* Получаем описатель текущего контекста. */

CGContextRef currentContext = UIGraphicsGetCurrentContext();

CGContextSaveGState(currentContext);

CGContextSetShadowWithColor(currentContext,

CGSizeMake(10.0f, 10.0f),

20.0f,

[[UIColor grayColor] CGColor]);

/* Сначала создаем путь. Просто описатель пути. */

CGMutablePathRef path = CGPathCreateMutable();

/* Это границы прямоугольника. */

CGRect firstRect = CGRectMake(55.0f,

60.0f,

150.0f,

150.0f);

/* Добавляем прямоугольник к пути. */

CGPathAddRect(path,

NULL,

firstRect);

/* Добавляем путь к контексту. */

CGContextAddPath(currentContext,

path);

/* Задаем голубой в качестве цвета заливки. */

[[UIColor colorWithRed:0.20f

green:0.60f

blue:0.80f

alpha:1.0f] setFill];

/* Проводим путь в контексте и применяем к нему заливку. */

CGContextDrawPath(currentContext,

kCGPathFill);

/* Избавляемся от пути. */

CGPathRelease(path);

/* Восстанавливаем контекст в исходном состоянии

(в котором мы начали с ним работать). */

CGContextRestoreGState(currentContext);

}

Рис. 17.26. Сохранение состояния графического контекста для точного отображения теней

 

17.10. Отрисовка градиентов

 

Постановка задачи

Требуется рисовать в графическом контексте градиенты, используя различные цвета.

 

Решение

Воспользуйтесь функцией CGGradientCreateWithColor.

 

Обсуждение

Мы уже поговорили о цвете в разделе 17.3 и теперь попробуем воспользоваться нашими навыками для решения более интересных задач, чем рисование простых прямоугольников и разноцветного текста.

В Core Graphics программист может создавать градиенты двух типов: осевой и радиальный (но мы обсудим только осевые градиенты). Осевой градиент начинается в определенной точке с одного цвета и заканчивается в другой точке иным цветом (конечно, градиент можно и начать и закончить одним и тем же цветом, но тогда он будет не слишком напоминать градиент). «Осевой» означает «относящийся к оси». Между двумя точками (начальной и конечной) создается сегмент линии, он и будет той осью, вдоль которой отрисовывается градиент. Образец осевого градиента показан на рис. 17.27. На самом деле это осевой градиент, начинающийся с голубого цвета и заканчивающийся зеленым, но на черно-белой иллюстрации этого, конечно же, не видно.

Рис. 17.27. Осевой градиент

Чтобы создать осевой градиент, вызовите функцию CGGradientCreateWithColorComponents. Возвращаемым значением этой функции будет новый градиент типа CGGradientRef. Это описатель градиента. Закончив работать с градиентом, необходимо вызвать процедуру CGGradientRelease, передав описатель тому градиенту, который вы ранее получили от CGGradientCreateWithColorComponents.

Функция CGGradientCreateWithColorComponents принимает четыре параметра.

• Цветовое пространство — это контейнер для цветового диапазона, он должен относиться к типу CGColorSpaceRef. Для этого параметра можем просто передать возвращаемое значение функции CGColorSpaceCreateDeviceRGB — и получим пространство цветов RGB.

 Массив цветовых компонентов (подробнее об этом — в разделе 17.3) — здесь должны содержаться значения красного, зеленого, голубого цветов и альфа-значение, все они относятся к типу CGFloat. Количество элементов в массиве тесно связано со значениями следующих двух параметров. Вы должны будете включить в этот массив такое количество значений, которого будет достаточно для того, чтобы указать ряд положений, обозначенных в четвертом параметре. Так, если вы запрашиваете только два положения (начальную и конечную точки), то в этом массиве должно быть минимум два цвета. А поскольку в состав каждого цвета входят красный, зеленый, голубой компоненты, а также альфа-значение, в данном массиве должно быть 2 × 4 элемента: четыре для первого цвета и четыре — для второго. Не волнуйтесь, если пока не все понимаете, — все встанет на свои места после изучения примеров.

 Положения оттенков в цветовом массиве — этот параметр определяет, как быстро в градиенте осуществляется переход от одного оттенка к другому. Количество элементов должно быть таким же, как и значение четвертого параметра. Например, если вы запрашиваете четыре цвета и хотите, чтобы первый цвет был начальным цветом градиента, а последний цвет располагался в конце градиента, то нужно предоставить массив из двух элементов типа CGFloat, где первый элемент имеет значение 0.0f (как в первом компоненте цветового массива), а второй элемент — 3.0f (как в четвертом компоненте цветового массива). Значения двух промежуточных цветов определяют, в каком именно порядке расположены в градиенте оттенки, лежащие между начальным и конечным цветами. Опять же не волнуйтесь, если это сложно сразу усвоить. Я приведу много примеров, на которых вся концепция станет совершенно ясна.

 Количество положений — здесь мы указываем, сколько цветов и положений должно быть в градиенте.

Рассмотрим пример. Предположим, мы хотим нарисовать градиент, который показан на рис. 17.27. Вот как это делается.

1. Выбираем начальную и конечную точки градиента — ось, вдоль которой будут изменяться оттенки. В данном случае я указываю переход слева направо. Представьте, что цвет изменяется по мере движения вдоль гипотетической линии. Цвета будут располагаться по оси так, что любая вертикальная линия, перпендикулярно пересекающая ось градиента, будет пролегать только по одному оттенку. В случае, показанном на рис. 17.27, любая вертикальная линия будет перпендикулярна оси градиента. Рассмотрим эти вертикальные линии подробнее. Действительно, в любой ее точке цвет градиента один и тот же. Вот так и строится градиент. Хорошо, хватит теории — переходим ко второму этапу.

2. Теперь нам нужно создать цветовое пространство, которое будет передано функции CGGradientCreateWithColorComponents в первом параметре, как было объяснено ранее:

CGColorSpaceRef colorSpace =

CGColorSpaceCreateDeviceRGB();

Закончив работу с этим цветовым пространством, мы избавимся от него.

3. Зададим голубой в качестве начального цвета (слева), а зеленый — в качестве конечного (справа), как показано на рис. 17.27. Названия, которыми я пользуюсь (startColorComponents и endColorComponents), выбраны произвольно и помогают нам не забыть о положении каждого цвета. Для указания того, какой цвет будет находиться в начале, а какой — в конце, мы воспользуемся позициями из массива:

UIColor *startColor = [UIColor blueColor];

CGFloat *startColorComponents =

(CGFloat *)CGColorGetComponents([startColor CGColor]);

UIColor *endColor = [UIColor greenColor];

CGFloat *endColorComponents =

(CGFloat *)CGColorGetComponents([endColor CGColor]);

Если вы забыли, какая концепция лежит в основе цветовых компонентов, вернитесь к этому вопросу, изложенному в разделе 17.3, а потом продолжайте читать.

4. Получив компоненты каждого цвета, мы помещаем все их в одномерный массив, который будет передан функции CGGradientCreateWithColorComponents:

CGFloat colorComponents[8]«Ошибка: данные не могут быть считаны, так как имеют неверный формат». — Примеч. пер .
 = {

/* Четыре компонента оранжевого цвета (RGBA) */

startColorComponents[0],

startColorComponents[1]Подробнее об этих элементах см.: http://habrahabr.ru/post/79280 /. — Примеч. пер.
,

startColorComponents[2]Odd (англ.) — «нечетный», delete (англ.) — «удалить». — Примеч. пер.
,

startColorComponents[3]Первая строка: «Потенциально неполная реализация метода». Вторая строка: «Неполная реализация метода». — Примеч. пер .
, /* Первый цвет = оранжевый */

/* Четыре компонента голубого цвета (RGBA) */

endColorComponents[0],

endColorComponents[1]Подробнее об этих элементах см.: http://habrahabr.ru/post/79280 /. — Примеч. пер.
,

endColorComponents[2]Odd (англ.) — «нечетный», delete (англ.) — «удалить». — Примеч. пер.
,

endColorComponents[3]Первая строка: «Потенциально неполная реализация метода». Вторая строка: «Неполная реализация метода». — Примеч. пер .
, /* Второй цвет = голубой */

};

5. Поскольку у нас в этом массиве всего два цвета, следует указать, что первый цвет расположен в самом начале градиента (точка с координатами (0; 0)) а второй — в самом конце (точка (1; 0)). Итак, поместим эти показатели в массив, предназначенный для передачи функции CGGradientCreateWithColorComponents:

CGFloat colorIndices[2]Odd (англ.) — «нечетный», delete (англ.) — «удалить». — Примеч. пер.
 = {

0.0f, /* Цвет 0 в массиве colorComponents */

1.0f, /* Цвет 1 в массиве colorComponents */

};

6. Теперь нам остается просто вызвать функцию CGGradientCreateWithColorComponents со всеми сгенерированными значениями:

CGGradientRef gradient =

CGGradientCreateWithColorComponents

(colorSpace,

(const CGFloat *)&colorComponents,

(const CGFloat *)&colorIndices,

2);

7. Прекрасно! Теперь в переменной gradient находится объект градиента. Пока не забыли, нужно высвободить цветовое пространство, созданное с помощью функции CGColorSpaceCreateDeviceRGB:

CGColorSpaceRelease(colorSpace);

Теперь воспользуемся процедурой CGContextDrawLinearGradient для отрисовки осевого градиента в графическом контексте. Эта процедура принимает пять параметров.

• Графический контекст — указывает графический контекст, в котором будет отрисовываться осевой градиент.

 Осевой градиент — описатель объекта осевого градиента. Этот объект градиента создан с помощью функции CGGradientCreateWithColorComponents.

 Начальная точка — точка в графическом контексте, указанная в параметре CGPoint, в которой начинается градиент.

 Конечная точка — точка в графическом контексте, указанная в параметре CGPoint, в которой заканчивается градиент.

 Параметры отрисовки градиента — указывают, что должно произойти, если начальная и конечная точки не совпадают с краями графического контекста. Для заполнения пространства, лежащего вне градиента, можно использовать начальный или конечный цвета. Этот параметр может принимать одно из следующих значений:

• kCGGradientDrawsAfterEndLocation — распространяет градиент на все точки после конечной точки градиента;

• kCGGradientDrawsBeforeStartLocation — распространяет градиент на все точки до начальной точки градиента;

• 0 — градиент не распространяется.

Чтобы распространить градиент в обе стороны, укажите оба параметра — «до» и «после», — воспользовавшись логическим оператором ИЛИ (обозначается символом |). Пример будет рассмотрен далее:

CGRect screenBounds = [[UIScreen mainScreen] bounds];

CGPoint startPoint, endPoint;

startPoint = CGPointMake(0.0f,

screenBounds.size.height / 2.0f);

endPoint = CGPointMake(screenBounds.size.width,

startPoint.y);

CGContextDrawLinearGradient

(currentContext,

gradient,

startPoint,

endPoint,

0);

CGGradientRelease(gradient);

Описатель градиента, который мы высвобождаем в конце этого кода, был создан в другом блоке кода в одном из предыдущих примеров.

Очевидно, что результат выполнения этого кода будет напоминать рис. 17.27. Поскольку мы начали градиент с самой левой точки экрана и распространили его до самой правой, то не можем воспользоваться теми значениями, которые способен получить последний параметр процедуры CGContextDrawLinearGradient, параметр отрисовки градиента. Исправим этот недостаток. Попробуем нарисовать такой градиент, как на рис. 17.28.

Рис. 17.28. Осевой градиент с оттенками, распространяющимися за его начальную и конечную точки

При написании кода воспользуемся той же процедурой, о которой говорили ранее:

— (void)drawRect:(CGRect)rect{

CGContextRef currentContext = UIGraphicsGetCurrentContext();

CGContextSaveGState(currentContext);

CGColorSpaceRef colorSpace =

CGColorSpaceCreateDeviceRGB();

UIColor *startColor = [UIColor orangeColor];

CGFloat *startColorComponents =

(CGFloat *)CGColorGetComponents([startColor CGColor]);

UIColor *endColor = [UIColor blueColor];

CGFloat *endColorComponents =

(CGFloat *)CGColorGetComponents([endColor CGColor]);

CGFloat colorComponents[8]«Ошибка: данные не могут быть считаны, так как имеют неверный формат». — Примеч. пер .
 = {

/* Четыре компонента оранжевого цвета (RGBA (RGBA) */

startColorComponents[0],

startColorComponents[1]Подробнее об этих элементах см.: http://habrahabr.ru/post/79280 /. — Примеч. пер.
,

startColorComponents[2]Odd (англ.) — «нечетный», delete (англ.) — «удалить». — Примеч. пер.
,

startColorComponents[3]Первая строка: «Потенциально неполная реализация метода». Вторая строка: «Неполная реализация метода». — Примеч. пер .
, /* Первый цвет = оранжевый */

/* Четыре компонента голубого цвета (RGBA) */

endColorComponents[0],

endColorComponents[1]Подробнее об этих элементах см.: http://habrahabr.ru/post/79280 /. — Примеч. пер.
,

endColorComponents[2]Odd (англ.) — «нечетный», delete (англ.) — «удалить». — Примеч. пер.
,

endColorComponents[3]Первая строка: «Потенциально неполная реализация метода». Вторая строка: «Неполная реализация метода». — Примеч. пер .
, /* Второй цвет = голубой */

};

CGFloat colorIndices[2]Odd (англ.) — «нечетный», delete (англ.) — «удалить». — Примеч. пер.
 = {

0.0f, /* Цвет 0 в массиве colorComponents */

1.0f, /* Цвет 1 в массиве colorComponents */

};

CGGradientRef gradient = CGGradientCreateWithColorComponents

(colorSpace,

(const CGFloat *)&colorComponents,

(const CGFloat *)&colorIndices,

2);

CGColorSpaceRelease(colorSpace);

CGPoint startPoint, endPoint;

startPoint = CGPointMake(120,

260);

endPoint = CGPointMake(200.0f,

220);

CGContextDrawLinearGradient (currentContext,

gradient,

startPoint,

endPoint,

kCGGradientDrawsBeforeStartLocation |

kCGGradientDrawsAfterEndLocation);

CGGradientRelease(gradient);

CGContextRestoreGState(currentContext);

}

Возможно, вам не совсем понятно, как при смешивании значений kCGGradientDrawsBeforeStartLocation и kCGGradientDrawsAfterEndLocation, переданных процедуре CGContextDrawLinearGradient, получается диагональный эффект, как на рис. 17.28. Поэтому уберем эти значения и зададим для этого параметра процедуры CGContextDrawLinearGradient значение 0 — как и раньше. Результат получится как на рис. 17.29.

Рис. 17.29. Осевой градиент без распространения цветов

На рис. 17.28 и 17.29 изображены одинаковые градиенты. Но у градиента на рис. 17.28 цвета начальной и конечной точек распространяются по обе стороны градиента на весь графический контекст, поэтому весь экран оказывается закрашен.

 

См. также

Раздел 17.3.

 

17.11. Перемещение фигур, нарисованных в графических контекстах

 

Постановка задачи

Требуется переместить все, что изображено в графическом контексте, на новое место, не изменяя при этом кода отрисовки, либо просто без труда сместить содержимое графического контекста.

 

Решение

Воспользуйтесь функцией CGAffineTransformMakeTranslation для создания аффинного преобразования сдвига.

 

Обсуждение

В разделе 17.8 было упомянуто о преобразованиях. Преобразование — это, в сущности, просто изменение способа отображения рисунка. Преобразования в Core Graphics — это объекты, применяемые к фигурам перед отрисовкой последних. Например, можно создать преобразование сдвига (Translation Transformation). «Сдвига чего?» — могли бы спросить вы. Дело в том, что преобразование сдвига — это механизм, позволяющий сместить фигуру или графический контекст.

Среди других типов преобразований следует также назвать вращение (см. раздел 17.13) и масштабирование (см. раздел 17.12). Все это примеры аффинных преобразований, то есть при таком преобразовании каждая точка оригинала сопоставляется с другой точкой в окончательной версии. Все преобразования, о которых мы будем говорить в этой книге, являются аффинными.

В ходе преобразования сдвига актуальное положение фигуры на пути или в графическом контексте сдвигается на другую относительную позицию. Например, если вы поставите точку с координатами (10; 20), примените к ней преобразование сдвига (30; 40) и снова ее поставите, точка окажется расположенной в координатах (40; 60), поскольку 40 = 10 + 30, а 60 = 20 + 40.

Чтобы создать новое преобразование сдвига, используется функция CGAffineTransformMakeTranslation, которая возвращает аффинное преобразование типа CGAffineTransform. Два параметра этой функции указывают сдвиг по осям X и Y в точках.

В разделе 17.8 мы изучили, что процедура CGPathAddRect принимает в качестве второго параметра объект преобразования типа CGAffineTransform. Чтобы сместить прямоугольник с его исходной позиции на другую, можно просто создать аффинное преобразование, указывающее изменения, которые вы хотели бы применить к координатам x и y, и передать преобразование второму параметру процедуры CGPathAddRect, как показано далее:

— (void)drawRect:(CGRect)rect{

/* Сначала создаем путь. Просто описатель пути. */

CGMutablePathRef path = CGPathCreateMutable();

/* Это границы прямоугольника. */

CGRect rectangle = CGRectMake(10.0f,

10.0f,

200.0f,

300.0f);

/* Мы хотим сместить прямоугольник на 100 точек вправо,

не изменив при этом его положения по оси Y. */

CGAffineTransform transform = CGAffineTransformMakeTranslation(100.0f,

0.0f);

/* Добавляем прямоугольник к пути. */

CGPathAddRect(path,

&transform,

rectangle);

/* Получаем описатель текущего контекста. */

CGContextRef currentContext =

UIGraphicsGetCurrentContext();

/* Добавляем путь к контексту. */

CGContextAddPath(currentContext,

path);

/* Задаем голубой в качестве цвета заливки. */

[[UIColor colorWithRed:0.20f

green:0.60f

blue:0.80f

alpha:1.0f] setFill];

/* Задаем для обводки коричневый цвет. */

[[UIColor brownColor] setStroke];

/* Задаем для ширины (обводки) значение 5. */

CGContextSetLineWidth(currentContext,

5.0f);

/* Проводим путь в контексте и применяем к нему заливку. */

CGContextDrawPath(currentContext,

kCGPathFillStroke);

/* Избавляемся от пути. */

CGPathRelease(path);

}

На рис. 17.30 показан результат выполнения этого блока кода внутри объекта-вида.

Сравните рис. 17.30 и 17.22. Видите разницу? Еще раз просмотрите исходный код для обеих фигур и убедитесь в том, что положения по осям X и Y, указанные для обоих прямоугольников, в обоих блоках кода идентичны. Различие заключается только в том, что на рис. 17.30 мы видим результат применения к прямоугольнику аффинного преобразования, когда прямоугольник добавляется к пути.

Кроме применения преобразований к фигурам, отрисовываемым относительно путей, мы можем применять преобразования и к графическому контексту с помощью процедуры CGContextTranslateCTM. Она применяет преобразование к текущей матрице преобразований (Current Transformation Matrix, CTM). Хотя это название и может показаться сложным, понять его смысл не составляет труда. Считайте CTM правилами, определяющими расположение центра вашего графического контекста, а также правилами проецирования каждой отрисовываемой точки на экране. Например, если вы приказываете Core Graphics поставить точку с координатами (0; 0), Core Graphics найдет центр экрана, получив эту информацию из текущей матрицы преобразований. Затем CTM выполнит определенные вычисления и сообщит Core Graphics, что искомая точка расположена в верхнем левом углу экрана. С помощью таких процедур, как CGContextTranslateCTM, можно изменить конфигурацию этой матрицы, после чего заставить все фигуры, отрисованные в графическом контексте, занять на холсте другие позиции. Вот пример, в котором мы достигаем точно такого же эффекта, как и на рис. 17.30, но применяем преобразование сдвига не к самому прямоугольнику, а к текущей матрице преобразований:

Рис. 17.30. Прямоугольник с аффинным преобразованием сдвига

— (void)drawRect:(CGRect)rect{

/* Сначала создаем путь. Просто описатель пути. */

CGMutablePathRef path = CGPathCreateMutable();

/* Это границы прямоугольника. */

CGRect rectangle = CGRectMake(10.0f,

10.0f,

200.0f,

300.0f);

/* Добавляем прямоугольник к пути. */

CGPathAddRect(path,

NULL,

rectangle);

/* Получаем описатель текущего контекста. */

CGContextRef currentContext = UIGraphicsGetCurrentContext();

/* Сохраняем состояние контекста, чтобы позже

можно было восстановить это состояние. */

CGContextSaveGState(currentContext);

/* Сдвигаем текущую матрицу преобразований

на 100 точек вправо. */

CGContextTranslateCTM(currentContext,

100.0f,

0.0f);

/* Добавляем путь к контексту. */

CGContextAddPath(currentContext,

path);

/* Задаем голубой в качестве цвета заливки. */

[[UIColor colorWithRed:0.20f

green:0.60f

blue:0.80f

alpha:1.0f] setFill];

/* Задаем для обводки коричневый цвет. */

[[UIColor brownColor] setStroke];

/* Задаем для ширины (обводки) значение 5. */

CGContextSetLineWidth(currentContext,

5.0f);

/* Проводим путь в контексте и применяем к нему заливку. */

CGContextDrawPath(currentContext,

kCGPathFillStroke);

/* Избавляемся от пути. */

CGPathRelease(path);

/* Восстанавливаем состояние контекста. */

CGContextRestoreGState(currentContext);

}

Запустив эту программу, вы получите точно такие же результаты, как и на рис. 17.30.

 

См. также

Разделы 17.8, 17.12 и 17.13.

 

17.12. Масштабирование фигур, нарисованных в графических контекстах

 

Постановка задачи

Требуется динамически масштабировать фигуры, отрисованные в графическом контексте, в сторону уменьшения или увеличения.

 

Решение

Создайте аффинное преобразование масштаба, воспользовавшись функцией CGAffineTransformMakeScale.

 

Обсуждение

В разделе 17.11 было объяснено, что такое преобразование и как применять его к фигурам и графическим контекстам. Один из вариантов преобразования, которым вы можете воспользоваться, — это масштабирование. Core Graphics позволяет без проблем масштабировать фигуру, например круг, на 100 % относительно ее исходного размера.

Чтобы создать аффинное преобразование масштаба, пользуйтесь функцией CGAffineTransformMakeScale, которая возвращает объект преобразования типа CGAffineTransform. Если вы хотите применить преобразование масштаба непосредственно к графическому контексту, примените процедуру CGContextScaleCTM, которая масштабирует CTM. Подробнее о CTM (текущей матрице преобразований) рассказано в разделе 17.11.

Функции преобразования масштаба принимают два параметра: масштабирование по оси X и масштабирование по оси Y. Еще раз обратимся к прямоугольнику с рис. 17.22. Если мы хотим масштабировать этот прямоугольник, чтобы его исходные длина и ширина уменьшились вполовину, то можно просто масштабировать по оси X и Y на 0,5 (вполовину от исходного значения), как показано здесь:

/* Масштабируем прямоугольник, уменьшая его на половину. */

CGAffineTransform transform =

CGAffineTransformMakeScale(0.5f, 0.5f);

/* Добавляем прямоугольник к пути. */

CGPathAddRect(path,

&transform,

rectangle);

На рис. 17.31 показано, что получится, когда мы применим преобразование масштаба к коду, написанному в разделе 17.8.

Рис. 17.31. Масштабирование прямоугольника

Дополнительно к функции CGAffineTransformMakeScale можно использовать процедуру CGContextScaleCTM, помогающую применить преобразование масштаба к графическому контексту. Следующий код даст тот же эффект, что и в предыдущем примере (вновь обратите внимание на рис. 17.31):

— (void)drawRect:(CGRect)rect{

/* Сначала создаем путь. Просто описатель пути. */

CGMutablePathRef path = CGPathCreateMutable();

/* Это границы прямоугольника. */

CGRect rectangle = CGRectMake(10.0f,

10.0f,

200.0f,

300.0f);

/* Добавляем прямоугольник к пути. */

CGPathAddRect(path,

NULL,

rectangle);

/* Получаем описатель текущего контекста. */

CGContextRef currentContext = UIGraphicsGetCurrentContext();

/* Масштабируем все фигуры, отрисованные в графическом контексте,

уменьшая их на половину. */

CGContextScaleCTM(currentContext,

0.5f,

0.5f);

/* Добавляем путь к контексту. */

CGContextAddPath(currentContext,

path);

/* Задаем голубой в качестве цвета заливки. */

[[UIColor colorWithRed:0.20f

green:0.60f

blue:0.80f

alpha:1.0f] setFill];

/* Задаем для обводки коричневый цвет. */

[[UIColor brownColor] setStroke];

/* Задаем для ширины (обводки) значение 5. */

CGContextSetLineWidth(currentContext,

5.0f);

/* Проводим путь в контексте и применяем к нему заливку. */

CGContextDrawPath(currentContext,

kCGPathFillStroke);

/* Избавляемся от пути. */

CGPathRelease(path);

}

 

См. также

Раздел 17.11.

 

17.13. Вращение фигур, нарисованных в графических контекстах

 

Постановка задачи

Требуется иметь возможность вращать содержимое, отрисованное в графическом контексте, не изменяя при этом код отрисовки.

 

Решение

Воспользуйтесь функцией CGAffineTransformMakeRotation для создания аффинного преобразования вращения.

 

Обсуждение

Перед тем как приступать к работе с этим разделом, настоятельно рекомендую вам перечитать материал из разделов 17.11 и 17.12. Чтобы сократить текст, я старался не дублировать в последующих разделах материал, уже изложенный в предыдущих.

Точно так же, как при масштабировании и сдвиге, мы можем применять преобразование вращения к фигурам, отрисованным на путях и прямо в графическом контексте. Можно использовать функцию CGAffineTransformMakeRotation и сообщать значение вращения в радианах, а в качестве возвращаемого значения получать преобразование вращения типа CGAffineTransform. Затем это преобразование можно применять к путям и фигурам. Если вы хотите повернуть весь контекст на определенный угол, используйте процедуру CGContextRotateCTM.

Повернем прямоугольник, изображенный на рис. 17.22, на 45° по часовой стрелке (рис. 17.32). Значение, полученное в результате вращения, должно быть выражено в радианах. Положительные значения дают вращение по часовой стрелке, а отрицательные — против часовой:

/* Вращаем прямоугольник на 45° по часовой стрелке. */

CGAffineTransform transform =

CGAffineTransformMakeRotation((45.0f * M_PI) / 180.0f);

/* Добавляем прямоугольник к пути. */

CGPathAddRect(path,

&transform,

rectangle);

Рис. 17.32. Вращение прямоугольника

Как было показано в разделе 17.12, мы можем применить преобразование и непосредственно к графическому контексту — с помощью процедуры CGContextRotateCTM.

 

См. также

Разделы 17.11 и 17.12.

 

17.14. Анимирование и перемещение видов

 

Постановка задачи

Требуется анимировать смещение видов.

 

Решение

При смещении видов используйте анимационные методы класса UIView.

 

Обсуждение

В операционной системе iOS предоставляются различные способы выполнения анимации, среди этих возможностей есть как низкоуровневые, так и сравнительно высокоуровневые. Самый высокий уровень работы в данном случае обеспечивается во фреймворке UIKit, о котором мы также поговорим в этом разделе. В UIKit содержится некоторая низкоуровневая функциональность Core Animation, предоставляемая нам в форме довольно аккуратного API, с которым очень удобно работать.

Работа с анимацией в UIKit начинается с вызова метода класса beginAnimations: context:, относящегося к классу UIView. Первый параметр — это опциональное имя, которое вы можете выбрать для вашей анимации, а второй — опциональный контекст, который можно получить позже для передачи анимационным методам делегатов. Вскоре мы поговорим о них обоих.

После того как вы запустите анимацию с помощью метода beginAnimations: context:, она не начнет происходить, так как для этого потребуется еще вызвать метод класса commitAnimations, относящийся к классу UIView. Вычисления, которые вы производите над объектом-видом между вызовом beginAnimations: context: и commitAnimations (в результате которых этот вид, к примеру, перемещается), будут сопровождаться анимацией только после вызова commitAnimations. Рассмотрим пример.

Как упоминалось в разделе 17.4, я включил в пакет моего приложения рисунок Xcode.png. Это ярлык Xcode, который я нашел в картинках Google (см. рис. 17.9). Теперь в моем контроллере вида (см. введение к этой главе) я хочу поместить этот рисунок в виде с изображением типа UIImageView, а потом переместить этот вид с изображением из верхнего левого угла экрана в нижний правый угол.

Вот как мы решим эту задачу.

1. Откройте. h-файл вашего контроллера вида.

2. Определите экземпляр UIImageView как свойство контроллера вида и назовите его xcodeImageView:

#import «ViewController.h»

@interface ViewController ()

@property (nonatomic, strong) UIImageView *xcodeImageView;

@end

3. Когда вид загрузится, поместите изображение Xcode.png в экземпляр UIImage:

— (void) viewDidLoad{

[super viewDidLoad];

UIImage *xcodeImage = [UIImage imageNamed:@"Xcode.png"];

self.xcodeImageView = [[UIImageView alloc]

initWithImage: xcodeImage];

/* Просто задаем размеры, чтобы изображение уменьшилось. */

[self.xcodeImageView setFrame: CGRectMake(0.0f,

0.0f,

100.0f,

100.0f)];

self.view.backgroundColor = [UIColor whiteColor];

[self.view addSubview: self.xcodeImageView];

}

4. На рис. 17.33 показано, как будет выглядеть вид, когда программа запускается в симуляторе iOS.

Рис. 17.33. Добавление вида с изображением в объект-вид

5. Теперь, когда вид появится на экране в методе экземпляра viewDidAppear: контроллера вида, приступим к исполнению анимационного блока, относящегося к виду с изображением. Эта анимация переместит изображение из исходной точки (в левом верхнем углу) в нижний правый угол. Кроме того, мы убедимся, что анимация произойдет за пятисекундный период:

— (void) viewDidAppear:(BOOL)paramAnimated{

[super viewDidAppear: paramAnimated];

/* Начинаем с верхнего левого угла. */

[self.xcodeImageView setFrame: CGRectMake(0.0f,

0.0f,

100.0f,

100.0f)];

[UIView beginAnimations:@"xcodeImageViewAnimation"

context:(__bridge void *)self.xcodeImageView];

/* Пятисекундная анимация. */

[UIView setAnimationDuration:5.0f];

/* Получаем делегаты анимации. */

[UIView setAnimationDelegate: self];

[UIView setAnimationDidStopSelector:

@selector(imageViewDidStop: finished: context:)];

/* Анимация заканчивается в нижнем правом углу. */

[self.xcodeImageView setFrame: CGRectMake(200.0f,

350.0f,

100.0f,

100.0f)];

[UIView commitAnimations];

}

6. Далее выполните реализацию метода делегата imageViewDidStop: finished: context: для контроллера вида, чтобы он вызывался UIKit по завершении анимации. Это не обязательно, так что для примера я просто зарегистрирую несколько сообщений, демонстрирующих, что метод действительно был вызван. В следующих примерах будет показано, как можно использовать метод для запуска какой-то иной активности в момент окончания анимации:

— (void)imageViewDidStop:(NSString *)paramAnimationID

finished:(NSNumber *)paramFinished

context:(void *)paramContext{

NSLog(@"Animation finished.");

NSLog(@"Animation ID = %@", paramAnimationID);

UIImageView *contextImageView = (__bridge UIImageView *)paramContext;

NSLog(@"Image View = %@", contextImageView);

}

Теперь, запустив приложение, вы заметите, что, как только отобразится вид, изображение, показанное на рис. 17.33, начнет перемещаться в нижний правый угол (рис. 17.34). На это уйдет 5 секунд.

Рис. 17.34. Анимируемое изображение переходит в правый нижний угол экрана

Кроме того, обратив внимание на консоль и дождавшись окончания анимации, вы увидите примерно следующий текст:

Animation finished.

Animation ID = xcodeImageViewAnimation

Image View = 

frame = (220 468; 100 100); opaque = NO;

userInteractionEnabled = NO;

layer = >

А теперь рассмотрим конкретные концепции и разберемся, как именно мы анимировали этот вид с изображением. Далее перечислены важные методы класса, относящиеся к UIView, о которых нужно знать, занимаясь анимацией с UIKit.

• beginAnimations: context: — запускает анимационный блок. Любое анимируемое изменение свойств, которое вы применяете к видам после вызова этого метода класса, будет вступать в силу после выполнения анимации.

• setAnimationDuration: — этот метод задает длительность анимации в секундах.

• setAnimationDelegate: — задает объект, который будет получать сообщения делегатов, касающиеся различных событий, которые могли произойти до, во время или после анимации. Если мы задаем объект делегата, это не означает, что анимационные делегаты немедленно запускаются. Кроме того, вы должны использовать различные методы-установщики, относящиеся к классу, применяя их к объекту вида. Так вы сообщаете UIKit, какие селекторы в вашем объекте-делегате какие делегатные сообщения должны получать.

• setAnimationDidStopSelector: — задает в объекте-делегате метод, который должен быть вызван после завершения анимации. Этот метод должен принимать три параметра в следующем порядке:

1) идентификатор анимации типа NSString: здесь будет содержаться идентификатор анимации, передаваемый с началом анимации методу класса beginAnimations: context:, относящемуся к классу UIView;

2) индикатор «завершения» типа NSNumber: этот параметр содержит в NSNumber логическое значение. Среда времени исполнения устанавливает его в YES, если анимация была остановлена в коде, не успев полностью завершиться. Если это значение равно NO, то это означает, что анимация была без перерывов воспроизведена до самого конца;

3) контекст типа void *: это контекст, который с началом анимации передается методу класса beginAnimations: context:, относящемуся к классу UIView.

• setAnimationWillStartSelector: — задает селектор, который должен быть вызван в объекте делегата перед самым началом анимации. Селектор, передаваемый этому методу класса, должен иметь два параметра в таком порядке:

1) идентификатор анимации типа NSString: среда времени исполнения задает для этого параметра значение идентификатора анимации, передаваемого с началом анимации методу класса beginAnimations: context:, относящемуся к классу UIView;

2) контекст типа void *: это контекст, который с началом анимации был передан методу класса beginAnimations: context:, относящемуся к классу UIView.

• setAnimationDelay: — задает задержку для анимации (в секундах) перед ее началом. Например, если это значение установлено в 3.0f, то анимация будет начинаться через 3 секунды после выполнения этого метода.

• setAnimationRepeatCount: — указывает количество прогонов анимации, которые должны быть выполнены в блоке кода.

Теперь, когда нам известны наиболее полезные методы класса UIView, помогающие анимировать виды, рассмотрим другую анимацию. В этом примере кода я создам два вида с изображениями (в каждом из них будет показано одно и то же изображение), и они появятся на экране в одно и то же время, одно в левом верхнем углу, другое — в правом нижнем (рис. 17.35).

Рис. 17.35. Исходное положение, с которого начинается анимация

В этом примере изображение из верхнего левого угла будет называться image 1, а из правого нижнего — image 2.

Как уже упоминалось, в этом коде мы собираемся создать два изображения, в верхнем левом и правом нижнем углах. Далее image 1 станет двигаться по направлению к image 2 и будет так перемещаться на протяжении 3 секунд, а потом медленно исчезнет. Когда image 1 начнет движение, станет двигаться и image 2 — оно пойдет в верхний левый угол экрана, где изначально находилось изображение image 1. Опять же мы хотим, чтобы анимация изображения image 2 завершилась за 3 секунды и оно медленно исчезло. Когда вы запустите этот код на устройстве или симуляторе iOS, такая анимация будет выглядеть очень классно. Теперь расскажу, как все это запрограммировать.

1. В верхней части. m-файла нашего контроллера вида определим два вида с изображениями:

@interface ViewController ()

@property (nonatomic, strong) UIImageView *xcodeImageView1;

@property (nonatomic, strong) UIImageView *xcodeImageView2;

@end

@implementation ViewController

2. В методе экземпляра viewDidLoad, относящемся к контроллеру вашего вида, инициализируем оба этих вида с изображениями и помещаем их в основной вид:

— (CGRect) bottomRightRect{

CGRect endRect;

endRect.origin.x = self.view.bounds.size.width — 100;

endRect.origin.y = self.view.bounds.size.height — 100;

endRect.size = CGSizeMake(100.0f, 100.0f);

return endRect;

}

— (void) viewDidLoad{

[super viewDidLoad];

UIImage *xcodeImage = [UIImage imageNamed:@"Xcode.png"];

self.xcodeImageView1 = [[UIImageView alloc]

initWithImage: xcodeImage];

self.xcodeImageView2 = [[UIImageView alloc]

initWithImage: xcodeImage];

/* Просто задаем размеры так, чтобы изображения уменьшились. */

[xcodeImageView1 setFrame: CGRectMake(0.0f,

0.0f,

100.0f,

100.0f)];

[self.xcodeImageView2 setFrame: [self bottomRightRect]];

self.view.backgroundColor = [UIColor whiteColor];

[self.view addSubview: self.xcodeImageView1];

[self.view addSubview: self.xcodeImageView2];

}

3. Реализуем для нашего контроллера вида метод экземпляра, который называется startTopLeftImageViewAnimation. Как понятно из названия, данный метод будет выполнять анимацию для изображения image 1, перемещая его из верхнего левого угла экрана в нижний правый, а изображение тем временем будет медленно исчезать. Такое исчезновение достигается установкой альфа-значения в 0:

— (void) startTopLeftImageViewAnimation{

/* Начинаем с верхнего левого угла. */

[self.xcodeImageView1 setFrame: CGRectMake(0.0f,

0.0f,

100.0f,

100.0f)];

[self.xcodeImageView1 setAlpha:1.0f];

[UIView beginAnimations:@"xcodeImageView1Animation"

context:(__bridge void *)self.xcodeImageView1];

/* Трехсекундная анимация */

[UIView setAnimationDuration:3.0f];

/* Получаем анимационные делегаты. */

[UIView setAnimationDelegate: self];

[UIView setAnimationDidStopSelector:

@selector(imageViewDidStop: finished: context:)];

/* Заканчиваем в нижнем правом углу. */

[self.xcodeImageView1 setFrame: CGRectMake(220.0f,

350.0f,

100.0f,

100.0f)];

[self.xcodeImageView1 setAlpha:0.0f];

[UIView commitAnimations];

}

4. Когда анимация какого-либо из этих видов остановится, мы удалим данный вид из иерархии родительских видов, так как больше в нем не нуждаемся. Как было показано в методе startTopLeftImageViewAnimation, мы передали селектор делегата методу класса setAnimationDidStopSelector:, относящемуся к классу UIView. Этот селектор будет вызываться после окончания анимации image 1 (как было показано ранее) и image 2 (как мы вскоре увидим). Вот реализация этого селектора делегата:

— (void)imageViewDidStop:(NSString *)paramAnimationID

finished:(NSNumber *)paramFinished

context:(void *)paramContext{

UIImageView *contextImageView = (__bridge UIImageView *)paramContext;

[contextImageView removeFromSuperview];

}

5. Кроме того, нам понадобится метод для анимирования image 2. Между написанием анимационных методов для image 2 и image 1 есть небольшая разница. Я хочу начать анимацию image 2, немного не дожидаясь завершения анимации image 1. Следовательно, если анимация image 1 завершается за 3 секунды, то я начну анимировать image 2 со второй секунды анимации image 1. Таким образом, анимация image 2 начнется еще до того, как изображение image 1 дойдет до нижнего правого угла экрана и исчезнет. Чтобы достичь такого результата, я установлю начало анимации для обоих изображений на одно и то же время, но перед началом анимации image 2 поставлю двухсекундную задержку. Итак, если обе анимации начнутся в час дня, то для изображения image 1 начальным моментом анимации будет 13:00:00, а конечным — 13:00:03. Соответствующие значения image 2 будут равны 13:00:02 и 13:00:05. Вот как будет происходить анимация image 2:

— (void) startBottomRightViewAnimationAfterDelay:(CGFloat)paramDelay{

/* Начинаем с нижнего правого угла. */

[self.xcodeImageView2 setFrame: [self bottomRightRect]];

[self.xcodeImageView2 setAlpha:1.0f];

[UIView beginAnimations:@"xcodeImageView2Animation"

context:(__bridge void *)self.xcodeImageView2];

/* Трехсекундная анимация */

[UIView setAnimationDuration:3.0f];

[UIView setAnimationDelay: paramDelay];

/* Получаем анимационные делегаты. */

[UIView setAnimationDelegate: self];

[UIView setAnimationDidStopSelector:

@selector(imageViewDidStop: finished: context:)];

/* Заканчиваем в верхнем левом углу. */

[self.xcodeImageView2 setFrame: CGRectMake(0.0f,

0.0f,

100.0f,

100.0f)];

[self.xcodeImageView2 setAlpha:0.0f];

[UIView commitAnimations];

}

6. И последнее, но немаловажное замечание. Как только вид отобразится, мы должны запустить методы startTopLeftImageViewAnimation и startBottomRightViewAnimationAfterDelay::

— (void) viewDidAppear:(BOOL)paramAnimated{

[super viewDidAppear: paramAnimated];

[self startTopLeftImageViewAnimation];

[self startBottomRightViewAnimationAfterDelay:2.0f];

}

 

17.15. Анимирование и масштабирование видов

 

Постановка задачи

Требуется возможность анимировать виды и масштабировать их в сторону увеличения или уменьшения.

 

Решение

Создайте для вида аффинное преобразование и используйте анимационные методы UIView для сопровождения масштабирования анимацией.

 

Обсуждение

Перед дальнейшей работой настоятельно рекомендую перечитать раздел 17.14.

Чтобы масштабировать вид, анимируя его при этом, можно либо применить к виду преобразование масштабирования в анимационном блоке (см. раздел 17.12), либо просто увеличить высоту и/или ширину вида.

Рассмотрим, как изменять масштаб вида, применяя к нему преобразование масштабирования:

— (void) viewDidAppear:(BOOL)paramAnimated{

[super viewDidAppear: paramAnimated];

/* Помещаем вид с изображением в центре основного вида данного

контроллера вида. */

self.xcodeImageView.center = self.view.center;

/* Убеждаемся, что к этому виду с изображением не применяется никакого

преобразования сдвига. */

self.xcodeImageView.transform = CGAffineTransformIdentity;

/* Начинаем анимацию. */

[UIView beginAnimations: nil

context: NULL];

/* Анимация продлится 5 секунд. */

[UIView setAnimationDuration:5.0f];

/* Вдвое увеличиваем вид с изображением в ширину и в длину. */

self.xcodeImageView.transform = CGAffineTransformMakeScale(2.0f,

2.0f);

/* Выполняем анимацию. */

[UIView commitAnimations];

}

В этом коде используется аффинное преобразование масштабирования, в результате которого вид с изображением становится в два раза больше по сравнению с исходными размерами. Самое большое достоинство такой операции заключается в том, что в ходе масштабирования начало координат (центр) при увеличении или уменьшении совпадает с началом координат (центром) самого вида. Предположим, что центр вашего вида расположен на экране в точке с координатами (100; 100), а вы хотите масштабировать вид, вдвое увеличив его ширину и высоту. В результате центр вида так и останется в точке (100; 100), в то время как сам вид увеличится в два раза. Если бы мы увеличивали вид, сначала специально добавив ему ширины, а потом высоты, то вид, который получился бы в итоге, находился бы немного не в той точке экрана, где был исходный вид. Это объясняется тем, что, изменяя высоту и ширину рамок вида, вы одновременно изменяете значения x и y контура вида, хотите вы того или нет. Поэтому вид с изображением не будет масштабироваться относительно своего центра. Исправление такой проблемы выходит за рамки этой книги, но вы можете самостоятельно разобраться с этой задачей — может быть, вам удастся найти решение. Дам одну подсказку: можно параллельно запустить две анимации. Одна из них будет изменять длину и ширину вида, а другая — перемещать центр вида.

 

См. также

Разделы 17.12 и 17.14.

 

17.16. Анимирование и вращение видов

 

Постановка задачи

Требуется анимировать виды на экране при вращении.

 

Решение

Создайте аффинное преобразование вращения, для анимирования вращения пользуйтесь методами класса UIView.

Перед дальнейшей работой настоятельно рекомендую перечитать раздел 17.14.

Чтобы вращать вид, анимируя его при этом, нужно применить к нему преобразование вращения в то время, как в коде выполняется анимационный блок (см. раздел 17.12). Рассмотрим пример кода, который прояснит это. Допустим, у нас есть рисунок Xcode.png (см. рис. 17.9) и мы хотим отобразить его в центре экрана. После того как картинка появится на экране, мы повернем ее на 90° за 5 секунд, а потом повернем обратно, поставив в исходное положение. Итак, когда вид с изображением появится на экране, повернем этот вид на 90° по часовой стрелке:

— (void) viewDidAppear:(BOOL)paramAnimated{

[super viewDidAppear: paramAnimated];

self.xcodeImageView.center = self.view.center;

/* Начинаем анимацию. */

[UIView beginAnimations:@"clockwiseAnimation"

context: NULL];

/* Анимация будет длиться 5 секунд. */

[UIView setAnimationDuration:5.0f];

[UIView setAnimationDelegate: self];

[UIView setAnimationDidStopSelector:

@selector(clockwiseRotationStopped: finished: context:)];

/* Поворачиваем вид с изображением на 90°. */

self.xcodeImageView.transform =

CGAffineTransformMakeRotation((90.0f * M_PI) / 180.0f);

/* Выполняем анимацию. */

[UIView commitAnimations];

}

Мы решили, что селектор clockwiseRotationStopped: finished: context: должен вызываться в тот момент, когда заканчивается анимация вращения по часовой стрелке. В этом методе мы будем вращать вид с изображением против часовой стрелки, обратно в положение, соответствующее 0° (то есть исходное). На это тоже уйдет 5 секунд.

— (void)clockwiseRotationStopped:(NSString *)paramAnimationID

finished:(NSNumber *)paramFinished

context:(void *)paramContext{

[UIView beginAnimations:@"counterclockwiseAnimation"

context: NULL];

/* 5 секунд */

[UIView setAnimationDuration:5.0f];

/* Возврат в исходное положение */

self.xcodeImageView.transform = CGAffineTransformIdentity;

[UIView commitAnimations];

}

Как было показано в разделах 17.14 и 17.15, а также в этом разделе, существует много способов анимировать виды (прямые или непрямые подклассы UIView). При выполнении анимации можно изменять немало свойств. Будьте креативны и экспериментируйте с другими свойствами UIView, о которых раньше, возможно, не знали. Не помешает также еще раз пересмотреть документацию по UIView в органайзере Xcode.

 

См. также

Разделы 17.13–17.15.

 

17.17. Получение изображения со скриншотом вида

 

Постановка задачи

Требуется сохранить содержимое объекта-вида, находящегося в вашем приложении, в виде изображения. Возможно, также потребуется сохранить это изображение на диске и выполнить с ним другое действие — например, позволить пользователю поделиться этой картинкой в любимой социальной сети (см. раздел 11.11).

 

Решение

Выполните следующие шаги.

1. Воспользуйтесь функцией UIGraphicsBeginImageContextWithOptions для создания нового контекста изображения. Этот контекст сразу станет текущим (актуальным), и именно в нем будет происходить все последующее рисование.

2. Вызовите метод drawViewHierarchyInRect: вашего класса UIView. В качестве параметра передайте этому методу границы вида, который вы хотите отрисовать в текущем контексте.

3. Вызовите метод UIGraphicsGetImageFromCurrentImageContext, возвращаемое значение которого — это представление текущего контекста в качестве изображения. Это изображение будет относиться к типу UIImage.

4. Преобразуйте ваш экземпляр изображения в данные, воспользовавшись функцией UIImagePNGRepresentation. Эта функция даст вам объект типа NSData.

5. Наконец, вызовите в вашем объекте данных метод экземпляра writeToUrl: atomically:, чтобы записать изображение на определенный адрес на диске — если хотите. Имея экземпляр UIImage, можете выполнить с этим изображением и любую другую операцию.

 

Обсуждение

Иногда разработчику требуется программно делать скриншот содержимого, которое отображается на экране устройства. В частности, это может понадобиться, если вы пишете приложение для рисования и хотите предоставить пользователю возможность сохранить сделанный рисунок в файле. Возможно, этот файл затем будет сохранен в облаке iCloud, откуда его можно будет впоследствии загрузить.

Перед тем как сохранить изображение или поделиться им таким образом, мы должны нарисовать его в контексте изображения. Контекст изображения остается для нас невидимым, так как мы даже не имеем его описателя. Тем не менее все рисовальные методы, которые вы вызываете, будут оказывать влияние на текущий контекст изображения. Контекст изображения можно сравнить с невидимым холстом для рисования. Чтобы получить представление вашего изображения, воспользуйтесь функцией UIGraphicsGetImageFromCurrentImageContext.

Когда вы начнете решать такую задачу с помощью нового SDK, вам всего лишь потребуется вызвать в виде метод drawViewHierarchyInRect: — и содержимое этого вида будет отрисовано в текущем контексте.

Итак, применим изученный материал на практике. В следующем фрагменте кода мы собираемся разместить в нашем виде ряд компонентов (при этом используются раскадровки, описанные в главе 6). Не важно, что именно вы поместите в раскадровке. Мы хотим снять содержимое нашего вида, сохранить эту информацию как изображение, а затем поместить это изображение в каталог Documents (Документы) на диске:

— (void) viewDidAppear:(BOOL)animated{

[super viewDidAppear: animated];

/* Делаем скриншот */

UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, YES, 0.0f);

if ([self.view drawViewHierarchyInRect: self.view.bounds]){

NSLog(@"Successfully draw the screenshot.");

} else {

NSLog(@"Failed to draw the screenshot.");

}

UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

/* Сохраняем его на диске */

NSFileManager *fileManager = [[NSFileManager alloc] init];

NSURL *documentsFolder = [fileManager URLForDirectory: NSDocumentDirectory

inDomain: NSUserDomainMask

appropriateForURL: nil

create: YES

error: nil];

NSURL *screenshotUrl = [documentsFolder

URLByAppendingPathComponent:@"screenshot.png"];

NSData *screenshotData = UIImagePNGRepresentation(screenshot);

if ([screenshotData writeToURL: screenshotUrl atomically: YES]){

NSLog(@"Successfully saved screenshot to %@", screenshotUrl);

} else {

NSLog(@"Failed to save screenshot.");

}

}

В начале этого кода мы создаем новый контекст изображения и получаем его представление в виде изображения с помощью UIGraphicsGetImageFromCurrentImageContext. Имея это представление, мы воспользуемся NSFileManager, чтобы найти путь к каталогу Documents (Документы) нашего приложения, который находится на диске (см. раздел 12.1). Наконец, мы получаем представление скриншота в виде данных (с помощью функции UIImagePNGRepresentation) и после этого можем сохранить данное представление на диске. Мы должны получить представление изображения в формате PNG или JPEG, воспользовавшись для этого функцией UIImageJPEGRepresentation. Так мы получим данные, соответствующие изображению в этом формате (PNG/JPEG). Имея данные, мы можем сохранить их на диске или выполнить с ними другие операции.

 

См. также

Раздел 11.11, глава 6.