この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは。きんくまです。
SwiftUIでお絵かきしてみたいです。
今までFlashとかhtmlのCanvasとかで同じようなのを作ったことがあるので、SwiftUIでも作れるのか試してみました。
ビットマップではなくて、ベクターのお絵かきになります。
できたもの
Model
1ストロークごとの開始地点の座標と、線を結ぶための複数の座標があれば描けそうです。
ForEachで回したいのでIdentifiableに準拠するためにidを入れてあります。
struct DrawLine: Identifiable {
static var idCount: Int = 0
var id: String
var points: [CGPoint]
static func makeDrawLine(points: [CGPoint]) -> DrawLine {
let line = DrawLine(id: "\(DrawLine.idCount)", points: points)
DrawLine.idCount += 1
return line
}
}
View
考え方としては、ドラッグをしながら座標を記録。ドラッグが終わったら、その座標をアーカイブとして保存。
描画も、ドラッグ中のものと、アーカイブしているものをそれぞれ分けて描画してあげれば、実装も簡単そうです。
struct ContentView: View {
// すでに描いたLine
@State private var lines: [DrawLine] = []
// いまドラッグ中のLine
@State private var currentLine: DrawLine?
var body: some View {
VStack {
// リセットボタン
Button(action: {
lines = []
}, label: {
Text("Clear")
})
ZStack {
// Canvas部分
Rectangle()
.fill(Color.white)
.border(Color.black, width: 1)
.gesture(
DragGesture(minimumDistance: 0, coordinateSpace: .local)
.onChanged({ value in
if currentLine == nil {
currentLine = DrawLine.makeDrawLine(points: [])
}
guard var line = currentLine else { return }
line.points.append(value.location)
currentLine = line
})
.onEnded({ value in
guard var line = currentLine else { return }
line.points.append(value.location)
lines.append(line)
// リセット
currentLine = nil
})
)
// 追加ずみのLineの描画
ForEach(lines) { line in
Path { path in
path.addLines(line.points)
}.stroke(Color.red, lineWidth: 1)
}.clipped()
// ドラッグ中のLineの描画
Path { path in
guard let line = currentLine else { return }
path.addLines(line.points)
}.stroke(Color.red, lineWidth: 1)
.clipped()
}.padding(20)
}
}
}
結構簡単に実装できました。
出てくる要望としては、とかでしょうか。
- 線ごとに色を変えたい
- 線ごとに太さを変えたい
- アンドゥを入れたい
1と2は、以下で実装できそうです。
- Modelに色と太さのプロパティを追加
- 色と太さの切り替えパネルを作る
- 切り替えパネルの状態を1ストロークごとに記録
- パスを描画するときに色と太さを反映
3は lines というプロパティの一番後ろデータを1つずつ削除していけばできますね。
まとめ
手軽に描画処理ができるのは良いなと思いました。
ではでは。