ОБНОВИТЬ
Я опубликовал версию этого ответа для Swift отдельно.
ОРИГИНАЛ
Это небольшая забавная проблема. Во-первых, есть много способов рисовать стрелки с изогнутыми или прямыми сторонами. Давайте выберем очень простой способ и обозначим нужные нам измерения:
![части стрелок](https://i.stack.imgur.com/jfb4u.png)
Мы хотим написать функцию, которая берет начальную точку, конечную точку, ширину хвоста, ширину головы и длину головы и возвращает путь, очерчивающий форму стрелки. Давайте создадим категорию с именем 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:
![точки стрелки, выровненные по оси](https://i.stack.imgur.com/Sxopv.png)
Довольно легко вычислить координаты точки на стрелке, выровненной по оси, но для этого нам понадобится общая длина стрелки. Воспользуемся функцией 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
Я поместил весь код в суть для простого копирования и вставки.
С помощью этой категории вы можете легко нарисовать стрелки:
![образец стрелки 1](https://i.stack.imgur.com/WqbzL.png)
![образец стрелки 2](https://i.stack.imgur.com/ZsIWH.png)
Поскольку вы просто создаете путь, вы можете выбрать не заливать его или не обводить, как в этом примере:
![образец стрелки без штрихов](https://i.stack.imgur.com/2wFhx.png)
Однако нужно быть осторожным. Этот код не мешает вам получить забавные результаты, если вы сделаете ширину головы меньше, чем ширину хвоста, или если вы сделаете длину головы больше, чем общая длина стрелки:
![образец узкой головы](https://i.stack.imgur.com/OJ4QU.png)
![образец слишком длинной головы](https://i.stack.imgur.com/hHVfT.png)
person
rob mayoff
schedule
26.11.2012