本文介绍了touchesMoved绘图在CAShapeLayer慢/滞后的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

正如在之前的StackOverflow问题中向我建议的那样,我正在尝试改进我的绘图方法,让我的用户将线条/点绘制成UIView。我现在正在尝试使用CAShapeLayer而不是dispatch_async进行绘制。这一切都正常,但是,当触摸移动变得缓慢而且路径滞后时,不断地进入CAShapeLayer,而我的旧(效率低下我被告知)代码工作得非常流畅和快速。您可以在下面看到我的旧代码。

As was suggested to me in a prior StackOverflow question, I'm trying to improve my drawing method for letting my user draw lines/dots into a UIView. I'm now trying to draw using a CAShapeLayer instead of dispatch_async. This all works correctly, however, drawing into the CAShapeLayer continuously while touches are moving becomes slow and the path lags behind, whereas my old (inefficient I was told) code worked beautifully smooth and fast. You can see my old code commented out below.

有什么方法可以改善我想要做的表现吗?也许我正在过度思考。

Is there any way to improve the performance for what I want to do? Maybe I'm overthinking something.

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

I'd appreciate any help offered.

代码:

@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相对较快。

This problem intrigued me as I've always found UIBezierPath/shapeLayer to be reletivly fast.

请注意,在上面的代码中,您继续为drawPath添加点。随着这种情况的增加,appendPath方法成为真正的资源负担。类似地,一遍又一遍地渲染相同的点是没有意义的。

It's important to note that in your code above, you continues to add points to drawPath. As this increases, the appendPath method becomes a real resource burden. Similarly, there is no point in successively rendering the same points over and over.

作为旁注,增加lineWidth和添加lineCap时会有明显的性能差异(无论如何)方法)。为了将苹果与苹果进行比较,在下面的测试中,我将两者都保留为默认值。

As a side note, there is a visible performance difference when increasing lineWidth and adding lineCap (regardless of approach). For the sake of comparing Apples with Apples, in the test below, I've left both to default values.

我拿了上面的代码并稍微改了一下。我使用的技术是在将当前渲染提交到图像之前,将触摸点添加到BezierPath,直到达到每个确定的数量。这与您的原始方法类似,但是,考虑到每个touchEvent都没有发生这种情况。它的CPU密集程度要低得多。我在最慢的设备(iPhone 4S)上测试了这两种方法,并注意到初始实现时的CPU利用率在绘制时始终保持在75-80%左右。使用修改后的/ CAShapeLayer方法,CPU利用率始终保持在10-15%左右。使用第二种方法,内存使用量仍然很小。

I took your above code and changed it a little. The technique I've used is to add touchPoints to the BezierPath up to a per-determined number, before committing the current rendering to image. This is similar to your original approach, however, given that it's not happening with every touchEvent. it's far less CPU intensive. I tested both approaches on the slowest device I have (iPhone 4S) and noted that CPU utilization on your initial implementation was consistently around 75-80% whilst drawing. Whilst with the modified/CAShapeLayer approach, CPU utilization was consistently around 10-15% Memory usage also remained minimal with the second approach.

以下是我使用的代码;

Below is the Code I used;

@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

这篇关于touchesMoved绘图在CAShapeLayer慢/滞后的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-12 08:11