Swift Touchイベント処理でお絵かきアプリを作ってみました

Touchイベントの勉強をきっかけに[こちら](https://swift-ios.keicode.com/ios/drawing-touch-events.php)の記事を見て作ってみました。
記事に書かれている主機能の他に消しゴム、ペン/消しゴムのサイズの変更を追加しました。

![Simulator Screen Shot - iPhone 8 Plus - 2021-01-23 at 20.39.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/644086/f0297a3a-f571-5fe0-6d2f-1d5f1e656797.png)

ViewControllerはこのような構成

```swift
class ViewController: UIViewController {

    @IBOutlet weak var drawView: DrawView! ///お絵かきのカスタムView
    @IBOutlet weak var eraserSelected: UISegmentedControl!
    @IBOutlet weak var selectColor: UISegmentedControl!
    @IBOutlet weak var selectSize: UISegmentedControl!

    @IBAction func undoTapped(_ sender: Any) {
    }
    
    @IBAction func clearTapped(_ sender: Any) {
    }
 
    @IBAction func penOrEraser(_ sender: UISegmentedControl) {
    }
    
    @IBAction func penTapped(_ sender: UISegmentedControl) {
    }

    @IBAction func sizeSelected(_ sender: UISegmentedControl) {
    }
}
```

サイズの種類を定義するEnum

```swift
enum size {
    case small
    case middle
    case big
}
```

一描きの要素を含むDrawing

```swift
struct Drawing {
    var points = [CGPoint]()
    var color = UIColor.black
    var size:size? = .small
}
```

タッチイベントの処理、主機能の処理のソースコードが詰まっている
カスタムビューDrawViewはこのような構成

```swift
class DrawView: UIView{
    
    var finishedDrawings = [Drawing]()///完成したかきデータ
    var currentDrawing: Drawing?///進行中のかきデータ
    var currentColor = UIColor.black
    var currentSize: size?
    var eraserSelected = false
        
    override func draw(_ rect: CGRect) {
        strokeToLine()///完成したデータと進行中のデータのDrawingを一つずつ処理していく
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        ///タッチポイントを取得してcurrentDrawingへ入れる
        setNeedsDisplay()
        ///setNeedsDisplayによってdrawが自動的に呼び出されてタッチが処理されていく
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        ///同上
        setNeedsDisplay()
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        ///同上、currentDrawingを完成データへ入れる
        setNeedsDisplay()
    }
    
    func strokeToLine(drawing:Drawing){
        ///drawingのpointsを線で繋ぐ処理
        let path = UIBezierPath()///宣言
        path.move(to: beginPoint)///開始点
        path.addLine(to: endPoint)///終了点
        path.stroke()///描画する
    }
    
    func clear(){
    }
    
    func undo(){
    }
    
    func setColor(_ color: UIColor){
    }
    
    func setSize(_ newSize:size){
    }
}
```

完成したソースコードは以下になります。

```ViewController.swift
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var drawView: DrawView!
    @IBOutlet weak var eraserSelected: UISegmentedControl!
    @IBOutlet weak var selectColor: UISegmentedControl!
    @IBOutlet weak var selectSize: UISegmentedControl!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @IBAction func undoTapped(_ sender: Any) {
        drawView.undo()
    }
    
    @IBAction func clearTapped(_ sender: Any) {
        drawView.clear()
    }
    
    
    @IBAction func penOrEraser(_ sender: UISegmentedControl) {
        switch eraserSelected.selectedSegmentIndex{
        case 0:
            drawView.eraserSelected = false
        case 1:
            drawView.eraserSelected = true
        default:
            break
        }
    }
    
    @IBAction func penTapped(_ sender: UISegmentedControl) {
        var c = UIColor.black
        switch selectColor.selectedSegmentIndex {
        case 1:
            c = UIColor.red
        case 2:
            c = UIColor.yellow
        default:
            break
        }
        drawView.setColor(c)
    }

    @IBAction func sizeSelected(_ sender: UISegmentedControl) {
        switch selectSize.selectedSegmentIndex {
        case 0:
            drawView.setSize(.small)
        case 1:
            drawView.setSize(.middle)
        case 2:
            drawView.setSize(.big)
        default:
            break
        }
    }
}
```

```DrawView.swift
class DrawView: UIView{
    
    var finishedDrawings = [Drawing]()
    var currentDrawing: Drawing?
    var currentColor = UIColor.black
    var currentSize: size?
    var eraserSelected = false
        
    override func draw(_ rect: CGRect) {
        for drawing in finishedDrawings{
            drawing.color.setStroke()
            strokeToLine(drawing: drawing)
        }
        
        if let drawing = currentDrawing{
            drawing.color.setStroke()
            strokeToLine(drawing: drawing)
        }
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let point = touches.first!.location(in: self)
        currentDrawing = Drawing()
        if eraserSelected {
            currentDrawing?.color = UIColor.white
        }else{
            currentDrawing?.color = currentColor
        }
        currentDrawing?.size = currentSize
        currentDrawing?.points.append(point)
        setNeedsDisplay()
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        let point = touches.first!.location(in: self)
        currentDrawing?.points.append(point)
        setNeedsDisplay()
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        let point = touches.first!.location(in: self)
        if var drawing = currentDrawing {
            drawing.points.append(point)
            finishedDrawings.append(drawing)
        }
        currentDrawing = nil
        setNeedsDisplay()
    }
    
    func strokeToLine(drawing:Drawing){
        var width:CGFloat = 6.0
        switch drawing.size{
        case .middle:
            width = 12.0
        case .big:
            width = 20.0
        default:
            width = 6.0
        }
        
        let path = UIBezierPath()
        path.lineCapStyle = .round
        path.lineJoinStyle = .round
        path.lineWidth = width
        
        let beginPoint = drawing.points[0]
        path.move(to: beginPoint)
        
        if drawing.points.count > 1{
            for i in 1...(drawing.points.count - 1){
                let endPoint = drawing.points[i]
                path.addLine(to: endPoint)
            }
        }
        path.stroke()
    }
    
    func clear(){
        finishedDrawings.removeAll()
        setNeedsDisplay()
    }
    
    func undo(){
        if finishedDrawings.count == 0 {
            return
        }
        finishedDrawings.remove(at: finishedDrawings.count - 1)
        setNeedsDisplay()
    }
    
    func setColor(_ color: UIColor){
        currentColor = color
    }
    
    func setSize(_ newSize:size){
       currentSize = newSize
    }
}
```