iOS. Приемы программирования Нахавандипур Вандад
NSLog(@"Failed to load the i.");
}
}
Если в пакете вашего приложения есть изображение Xcode.png, то после запуска этого кода на консоли появится надпись Successfully loaded the i (Изображение успешно загружено). Если изображения нет — будет написано Failed to load the i (Не удалось загрузить изображение). В оставшейся части данного раздела предполагается, что у вас в пакете приложения есть нужное изображение. Можете смело помещать в пакет приложения и другие картинки, а потом ставить ссылки именно на них, а не на Xcode.png, которым я буду пользоваться в примерах кода.
Два самых простых способа отрисовки изображения типа UIImage в графическом контексте таковы:
• воспользоваться методом экземпляра drawAtPoint:, относящимся к классу UIImage. Таким образом в указанной точке отрисовывается изображение оригинального размера. Для создания этой точки используется функция CGPointMake;
• воспользоваться методом экземпляра drawInRect:, относящимся к классу UIImage. Изображение отрисовывается в заданной прямоугольной области. Для создания этой прямоугольной области используется функция CGRectMake:
— (void)drawRect:(CGRect)rect{
/* Предполагается, что нужное изображение есть в пакете вашего приложения
и его можно загрузить. */
UIImage *xcodeIcon = [UIImage iNamed:@"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 is в терминологии 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.h2Label.font = [UIFont systemFontOfSize:15.0f];
/* Создаем адаптивное изображение */
UIImage *i = [UIImage iNamed:@"Button"];
UIEdgeInsets edgeInsets;
edgeInsets.left = 14.0f;
edgeInsets.top = 0.0f;
edgeInsets.right = 14.0f;
edgeInsets.bottom = 0.0f;
i = [i resizableImageWithCapInsets: edgeInsets];
/* Задаем фоновое изображение для кнопки */
[self.button setBackgroundImage: i 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);