touchesMoved drawing in CAShapeLayer slow/laggy

后端 未结 1 1007
[愿得一人]
[愿得一人] 2020-12-18 07:15

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

相关标签:
1条回答
  • 2020-12-18 07:51

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

    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.

    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.

    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
    
    0 讨论(0)
提交回复
热议问题