Skip to content

Instantly share code, notes, and snippets.

@kangddong
Last active June 2, 2025 05:14
Show Gist options
  • Save kangddong/5fe777c6bcb2fcca87e604245a1ff444 to your computer and use it in GitHub Desktop.
Save kangddong/5fe777c6bcb2fcca87e604245a1ff444 to your computer and use it in GitHub Desktop.
직선, 물결 그리기
protocol PencilStrokeEditAble: AnyObject {
var startPoint: CGPoint? { get set }
var initialStrokeCount: Int { get set }
var inkTool: PKInkingTool { get set }
func beginStroke(at point: CGPoint, initialStrokeCount: Int)
func updateStroke(to point: CGPoint) -> PKStroke?
func endStroke()
func update(inkTool: PKInkingTool)
}
extension PencilStrokeEditAble {
func beginStroke(at point: CGPoint, initialStrokeCount: Int) {
self.startPoint = point
self.initialStrokeCount = initialStrokeCount
}
func endStroke() {
self.startPoint = nil
self.initialStrokeCount = 0
}
func update(inkTool: PKInkingTool) {
self.inkTool = inkTool
}
}
final class StraightLineManager: PencilStrokeEditAble {
var startPoint: CGPoint?
var initialStrokeCount: Int = 0
var inkTool: PKInkingTool = PKInkingTool(.pen, color: .black, width: 4)
func updateStroke(to point: CGPoint) -> PKStroke? {
guard let start = startPoint else { return nil }
let interpolatedWidth = inkTool.width * 0.65
let size = CGSize(width: interpolatedWidth, height: interpolatedWidth)
let points = [
PKStrokePoint(location: start, timeOffset: 0, size: size, opacity: 1, force: 1, azimuth: 0, altitude: 0),
PKStrokePoint(location: point, timeOffset: 1, size: size, opacity: 1, force: 1, azimuth: 0, altitude: 0)
]
let path = PKStrokePath(controlPoints: points, creationDate: Date())
let ink = PKInk(inkTool.inkType, color: inkTool.color)
return PKStroke(ink: ink, path: path)
}
}
final class WavyLineManager: PencilStrokeEditAble {
var startPoint: CGPoint?
var initialStrokeCount: Int = 0
var inkTool: PKInkingTool = PKInkingTool(.pen, color: .black, width: 4)
// 물결 효과를 위한 파라미터
private let waveAmplitude: CGFloat = 5.0 // 물결의 높이
private let waveFrequency: CGFloat = 3.0 // 물결의 주기
private let waveLength: CGFloat = 20 // 한 파장의 길이 (px 기준)
func updateStroke(to point: CGPoint) -> PKStroke? {
guard let start = startPoint else { return nil }
// 시작점과 현재 점 사이 거리 및 방향 계산
let dx = point.x - start.x
let dy = point.y - start.y
let distance = hypot(dx, dy)
// 선분 길이에 따라 세그먼트 분할 (최소 2세그먼트 이상)
let segments = max(Int(distance / 5), 2)
// 선분의 단위 방향 벡터
let ux = dx / distance
let uy = dy / distance
// 법선 벡터 = (-uy, ux)
let perp = CGPoint(x: -uy, y: ux)
let interpolatedWidth = inkTool.width * 0.65
let size = CGSize(width: interpolatedWidth, height: interpolatedWidth)
var points: [PKStrokePoint] = []
for i in 0...segments {
let t = CGFloat(i) / CGFloat(segments)
// 선형 보간으로 기본 위치 계산
let baseX = start.x + dx * t
let baseY = start.y + dy * t
let basePoint = CGPoint(x: baseX, y: baseY)
// sine 기반 offset: 2π * frequency 를 곱해 주기 조절
let offset = waveAmplitude * sin((distance * t / waveLength) * 2 * .pi)
let offsetPoint = CGPoint(
x: basePoint.x + perp.x * offset,
y: basePoint.y + perp.y * offset
)
let strokePoint = PKStrokePoint(
location: offsetPoint,
timeOffset: Double(t),
size: size,
opacity: 1,
force: 1,
azimuth: 0,
altitude: 0
)
points.append(strokePoint)
}
let path = PKStrokePath(controlPoints: points, creationDate: Date())
let ink = PKInk(inkTool.inkType, color: inkTool.color)
return PKStroke(ink: ink, path: path)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment