АКТУАЛИЗИРАНЕ
Публикувах Swift версия на този отговор отделно.
ОРИГИНАЛНИ
Това е забавен малък проблем. Първо, има много начини за рисуване на стрели, с извити или прави страни. Нека изберем много прост начин и да обозначим измерванията, от които се нуждаем:
Искаме да напишем функция, която взема началната точка, крайната точка, ширината на опашката, ширината на главата и дължината на главата и връща път, очертаващ формата на стрелката. Нека създадем категория с име dqd_arrowhead
, за да добавим този метод към UIBezierPath
:
// UIBezierPath+dqd_arrowhead.h
@interface UIBezierPath (dqd_arrowhead)
+ (UIBezierPath *)dqd_bezierPathWithArrowFromPoint:(CGPoint)startPoint
toPoint:(CGPoint)endPoint
tailWidth:(CGFloat)tailWidth
headWidth:(CGFloat)headWidth
headLength:(CGFloat)headLength;
@end
Тъй като има седем ъгъла по пътя на стрелката, нека започнем нашата реализация, като наименуваме тази константа:
// UIBezierPath+dqd_arrowhead.m
#import "UIBezierPath+dqd_arrowhead.h"
#define kArrowPointCount 7
@implementation UIBezierPath (dqd_arrowhead)
+ (UIBezierPath *)dqd_bezierPathWithArrowFromPoint:(CGPoint)startPoint
toPoint:(CGPoint)endPoint
tailWidth:(CGFloat)tailWidth
headWidth:(CGFloat)headWidth
headLength:(CGFloat)headLength {
Добре, лесната част е готова. Сега, как да намерим координатите на тези седем точки по пътя? Много по-лесно е да намерите точките, ако стрелката е подравнена по оста X:
Доста лесно е да изчислим координатите на точката върху подравнена по оста стрелка, но ще ни трябва общата дължина на стрелката, за да го направим. Ще използваме функцията hypotf
от стандартната библиотека:
CGFloat length = hypotf(endPoint.x - startPoint.x, endPoint.y - startPoint.y);
Ще извикаме помощен метод, за да изчислим действително седемте точки:
CGPoint points[kArrowPointCount];
[self dqd_getAxisAlignedArrowPoints:points
forLength:length
tailWidth:tailWidth
headWidth:headWidth
headLength:headLength];
Но трябва да трансформираме тези точки, защото като цяло не се опитваме да създадем стрелка, подравнена по оста. За щастие, Core Graphics поддържа вид трансформация, наречена афинна трансформация, която ни позволява да завъртаме и транслираме (плъзгаме) точки. Ще извикаме друг помощен метод, за да създадем трансформацията, която превръща нашата подравнена по оста стрелка в стрелката, която ни беше поискана:
CGAffineTransform transform = [self dqd_transformForStartPoint:startPoint
endPoint:endPoint
length:length];
Сега можем да създадем път на Core Graphics, като използваме точките на подравнената по оста стрелка и трансформацията, която я превръща в стрелката, която искаме:
CGMutablePathRef cgPath = CGPathCreateMutable();
CGPathAddLines(cgPath, &transform, points, sizeof points / sizeof *points);
CGPathCloseSubpath(cgPath);
И накрая, можем да увием UIBezierPath
около CGPath
и да го върнем:
UIBezierPath *uiPath = [UIBezierPath bezierPathWithCGPath:cgPath];
CGPathRelease(cgPath);
return uiPath;
}
Ето помощния метод, който изчислява координатите на точката. Това е съвсем просто. Обърнете се към диаграмата на подравнената по оста стрелка, ако трябва.
+ (void)dqd_getAxisAlignedArrowPoints:(CGPoint[kArrowPointCount])points
forLength:(CGFloat)length
tailWidth:(CGFloat)tailWidth
headWidth:(CGFloat)headWidth
headLength:(CGFloat)headLength {
CGFloat tailLength = length - headLength;
points[0] = CGPointMake(0, tailWidth / 2);
points[1] = CGPointMake(tailLength, tailWidth / 2);
points[2] = CGPointMake(tailLength, headWidth / 2);
points[3] = CGPointMake(length, 0);
points[4] = CGPointMake(tailLength, -headWidth / 2);
points[5] = CGPointMake(tailLength, -tailWidth / 2);
points[6] = CGPointMake(0, -tailWidth / 2);
}
Изчисляването на афинната трансформация е по-сложно. Тук се намесва тригонометрията. Можете да използвате atan2
и функциите CGAffineTransformRotate
и CGAffineTransformTranslate
, за да я създадете, но ако помните достатъчно тригонометрия, можете да я създадете директно. Консултирайте се с “Математиката зад матриците” в Ръководството за програмиране на Quartz 2D за повече информация относно това, което правя тук:
+ (CGAffineTransform)dqd_transformForStartPoint:(CGPoint)startPoint
endPoint:(CGPoint)endPoint
length:(CGFloat)length {
CGFloat cosine = (endPoint.x - startPoint.x) / length;
CGFloat sine = (endPoint.y - startPoint.y) / length;
return (CGAffineTransform){ cosine, sine, -sine, cosine, startPoint.x, startPoint.y };
}
@end
Сложих целия код в същност за лесно копиране и поставяне.
С тази категория можете лесно да рисувате стрелки:
Тъй като просто генерирате път, можете да изберете да не го запълвате или да не го рисувате, както в този пример:
Все пак трябва да внимавате. Този код не ви пречи да получавате страхотни резултати, ако направите ширината на главата по-малка от ширината на опашката или ако направите дължината на главата по-голяма от общата дължина на стрелката:
person
rob mayoff
schedule
26.11.2012