iOS 开发简单的手绘应用

1,821 阅读3分钟

开发一款简单的 iOS 手绘应用,

收集点,绘制形状,给形状着色,呈现给用户,好像就完了

框架是 Quartz2D

1, 收集点

首先需要有一个界面 UIView, 用这个界面监听用户的手势,收集点

  • 用户按下手指

location(in, 从触摸事件中,获得在画板中的坐标


var lastPoint = CGPoint.zero


override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard let touch = touches.first else {
      return
    }
    // ...
    lastPoint = touch.location(in: view)
  }
  • 用户移动手指

 override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
 		//  ...
 }
  • 用户抬起手指


override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
 		// ...
 }
 

2, 绘制形状,给形状着色

开辟一块绘图上下文 UIGraphicsGetCurrentContext,

使用采集的点连线

用户手绘的不是一段连续的曲线,是很多个线段拼接起来的


func drawLine(from fromPoint: CGPoint, to toPoint: CGPoint) {
   UIGraphicsBeginImageContext(view.frame.size)
   guard let context = UIGraphicsGetCurrentContext() else {
     return
   }
   // ...
   // 绘制
   context.move(to: fromPoint)
   context.addLine(to: toPoint)
   
   context.setLineCap(.round)
   context.setBlendMode(.normal)
   context.setLineWidth(brushWidth)
   context.setStrokeColor(color.cgColor)
   
   context.strokePath()
   // ...
   
   UIGraphicsEndImageContext()
 }

3, 呈现给用户

第一步使用的 UIView 是 UIImageView,

  • 绘制就是画一小段,取出画好的图片,赋给 UIImageView,我们就看到了

  • 连续的绘制,是

画一小段,取出画好的图片,赋给 UIImageView,并用变量保存下最新的图片

接着画,先把刚才的图片变量绘制一遍,再画一小段,取出画好的图片,赋给 UIImageView,并用变量保存下最新的图片

func drawLine(from fromPoint: CGPoint, to toPoint: CGPoint) {
  UIGraphicsBeginImageContext(view.frame.size)
  guard let context = UIGraphicsGetCurrentContext() else {
    return
  }
  tempImageView.image?.draw(in: view.bounds)
  
  // 绘制 ...
  
  tempImageView.image = UIGraphicsGetImageFromCurrentImageContext()
  
  UIGraphicsEndImageContext()
}

4, 画笔设置

修改画笔颜色和粗细

func drawLine(from fromPoint: CGPoint, to toPoint: CGPoint) {
  UIGraphicsBeginImageContext(view.frame.size)
  guard let context = UIGraphicsGetCurrentContext() else {
    return
  }
  
  // ...
  
  context.setBlendMode(.normal)
  
  // 调颜色
  context.setLineWidth(brushWidth)
  // 调粗细
   context.setStrokeColor(color.cgColor)
  // ...
}

画笔变橡皮擦

  • 方法一,把画笔的颜色,调成画板的颜色,就成了橡皮擦

  • 方法 2 ,

把画笔的颜色,调成透明,

把绘图上下文的混色模式改掉

就成了橡皮擦

       switch type {
       case .pencil, .none:
           context.setBlendMode(.color)
       case .eraser:
           context.setLineWidth(15)
           context.setStrokeColor(UIColor.clear.cgColor)
           context.setBlendMode(.clear)
       }
   ```        
       
       
### 5,后续
 
#### 更多功能:
 
加入文本输入功能,

需要一个文本框控件 `UITextField`、 `UITextView`

文本框控件一般可以拖动,

文本框放在画布上,拖出画布了,有些问题。

这时候要做一个边界检测

#### 性能优化: 

##### 一般性能优化,就是打印函数的执行时间

当画布的大小为 `1366 X 7700` ( iPad Pro + UIScrollView ) 的时候,画布很大,

全部绘制一遍,并取出图片,性能消耗很大

tempImageView.image?.draw(in: view.bounds)
// ...

tempImageView.image = UIGraphicsGetImageFromCurrentImageContext()


绘制一次,需要约 0.07 秒,



        let t = Date()
        self.drawingImage()
        if #available(iOS 13.0, *) {
            let span = t.distance(to: Date())
            print(span)
        }
我们期望 60 的 FPS, 每一帧计算时间 0.016, **所以频繁调用该方法,卡得厉害**


***
 
 
之前的方法是一个点,一个点的绘制
 
移动一下,绘制一次
 

override func touchesMoved(_ touches: Set, with event: UIEvent?) { guard let touch = touches.first else { return } swiped = true let currentPoint = touch.location(in: view) drawLine(from: lastPoint, to: currentPoint)

lastPoint = currentPoint

}


画一段线,  `func touchesMoved()` ,一般可以触发 3060 次,收集的点比较多,线条柔和

此时频繁调用该消耗性能方法,

只能触发 6 次,画一段只能采集 6 个点,正常手速,就画出来一个多边形

***

##### 可以这样优化,点的收集与绘制分离

使用一个 Timer ,每隔 0.15 秒,绘制一次

原本收集点,是一个 CGPoint, 现在收集点,是一个 [CGPoint]

*  原本画一次之前的 image, 连一根线,更新图片变量并呈现 


n 个点, 来 n 次全画板绘制


*  现在画一次之前的 image, 连接多根线,更新图片变量并呈现


n 个点, 来 1 次全画板绘制


消耗性能的方法,少调用,就对了

tempImageView.image?.draw(in: view.bounds)
// ...

tempImageView.image = UIGraphicsGetImageFromCurrentImageContext()
  
  
### [前 4 点的代码,见 github ](https://github.com/coyingcat/SwiftDrawing)
  
  后续需要整理,tbd