iOS: получить CGMutablePath не в drawRect: и назначить его пути CAShapeLayer
Я бился головой об этом часами. Я пытался читать и искал ответы. Я даже попробовал подход "уходи и возвращайся к нему позже", он не сработал. Я даже пробовал йогу и медитировал! Я не победил..
Поэтому, пожалуйста, просветите меня, эксперты.
Все, что я пытаюсь получить копию CGPath не внутри drawRect:
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, ......);
CGPathAddLineToPoint(path, ....);
CGPathAddLineToPoint(path, ....);
CGPathAddLineToPoint(path, ....);
CGPathAddLineToPoint(path, ....);
И наконец:
CGPathCloseSubpath(path);
Это не сработало, поэтому я попытался также использовать вместо 2 вызовов выше:
CGContextAddPath(context, path);
CGPathCreateMutableCopy(path);
И в конце концов - я просто хочу иметь возможность получить этот путь и вставить его как путь в CAShapeLayer и анимировать его, выполнив этот вызов:
animation.toValue = [UIBezierPath bezierPathWithCGPath:path];
Поэтому, прежде чем я попытаюсь заняться другим расслабляющим занятием, таким как "Небесный дайвинг" без парашюта, в надежде очистить свой разум и, наконец, сделать это правильно, не могли бы вы помочь мне здесь, показав мне несколько примеров? Кроме того, можете ли вы подробно объяснить мне, как правильно использовать следующие функции:
CGPathCloseSubpath(path);
CGContextAddPath(context, path);
CGPathCreateMutableCopy(path);
Заранее спасибо.
1 ответ
CGPath - это структура данных в стиле C с API в стиле C. В основном это список точек, которые составляют путь.
CGContext - это также структура данных в стиле C (возможно, немного больше) для рисования изображений, то есть преобразования точек в цветные пиксели.
Я поместил свои фрагменты в функцию, которую вы можете просто скопировать и вставить в один экранный проект xcode. Вы найдете объяснения в виде комментариев в коде и две функции для отладки и глубокого изучения объектов CGPath (которые я взял из https://github.com/erica/iOS-6-Cookbook с некоторыми дополнениями).
#import "ViewController.h"
#import "OverlayView.h"
#import <QuartzCore/QuartzCore.h>
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// create a graphics context with the C-API of the QuartzCore framework
// the graphics context is only required for drawing the path
CGRect compositionBounds = CGRectMake(30, 30, 300, 508);
UIGraphicsBeginImageContext(compositionBounds.size);
CGContextRef ctx = UIGraphicsGetCurrentContext();
// create a mutable path with QuartzCore
CGMutablePathRef p1 = CGPathCreateMutable();
// create a CGPath with UIKit classes using Obj-C
UIBezierPath *bp = [UIBezierPath bezierPath];
[bp moveToPoint:(CGPoint){180, 120}];
[bp addLineToPoint:(CGPoint){30, 170}];
[bp addLineToPoint:(CGPoint){135, 175}];
[bp addLineToPoint:(CGPoint){105, 115}];
// if you fill the path during drawing it gets closed implicitly
// [bp closePath];
// add the CGPath to our mutable path
CGPathAddPath(p1, nil, bp.CGPath);
// add a closed triangle as subpath to our mutable path
CGPathMoveToPoint(p1, nil, 50, 250);
CGPoint p2[3];
p2[0] = CGPointMake(10, 20);
p2[1] = (CGPoint){210, 50};
p2[2] = (CGPoint){80, 120};
CGPathAddLines(p1, nil, p2, 3);
// explicitly close our last added subpath
CGPathCloseSubpath(p1);
// draw our mutable path (with all its subpaths) filling its area
CGContextAddPath(ctx, p1);
CGContextSetFillColorWithColor(ctx, [[UIColor redColor] CGColor]);
CGContextFillPath(ctx);
// draw our mutable path (with all its subpaths) stroking its outline
CGContextAddPath(ctx, p1);
CGContextSetRGBStrokeColor(ctx, 0, 0, 0, 1.0);
CGContextStrokePath(ctx);
// display our mutable path in an image view on screen
UIImage *i = UIGraphicsGetImageFromCurrentImageContext();
UIImageView *iv = [[UIImageView alloc] initWithImage:i];
[self.view addSubview:iv];
iv.frame = self.view.frame;
// release ressources created with the C-API of QuartzCore
CGPathRelease(p1);
UIGraphicsEndImageContext();
// create a mutable copy of a path and don't use it for drawing
CGMutablePathRef p3 = CGPathCreateMutableCopy(bp.CGPath);
NSArray *p3points = [self pointsFromCGPath:p3];
for (NSValue *point in p3points) {
NSLog(@"path element in p3: %@", NSStringFromCGPoint(point.CGPointValue));
}
// note that UIBezierPath takes care of its path itself
CGPathRelease(p3);
}
// modified from https://github.com/erica/iOS-6-Cookbook
#define VALUE(_INDEX_) [NSValue valueWithCGPoint:points[_INDEX_]]
void getPointsFromBezier(void *info, const CGPathElement *element)
{
NSMutableArray *bezierPoints = (__bridge NSMutableArray *)info;
// Retrieve the path element type and its points
CGPathElementType type = element->type;
CGPoint *points = element->points;
switch (type) {
case kCGPathElementMoveToPoint:
NSLog(@"MoveToPoint (%3.2f, %3.2f", points->x, points->y);
break;
case kCGPathElementAddLineToPoint:
NSLog(@"AddLineToPoint (%3.2f, %3.2f)", points->x, points->y);
break;
case kCGPathElementAddQuadCurveToPoint:
NSLog(@"AddQuadCurveToPoint (%3.2f, %3.2f), (%3.2f, %3.2f)", points->x, points->y, points[1].x, points[1].y);
break;
case kCGPathElementAddCurveToPoint:
NSLog(@"AddCurveToPoint (%3.2f, %3.2f), (%3.2f, %3.2f), (%3.2f, %3.2f)", points->x, points->y, points[1].x, points[1].y, points[2].x, points[2].y);
break;
case kCGPathElementCloseSubpath:
NSLog(@"CloseSubpath (%3.2f, %3.2f)", points->x, points->y);
break;
default:
NSLog(@"unknown");
break;
}
// Add the points if they're available (per type)
if (type != kCGPathElementCloseSubpath)
{
[bezierPoints addObject:VALUE(0)];
if ((type != kCGPathElementAddLineToPoint) &&
(type != kCGPathElementMoveToPoint))
[bezierPoints addObject:VALUE(1)];
}
if (type == kCGPathElementAddCurveToPoint)
[bezierPoints addObject:VALUE(2)];
}
- (NSArray *)pointsFromCGPath:(CGPathRef)path
{
NSMutableArray *points = [NSMutableArray array];
CGPathApply(path, (__bridge void *)points, getPointsFromBezier);
return points;
}