iOS 畫板 塗鴉 答題

不覺夢迴發表於2017-12-13

在開始之前,首先感謝畫板(塗鴉)實現 - iOSiOS 畫板/塗鴉 你畫我猜 demo (OC版)的作者,在下從他們的的部落格中獲得了很多啟發。還要感謝外國友人提供的 曲線優化策略。

效果圖

首先從確定實現方案開始。也經歷了許多挫折。因為最開始我是想用 OpenGL 來實現的。。但是由於種種原因,最後選擇了 UIBezierPath 配合 CAShapeLayer 再加上 截圖合成來實現。下面來看具體思路。

獲取觸控的手勢,都是通過相同的回撥來獲取的


- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event 

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent*)event

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent*)event 

複製程式碼

然後就是處理使用者操作所獲的點,以進行下一步的繪製,此時需記錄下開始的點,然後在move 方法中進行下一步的操作,最後操作結束。


p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #000000}p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px 'PingFang SC'; color: #1e9421}p.p3 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #3e1e81}p.p4 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #294c50}p.p5 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #1e9421}p.p6 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #1337ff}p.p7 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Menlo; color: #000000; min-height: 16.0px}span.s1 {font-variant-ligatures: no-common-ligatures}span.s2 {font-variant-ligatures: no-common-ligatures; color: #539aa4}span.s3 {font-variant-ligatures: no-common-ligatures; color: #0435ff}span.s4 {font: 14.0px Menlo; font-variant-ligatures: no-common-ligatures; color: #000000}span.s5 {font: 14.0px Menlo; font-variant-ligatures: no-common-ligatures}span.s6 {font-variant-ligatures: no-common-ligatures; color: #c42275}span.s7 {font-variant-ligatures: no-common-ligatures; color: #31595d}span.s8 {font-variant-ligatures: no-common-ligatures; color: #78492a}span.s9 {font-variant-ligatures: no-common-ligatures; color: #000000}span.s10 {font-variant-ligatures: no-common-ligatures; color: #294c50}span.s11 {font-variant-ligatures: no-common-ligatures; color: #1e9421}span.s12 {font: 14.0px 'PingFang SC'; font-variant-ligatures: no-common-ligatures; color: #1e9421}span.s13 {font-variant-ligatures: no-common-ligatures; color: #1337ff}span.s14 {font-variant-ligatures: no-common-ligatures; color: #3e1e81}span.s15 {font-variant-ligatures: no-common-ligatures; color: #703daa}span.s16 {font-variant-ligatures: no-common-ligatures; color: #6122ae}span.s17 {font-variant-ligatures: no-common-ligatures; color: #c81b13}

        ctr = 0;
        brush.endPoint = currentPoint;
        // 預設畫線,點選同一個點時,新增黑點
        if (brush.shapeType == LxShapeDefault && CGPointEqualToPoint(brush.beginPoint, brush.endPoint)) {
            [brush.bezierPath addArcWithCenter:currentPoint radius:self.lindWidth/4.0 startAngle:0 endAngle:2*M_PI clockwise:NO];
        } else if (brush.shapeType == LxShapeEraser) {
            [brush.bezierPath addArcWithCenter:currentPoint radius:self.lindWidth/4.0 startAngle:0 endAngle:2*M_PI clockwise:NO];
        }
        if (brush.shapeType == LxShapeEraser) {
            [self drawEraser:brush];
        } else {
            [self.canvas setBrush:brush];
        }
        [self storeCurrentImage];
    } else { // 移動
        if (brush.shapeType == LxShapeDefault || brush.shapeType == LxShapeEraser) {
//            CGPoint centerPoint = CGPointMake((currentPoint.x+self.lastPoint.x)*0.5, (currentPoint.y+self.lastPoint.y)*0.5);
//            [brush.bezierPath addQuadCurveToPoint:currentPoint controlPoint:centerPoint];
            // 此處優化策略,參考
            // https://code.tutsplus.com/tutorials/smooth-freehand-drawing-on-ios--mobile-13164
            ctr++;
            pts[ctr] = currentPoint;
            if (ctr == 4) {
                pts[3] = CGPointMake((pts[2].x + pts[4].x)/2.0, (pts[2].y + pts[4].y)/2.0); // move the endpoint to the middle of the line joining the second control point of the first Bezier segment and the first control point of the second Bezier segment
                
                [brush.bezierPath moveToPoint:pts[0]];
                [brush.bezierPath addCurveToPoint:pts[3] controlPoint1:pts[1] controlPoint2:pts[2]]; // add a cubic Bezier from pt[0] to pt[3], with control points pt[1] and pt[2]
                // replace points and get ready to handle the next segment
                pts[0] = pts[3]; 
                pts[1] = pts[4]; 
                ctr = 1;
            }
        } else if (brush.shapeType == LxShapeLine) {
            [brush.bezierPath removeAllPoints];
            [brush.bezierPath moveToPoint:brush.beginPoint];
            [brush.bezierPath addLineToPoint:currentPoint];
        } else if (brush.shapeType == LxShapeEllipse) {
            brush.bezierPath = [UIBezierPath bezierPathWithOvalInRect:[self getRectWithStartPoint:brush.beginPoint endPoint:currentPoint]];
        } else if (brush.shapeType == LxShapeRect) {
            brush.bezierPath = [UIBezierPath bezierPathWithRect:[self getRectWithStartPoint:brush.beginPoint endPoint:currentPoint]];
//        } else if (brush.shapeType == LxShapeEraser) {
            
        } else {
            NSLog(@"%ld", brush.shapeType);
        }
        if (brush.shapeType == LxShapeEraser) {
            [self drawEraser:brush];
        } else {
            [self.canvas setBrush:brush];
        }
        self.lastPoint = currentPoint;
    }
}
複製程式碼

其中繪製線段、橢圓、矩形的部分不再贅述。

關於撤銷和恢復的部分是基於每次操作後的截圖,把截圖非同步儲存到本地指定資料夾內,然後通過當前所在的索引切換,即可實現撤銷和恢復。

其中的錄屏(有音訊)功能使用 ReplayKit 來簡單實現。如果只是了無音訊的錄屏,可參考ASScreenRecorder.

Demo連結 , 覺得好的請點一個 Star。

相關文章