本文介绍了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.

以下是我使用的代码;

@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 缓慢/滞后的绘图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-17 14:15