Drawing Canvas
๋ฌด์ธ๊ฐ๋ฅผ ๊ทธ๋ ค์ผ ํ ๋๋ ์ผ๋จ์ PencilKit. ๋ถ์กฑํ๋ค ์ถ์ผ๋ฉด CoreGraphics.
๊ฐ์
iOS์์ โ๊ทธ๋ฆฌ๊ธฐโ๋ ๋ ๊ฐ๋์ ๋๋ค.
- PencilKit โ Apple Pencil ํ์ยท๊ธฐ์ธ๊ธฐยท๋ฐฉ์๊ฐ์ ์๋์ผ๋ก ์ธ์ํ๊ณ , ๋๊ตฌ ํ๋ ํธ(
PKToolPicker)์ ๋ฌดํ ์บ๋ฒ์ค๋ฅผ ๋ฌด๋ฃ๋ก ์ป๋ ๊ณ ์์ค ํ๋ ์์ํฌ. ๋ ธํธยท์๋ช ยท์ค์ผ์น ์ฑ์ด๋ผ๋ฉด ์ฌ๊ธฐ์ ์์ํ์ธ์. - CoreGraphics(Quartz 2D) โ
CGContext/CGPath๊ธฐ๋ฐ์ ์ ์์ค 2D ๋๋ก์ ์์ง. ๋ํ ์๋ํฐ, ์ฐจํธ, ์๊ทธ๋์ฒ ์บก์ฒ ๊ฐ์ ๊ทธ๋ฆฌ๊ธฐ ๋์ ์์ฒด๋ฅผ ์ง์ ์ ์ดํด์ผ ํ ๋ ์ฌ์ฉํฉ๋๋ค.
// SwiftUI์์ CoreGraphics๋ฅผ ๊ฐ์ฅ ๊ฐ๋ณ๊ฒ ์ฐ๋ ๋ฐฉ๋ฒ
Canvas { context, size in
var path = Path()
path.move(to: .zero)
path.addCurve(to: CGPoint(x: size.width, y: size.height),
control1: CGPoint(x: size.width, y: 0),
control2: CGPoint(x: 0, y: size.height))
context.stroke(path, with: .color(.purple), lineWidth: 3)
}
์ด ๋ชจ๋์ 3๊ฐ ๋ฐ๋ชจ๋ ๊ณ ์์ค PencilKit์ผ๋ก ์์ํด ์ ์ฐจ ์ ์์ค CoreGraphics๋ก ๋ด๋ ค๊ฐ๋ฉฐ ๋๋ก์์ ๋ค๋ฃน๋๋ค.
1. PencilKit Canvas
๋ฌด์์ ๋ฐฐ์ฐ๋ โ
PKCanvasView๋ฅผ SwiftUI์ ํตํฉํด ์์ญ ์ค๋ก ๋ณธ๊ฒฉ ๋๋ก์ ์ฑ์ ๋ง๋๋ ํจํด.
struct PencilCanvas: UIViewRepresentable {
@Binding var canvas: PKCanvasView
let toolPicker = PKToolPicker()
func makeUIView(context: Context) -> PKCanvasView {
canvas.drawingPolicy = .anyInput // ์๋ฎฌ๋ ์ดํฐ์์๋ ๋ง์ฐ์ค ์
๋ ฅ ํ์ฉ
canvas.tool = PKInkingTool(.pen, color: .black, width: 5)
toolPicker.setVisible(true, forFirstResponder: canvas)
toolPicker.addObserver(canvas)
canvas.becomeFirstResponder()
return canvas
}
func updateUIView(_ uiView: PKCanvasView, context: Context) {}
}
PencilKit์ด ์๋์ผ๋ก ์ฒ๋ฆฌํด์ฃผ๋ ๊ฒ:
- Apple Pencil ํ์ยท๊ธฐ์ธ๊ธฐยท๋ฐฉ์๊ฐ
- ํยท์ฐํยท๋ง์ปคยทํ์ด๋ผ์ดํฐยท์ง์ฐ๊ฐยท์(Ruler) ๋๊ตฌ
- ๋ฌดํ ์บ๋ฒ์ค, ํ์น-์ค
- ์๊ธ์จ ์ธ์ ์ฐ๋
๋ด๋ณด๋ด๊ธฐ๋ ํ ์ค:
let image = canvas.drawing.image(from: canvas.bounds, scale: UIScreen.main.scale)
๐ก PencilKit์ ํ๊ณ: ๊ฒฐ๊ณผ๋ฌผ์ ๋ํ ์ธ๋ฐํ ์ปค์คํฐ๋ง์ด์ง์ ์ ์ฝ์ด ํฝ๋๋ค. ๋๊ตฌยท์์ยท๊ฒฐ๊ณผ๋ฌผ์ ๋ชจ์์ ์์ ๋กญ๊ฒ ์ ์ดํ๊ณ ์ถ์ผ๋ฉด ๋ค์ ๋ฐ๋ชจ(CoreGraphics)๋ก ๋ด๋ ค๊ฐ์ธ์.
2. Freehand Drawing
๋ฌด์์ ๋ฐฐ์ฐ๋ โ SwiftUI
Canvas์DragGesture๋ก CoreGraphics ๊ธฐ๋ฐ ์์ ๋๋ก์์ ์ฒ์๋ถํฐ ๋ง๋๋ ํจํด.
struct Stroke {
var color: Color
var lineWidth: CGFloat
var points: [CGPoint]
}
@State var strokes: [Stroke] = []
@State var current: Stroke?
Canvas { context, size in
for stroke in strokes + (current.map { [$0] } ?? []) {
var path = Path()
path.addLines(stroke.points)
context.stroke(path, with: .color(stroke.color), lineWidth: stroke.lineWidth)
}
}
.gesture(
DragGesture(minimumDistance: 0)
.onChanged { value in
if current == nil {
current = Stroke(color: selectedColor, lineWidth: width, points: [])
}
current?.points.append(value.location)
}
.onEnded { _ in
if let s = current { strokes.append(s) }
current = nil
}
)
ํต์ฌ ํฌ์ธํธ:
Canvas๋ ๋งค ํ๋ ์ ๋ค์ ๊ทธ๋ฆฌ๋ immediate-mode API. ๋ชจ๋ stroke๋ฅผ ๋ค์ ๊ทธ๋ฆฌ๋ ๋น์ฉ์ด ๋ถ๋ด๋๋ฉด ์บ์ฑ์ด ํ์ํฉ๋๋ค.- ์์ ํ๋ ํธ, ๊ตต๊ธฐ ์ฌ๋ผ์ด๋, ์ง์ฐ๊ฐ, undo/redo๋ ๋ชจ๋
[Stroke]๋ฐฐ์ด์ ์กฐ์ํ๋ ์ผ. - PencilKit์ด 5์ค๋ก ํด์ฃผ๋ ์ธํฐ๋์ ์, ์ฌ๊ธฐ์๋ ์ง์ ๋น๋ํ๋ ๋งํผ ๋ชจ๋ ๊ฒ์ ์ ์ดํ ์ ์์ต๋๋ค.
3. Shape Builder
๋ฌด์์ ๋ฐฐ์ฐ๋ โ ํฐ์น ์ ์ค์ฒ๋ก ์ ยท์ฌ๊ฐํยท์ยท์ผ๊ฐํ์ ์ธํฐ๋ํฐ๋ธํ๊ฒ ๊ทธ๋ฆฌ๊ณ , stroke์ fill ์์์ ๋ถ๋ฆฌํด ๋ค๋ฃจ๋ ํจํด.
enum ShapeKind { case line, rectangle, ellipse, triangle }
struct DrawnShape {
var kind: ShapeKind
var rect: CGRect // ์์์ -๋์ ์ด ๋ง๋๋ ๊ฒฝ๊ณ ์ฌ๊ฐํ
var stroke: Color
var fill: Color
}
@State var shapes: [DrawnShape] = []
@State var preview: DrawnShape?
Canvas { context, size in
for shape in shapes + (preview.map { [$0] } ?? []) {
let path = path(for: shape)
context.fill(path, with: .color(shape.fill))
context.stroke(path, with: .color(shape.stroke), lineWidth: 2)
}
}
.gesture(
DragGesture(minimumDistance: 0)
.onChanged { value in
let r = CGRect(start: value.startLocation, end: value.location)
preview = DrawnShape(kind: kind, rect: r, stroke: strokeColor, fill: fillColor)
}
.onEnded { _ in
if let p = preview { shapes.append(p) }
preview = nil
}
)
์ฌ๊ธฐ์ ๋ฐฐ์ฐ๋ ํต์ฌ:
- ๋๋๊ทธ ์์์ -ํ์ฌ์ ์ผ๋ก ๋ง๋ค์ด์ง๋ ๊ฒฝ๊ณ ์ฌ๊ฐํ(
rect)์์ ๋ํ์ ์ ์ - ๊ฐ ๋ํ ์ข
๋ฅ๋ง๋ค
Path๋ก ๋งคํ (์ ๋ถ /addRect/addEllipse(in:)/ ์ผ๊ฐํ path) - fill๊ณผ stroke๋ฅผ ๋ณ๊ฐ์ ์์์ผ๋ก ๋ค๋ฃจ๋ ๋ ๋จ๊ณ ๊ทธ๋ฆฌ๊ธฐ
์ค์ ํ
Best Practices
- ๋จ์ ๋๋ก์/ํ๊ธฐ ์ฑ์ด๋ผ๋ฉด PencilKit์ผ๋ก ์์ โ ๊ฐ๋ฐ ์๋์ ํ์ง์์ ์๋์ ์ผ๋ก ์ ๋ฆฌํฉ๋๋ค.
- SwiftUI
Canvas๋ immediate-mode๋ผ ๋งค ํ๋ ์ ๋ค์ ๊ทธ๋ฆฝ๋๋ค. stroke๊ฐ 1000๊ฐ ๋จ์๋ก ๋์ด๋๋ฉด vector โ bitmap ์บ์ฑ์ ๊ณ ๋ คํ์ธ์. - PKDrawing์
Data๋ก ์ง๋ ฌํ/์ญ์ง๋ ฌํ๊ฐ ๊ฐํธํฉ๋๋ค. ์ ์ฅ/๋ณต์์ด ํ์ํ๋ฉด PencilKit์ด ์ ๋ฆฌ.
์ฃผ์ ์ฌํญ
- CoreGraphics(Quartz 2D)์ ์ขํ ์์ ์ ์ขํ๋จ(์ํ์ ์ขํ). UIKit/SwiftUI๋ ์ข์๋จ. ๋์ ํผ์ฉํ ๋ ๋ณํ์ ์ฃผ์.
- PencilKit์ iOS/iPadOS ์ ์ฉ. macOS Catalyst์์๋ ์ผ๋ถ ๊ธฐ๋ฅ์ด ์ ํ๋ฉ๋๋ค.
- Apple Pencil์ predicted touches๋ฅผ ํ์ฉํ๋ฉด ์ฒด๊ฐ ์ง์ฐ์ ์ค์ผ ์ ์์ต๋๋ค (
UITouch.predictedTouches(for:)). - ๊ณ ํด์๋ ๋นํธ๋งต์ ๋ง๋ค ๋๋
UIScreen.main.scale์ ๊ณฑํด ๋ฆฌ์ฌ์ด์ฆํด์ผ ํ๋ฆฟํด์ง์ง ์์ต๋๋ค.