iOS, SwiftUI, Swift

SwiftUIでお絵かきアプリ

SwiftUIでお絵かきアプリを作りました。
指をなぞると線がかける機能があります。

https://github.com/SatoTakeshiX/EditorApp

ドラッグイベント

画面のドラッグイベントはView.gesture()メソッドにDragGestureインスタンスを引数にわたすと取得できます。DragGestureonChangedメソッドでドラッグ中、onEndedメソッドでドラッグ終了を検知できます。

.gesture(
    DragGesture()
        .onChanged({ (value) in})
        .onEnded({ (value) in})
)

どちらもクロージャーでイベントのValueを取得できます。メンバー変数は次の通り。

  • location: CGPoint, イベントの位置座標
  • startLocation: CGPoint, イベントが始まったときの位置座標

線の引き方

線を引く方法はPathを使います。クロージャーのpathに座標ポイントを渡すと線を引けます。
strokeメソッドで色と線の幅を設定します。

Path { path in
    path.addLines(self.points)//線を引くポイントの配列をパラメーターとして渡す

}
.stroke(self.tmpDrawPoints.color, lineWidth: 10)//

ドラッグ座標の扱い

今回線の色は赤と消しゴム(背景と同じ色指定で消えたようにみせる)の2つを用意しました。
ボタンで色の切り替えができるようにします。

VStack(spacing: 10) {
    Button(action: {
        self.selectedColor = .red

    }) { Text("赤")
    }
    Button(action: {
        self.selectedColor = .clear
    }) { Text("消しゴム")
    }
}
.frame(minWidth: 0.0, maxWidth: CGFloat.infinity)
.background(Color.gray)

ドラッグした場合の位置座標と色を保持するためにDrawPointsという型を定義します。

struct DrawPoints: Identifiable {
    var points: [CGPoint]
    var color: Color
    var id = UUID()
}

DrawPointsをもとにViewを描画するDrawPathViewを定義します。
ZStackForEachを組み合わせてDrawPointsごとにPathを作ります。

struct DrawPathView: View {
    var drawPointsArray: [DrawPoints]
    init(drawPointsArray: [DrawPoints]) {
        self.drawPointsArray = drawPointsArray
    }
    var body: some View {
        ZStack {
            ForEach(drawPointsArray) { data in
                Path { path in
                    path.addLines(data.points)
                }
                .stroke(data.color, lineWidth: 10)
            }
        }
    }
}

お絵かきアプリ

今回のお絵かきアプリのViewをOverlayViewとします。プロパティを@Stateで定義しておきます。

struct OverlayView: View {
    @State var tmpDrawPoints: DrawPoints = DrawPoints(points: [], color: .red)
    @State var endedDrawPoints: [DrawPoints] = []
    @State var startPoint: CGPoint = CGPoint.zero
    @State var selectedColor: DrawType = .red

    var body: some View {}

キャンバスとなるViewをRectangleで作ります。
.overlayDrawPathViewを作りドラッグした際のendedDrawPointsを渡します。
またendedDrawPointsだけだとドラッグが終了しないとPathの描画がされないので描画中に更新されるtmpDrawPointsも利用してPathを描画しておきます。

Rectangle()
    .foregroundColor(Color.white)
    .frame(width: 300, height: 300, alignment: .center)
    .overlay(
        DrawPathView(drawPointsArray: endedDrawPoints)
            .overlay(
                // ドラッグ中の描画。指を離したらここの描画は消えるがDrawPathViewが上書きするので見た目は問題ない
                Path { path in
                    path.addLines(self.tmpDrawPoints.points)
                }
                .stroke(self.tmpDrawPoints.color, lineWidth: 10)
        )
)
    .gesture(
        DragGesture()
            .onChanged({ (value) in
          // ドラッグごとのイベントのみを更新する
                if self.startPoint != value.startLocation {
                    self.tmpDrawPoints.points.append(value.location)
                    self.tmpDrawPoints.color = self.selectedColor.color

                }
            })
            .onEnded({ (value) in
                self.startPoint = value.startLocation
                self.endedDrawPoints.append(self.tmpDrawPoints)
                self.tmpDrawPoints = DrawPoints(points: [], color: self.selectedColor.color)
            })
)

まとめ

簡単ですが、指でお絵かきアプリが作れました。
まとめます。

  • .gesture(DragGesture())でドラッグイベントを取得する
  • Path { path in }で線を引く
  • ドラッグイベントごとに座標を保持する
  • Viewに描画

宣伝

一冊でSwiftUIの仕組みがわかる「SwiftUI実践入門」Boothで発売中です。
IMG_6059

https://personal-factory.booth.pm/items/1579464

Author image

About Sato Takeshi

  • Tokyo, Japan