触摸移动在CAShapeLayer绘图慢/ laggy

正如在先前的StackOverflow问题中向我推荐的那样,我试图改进我的绘图方法,让我的用户在UIView中绘制线条/点。 我现在试图绘制使用CAShapeLayer而不是dispatch_async。 然而,这一切都正常工作,但是,在触摸移动的同时不断移动到CAShapeLayer中变得缓慢,path滞后,而我的旧的(效率低下的我被告知)代码工作得非常stream畅和快速。 你可以看到我的旧代码注释如下。

有什么办法来改善我想要做的事情吗? 也许我正在过度看待一些事情。

我会很感激提供的任何帮助。

码:

@property (nonatomic, assign) NSInteger center; @property (nonatomic, strong) CAShapeLayer *drawLayer; @property (nonatomic, strong) UIBezierPath *drawPath; @property (nonatomic, strong) UIView *drawView; @property (nonatomic, strong) UIImageView *drawingImageView; CGPoint points[4]; - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; self.center = 0; points[0] = [touch locationInView:self.drawView]; if (!self.drawLayer) { CAShapeLayer *layer = [CAShapeLayer layer]; layer.lineWidth = 3.0; layer.lineCap = kCALineCapRound; layer.strokeColor = self.inkColor.CGColor; layer.fillColor = [[UIColor clearColor] CGColor]; [self.drawView.layer addSublayer:layer]; self.drawView.layer.masksToBounds = YES; self.drawLayer = layer; } } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; self.center++; points[self.center] = [touch locationInView:self.drawView]; if (self.center == 3) { UIBezierPath *path = [UIBezierPath bezierPath]; points[2] = CGPointMake((points[1].x + points[3].x)/2.0, (points[1].y + points[3].y)/2.0); [path moveToPoint:points[0]]; [path addQuadCurveToPoint:points[2] controlPoint:points[1]]; points[0] = points[2]; points[1] = points[3]; self.center = 1; [self drawWithPath:path]; } } - (void)drawWithPath:(UIBezierPath *)path { if (!self.drawPath) { self.drawPath = [UIBezierPath bezierPath]; } [self.drawPath appendPath:path]; self.drawLayer.path = self.drawPath.CGPath; [self.drawLayer setNeedsDisplay]; // Below code worked faster and didn't lag behind at all really /* dispatch_async(dispatch_get_main_queue(), ^{ UIGraphicsBeginImageContextWithOptions(self.drawingImageView.bounds.size, NO, 0.0); [self.drawingImageView.image drawAtPoint:CGPointZero]; [self.inkColor setStroke]; [path stroke]; self.drawingImageView.image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); }); */ } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { if (self.center == 0) { UIBezierPath *path = [UIBezierPath bezierPath]; [path moveToPoint:points[0]]; [path addLineToPoint:points[0]]; [self drawWithPath:path]; } self.drawLayer = nil; self.drawPath = nil; } 

这个问题引起了我的兴趣,因为我总是发现UIBezierPath / shapeLayer相对较快。

需要注意的是,在上面的代码中,您将继续向drawPath添加点。 随着增加,appendPath方法成为一个真正的资源负担。 同样,连续不断地渲染相同的点也是没有意义的。

作为一个方面说明,当增加lineWidth和添加lineCap(不pipe方法如何)时,会有明显的性能差异。 为了比较苹果和苹果,在下面的testing中,我已经把它们都设置为默认值。

我把你的上面的代码,改变了一下。 我使用的技术是在将当前渲染提交到图像之前,将触点添加到贝塞尔path(BezierPath)达到每个确定的数字。 这和你原来的方法很相似,但是,这并不是每个touchEvent都会发生的。 它的CPU密度要小得多。 我在最慢的设备(iPhone 4S)上testing了两种方法,并注意到初始执行时的CPU利用率在绘制时一直在75-80%左右。 在修改/ CAShapeLayer方法的同时,CPU利用率一直保持在10-15%左右。第二种方法的内存使用率也很低。

以下是我使用的代码;

 @interface MDtestView () // a simple UIView Subclass @property (nonatomic, assign) NSInteger cPos; @property (nonatomic, strong) CAShapeLayer *drawLayer; @property (nonatomic, strong) UIBezierPath *drawPath; @property (nonatomic, strong) NSMutableArray *bezierPoints; @property (nonatomic, assign) NSInteger pointCount; @property (nonatomic, strong) UIImageView *drawingImageView; @end @implementation MDtestView CGPoint points[4]; - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { // Initialization code } return self; } -(id)initWithCoder:(NSCoder *)aDecoder{ self = [super initWithCoder:aDecoder]; if (self) { // } return self; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; self.cPos = 0; points[0] = [touch locationInView:self]; if (!self.drawLayer) { // this should be elsewhere but kept it here to follow your code self.drawLayer = [CAShapeLayer layer]; self.drawLayer.backgroundColor = [UIColor clearColor].CGColor; self.drawLayer.anchorPoint = CGPointZero; self.drawLayer.frame = self.frame; //self.drawLayer.lineWidth = 3.0; // self.drawLayer.lineCap = kCALineCapRound; self.drawLayer.strokeColor = [UIColor redColor].CGColor; self.drawLayer.fillColor = [[UIColor clearColor] CGColor]; [self.layer insertSublayer:self.drawLayer above:self.layer ]; self.drawingImageView = [UIImageView new]; self.drawingImageView.frame = self.frame; [self addSubview:self.drawingImageView]; } } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; if (!self.drawPath) { self.drawPath = [UIBezierPath bezierPath]; // self.drawPath.lineWidth = 3.0; // self.drawPath.lineCapStyle = kCGLineCapRound; } // grab the current time for testing Path creation and appending CFAbsoluteTime cTime = CFAbsoluteTimeGetCurrent(); self.cPos++; //points[self.cPos] = [touch locationInView:self.drawView]; points[self.cPos] = [touch locationInView:self]; if (self.cPos == 3) { /* uncomment this block to test old method UIBezierPath *path = [UIBezierPath bezierPath]; [path moveToPoint:points[0]]; points[2] = CGPointMake((points[1].x + points[3].x)/2.0, (points[1].y + points[3].y)/2.0); [path addQuadCurveToPoint:points[2] controlPoint:points[1]]; points[0] = points[2]; points[1] = points[3]; self.cPos = 1; dispatch_async(dispatch_get_main_queue(), ^{ UIGraphicsBeginImageContextWithOptions(self.drawingImageView.bounds.size, NO, 0.0); [self.drawingImageView.image drawAtPoint:CGPointZero]; // path.lineWidth = 3.0; // path.lineCapStyle = kCGLineCapRound; [[UIColor redColor] setStroke]; [path stroke]; self.drawingImageView.image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); NSLog(@"it took %.2fms to draw via dispatchAsync", 1000.0*(CFAbsoluteTimeGetCurrent() - cTime)); }); */ // I've kept the original structure in place, whilst comparing apples for apples. we really don't need to create // a new bezier path and append it. We can simply add the points to the global drawPath, and zero it at an // appropriate point. This would also eliviate the need for appendPath // /* [self.drawPath moveToPoint:points[0]]; points[2] = CGPointMake((points[1].x + points[3].x)/2.0, (points[1].y + points[3].y)/2.0); [self.drawPath addQuadCurveToPoint:points[2] controlPoint:points[1]]; points[0] = points[2]; points[1] = points[3]; self.cPos = 1; self.drawLayer.path = self.drawPath.CGPath; NSLog(@"it took %.2fms to render %i bezier points", 1000.0*(CFAbsoluteTimeGetCurrent() - cTime), self.pointCount); // 1 point for MoveToPoint, and 2 points for addQuadCurve self.pointCount += 3; if (self.pointCount > 100) { self.pointCount = 0; [self commitCurrentRendering]; } // */ } } - (void)commitCurrentRendering{ CFAbsoluteTime cTime = CFAbsoluteTimeGetCurrent(); @synchronized(self){ CGRect paintLayerBounds = self.drawLayer.frame; UIGraphicsBeginImageContextWithOptions(paintLayerBounds.size, NO, [[UIScreen mainScreen]scale]); CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetBlendMode(context, kCGBlendModeCopy); [self.layer renderInContext:context]; CGContextSetBlendMode(context, kCGBlendModeNormal); [self.drawLayer renderInContext:context]; UIImage *previousPaint = UIGraphicsGetImageFromCurrentImageContext(); self.layer.contents = (__bridge id)(previousPaint.CGImage); UIGraphicsEndImageContext(); [self.drawPath removeAllPoints]; } NSLog(@"it took %.2fms to save the context", 1000.0*(CFAbsoluteTimeGetCurrent() - cTime)); } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { if (self.cPos == 0) { /* //not needed UIBezierPath *path = [UIBezierPath bezierPath]; [path moveToPoint:points[0]]; [path addLineToPoint:points[0]]; [self drawWithPath:path]; */ } if (self.cPos == 2) { [self commitCurrentRendering]; } // self.drawLayer = nil; [self.drawPath removeAllPoints]; } @end