feat(notes): make the canvas resizable, add pinch/double click gestures, make sure the notes don't cut off
This commit is contained in:
@@ -25,6 +25,11 @@ struct HandwritingCanvasView: UIViewRepresentable {
|
|||||||
// Store canvas in coordinator for later access
|
// Store canvas in coordinator for later access
|
||||||
context.coordinator.canvasView = canvasView
|
context.coordinator.canvasView = canvasView
|
||||||
|
|
||||||
|
// Restore saved tool if available
|
||||||
|
if let savedTool = Coordinator.savedTool {
|
||||||
|
canvasView.tool = savedTool
|
||||||
|
}
|
||||||
|
|
||||||
return canvasView
|
return canvasView
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,6 +50,8 @@ struct HandwritingCanvasView: UIViewRepresentable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Coordinator: NSObject, PKCanvasViewDelegate {
|
class Coordinator: NSObject, PKCanvasViewDelegate {
|
||||||
|
static var savedTool: PKTool?
|
||||||
|
|
||||||
var drawing: Binding<PKDrawing>
|
var drawing: Binding<PKDrawing>
|
||||||
var onDrawingChanged: ((PKDrawing) -> Void)?
|
var onDrawingChanged: ((PKDrawing) -> Void)?
|
||||||
var canvasView: PKCanvasView?
|
var canvasView: PKCanvasView?
|
||||||
@@ -78,6 +85,9 @@ struct HandwritingCanvasView: UIViewRepresentable {
|
|||||||
func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
|
func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
|
||||||
drawing.wrappedValue = canvasView.drawing
|
drawing.wrappedValue = canvasView.drawing
|
||||||
onDrawingChanged?(canvasView.drawing)
|
onDrawingChanged?(canvasView.drawing)
|
||||||
|
|
||||||
|
// Save the current tool for persistence
|
||||||
|
Coordinator.savedTool = canvasView.tool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,21 +30,47 @@ struct ScrollableCanvasView: UIViewRepresentable {
|
|||||||
// Allow finger touches to pass through for scrolling
|
// Allow finger touches to pass through for scrolling
|
||||||
canvasView.allowsFingerDrawing = false
|
canvasView.allowsFingerDrawing = false
|
||||||
|
|
||||||
// Set initial canvas size - will be updated in updateUIView with proper bounds
|
// Set initial canvas size based on drawing bounds
|
||||||
let canvasWidth: CGFloat = 800 // Placeholder width
|
let drawingBounds = drawing.bounds
|
||||||
let canvasHeight: CGFloat = 3000 // Start with a large canvas
|
let canvasWidth: CGFloat
|
||||||
|
let canvasHeight: CGFloat
|
||||||
|
|
||||||
|
if drawingBounds.isEmpty {
|
||||||
|
canvasWidth = 2000
|
||||||
|
canvasHeight = 3000
|
||||||
|
} else {
|
||||||
|
canvasWidth = max(drawingBounds.maxX + 500, 2000)
|
||||||
|
canvasHeight = max(drawingBounds.maxY + 500, 3000)
|
||||||
|
}
|
||||||
|
|
||||||
canvasView.frame = CGRect(x: 0, y: 0, width: canvasWidth, height: canvasHeight)
|
canvasView.frame = CGRect(x: 0, y: 0, width: canvasWidth, height: canvasHeight)
|
||||||
|
|
||||||
scrollView.addSubview(canvasView)
|
scrollView.addSubview(canvasView)
|
||||||
scrollView.contentSize = canvasView.frame.size
|
scrollView.contentSize = canvasView.frame.size
|
||||||
|
|
||||||
// Allow scrolling beyond content
|
// Allow scrolling beyond content in both directions
|
||||||
scrollView.alwaysBounceVertical = true
|
scrollView.alwaysBounceVertical = true
|
||||||
|
scrollView.alwaysBounceHorizontal = true
|
||||||
|
|
||||||
|
// Enable zooming
|
||||||
|
scrollView.minimumZoomScale = 0.25 // Zoom out to 25% (see full canvas)
|
||||||
|
scrollView.maximumZoomScale = 2.0 // Zoom in to 200%
|
||||||
|
scrollView.delegate = context.coordinator
|
||||||
|
|
||||||
// Store references in coordinator
|
// Store references in coordinator
|
||||||
context.coordinator.canvasView = canvasView
|
context.coordinator.canvasView = canvasView
|
||||||
context.coordinator.scrollView = scrollView
|
context.coordinator.scrollView = scrollView
|
||||||
|
|
||||||
|
// Add double-tap gesture for zoom toggle
|
||||||
|
let doubleTapGesture = UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleDoubleTap(_:)))
|
||||||
|
doubleTapGesture.numberOfTapsRequired = 2
|
||||||
|
scrollView.addGestureRecognizer(doubleTapGesture)
|
||||||
|
|
||||||
|
// Restore saved tool if available
|
||||||
|
if let savedTool = Coordinator.savedTool {
|
||||||
|
canvasView.tool = savedTool
|
||||||
|
}
|
||||||
|
|
||||||
return scrollView
|
return scrollView
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,23 +82,24 @@ struct ScrollableCanvasView: UIViewRepresentable {
|
|||||||
}
|
}
|
||||||
canvasView.isUserInteractionEnabled = isEditable
|
canvasView.isUserInteractionEnabled = isEditable
|
||||||
|
|
||||||
// Update canvas width to match scroll view bounds (once they're valid)
|
|
||||||
let canvasWidth = scrollView.bounds.width > 0 ? scrollView.bounds.width : 800
|
|
||||||
|
|
||||||
// Expand canvas if drawing extends beyond current bounds
|
// Expand canvas if drawing extends beyond current bounds
|
||||||
let drawingBounds = drawing.bounds
|
let drawingBounds = drawing.bounds
|
||||||
|
let currentWidth = canvasView.frame.width
|
||||||
let currentHeight = canvasView.frame.height
|
let currentHeight = canvasView.frame.height
|
||||||
|
let requiredWidth: CGFloat
|
||||||
let requiredHeight: CGFloat
|
let requiredHeight: CGFloat
|
||||||
|
|
||||||
if drawingBounds.isEmpty {
|
if drawingBounds.isEmpty {
|
||||||
|
requiredWidth = 2000
|
||||||
requiredHeight = 3000
|
requiredHeight = 3000
|
||||||
} else {
|
} else {
|
||||||
|
requiredWidth = max(drawingBounds.maxX + 500, 2000) // Add padding to the right
|
||||||
requiredHeight = max(drawingBounds.maxY + 500, 3000) // Add padding below
|
requiredHeight = max(drawingBounds.maxY + 500, 3000) // Add padding below
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update canvas size if needed
|
// Update canvas size if needed
|
||||||
if requiredHeight > currentHeight || abs(canvasView.frame.width - canvasWidth) > 1 {
|
if requiredWidth > currentWidth || requiredHeight > currentHeight {
|
||||||
canvasView.frame = CGRect(x: 0, y: 0, width: canvasWidth, height: requiredHeight)
|
canvasView.frame = CGRect(x: 0, y: 0, width: requiredWidth, height: requiredHeight)
|
||||||
scrollView.contentSize = canvasView.frame.size
|
scrollView.contentSize = canvasView.frame.size
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +113,9 @@ struct ScrollableCanvasView: UIViewRepresentable {
|
|||||||
Coordinator(drawing: $drawing, onDrawingChanged: onDrawingChanged)
|
Coordinator(drawing: $drawing, onDrawingChanged: onDrawingChanged)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Coordinator: NSObject, PKCanvasViewDelegate {
|
class Coordinator: NSObject, PKCanvasViewDelegate, UIScrollViewDelegate {
|
||||||
|
static var savedTool: PKTool?
|
||||||
|
|
||||||
var drawing: Binding<PKDrawing>
|
var drawing: Binding<PKDrawing>
|
||||||
var onDrawingChanged: ((PKDrawing) -> Void)?
|
var onDrawingChanged: ((PKDrawing) -> Void)?
|
||||||
var canvasView: PKCanvasView?
|
var canvasView: PKCanvasView?
|
||||||
@@ -121,6 +150,54 @@ struct ScrollableCanvasView: UIViewRepresentable {
|
|||||||
func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
|
func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
|
||||||
drawing.wrappedValue = canvasView.drawing
|
drawing.wrappedValue = canvasView.drawing
|
||||||
onDrawingChanged?(canvasView.drawing)
|
onDrawingChanged?(canvasView.drawing)
|
||||||
|
|
||||||
|
// Save the current tool for persistence
|
||||||
|
Coordinator.savedTool = canvasView.tool
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - UIScrollViewDelegate for zooming
|
||||||
|
|
||||||
|
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
|
||||||
|
return canvasView
|
||||||
|
}
|
||||||
|
|
||||||
|
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
|
||||||
|
// Snap back to normal zoom (1.0) if close enough
|
||||||
|
if scale > 0.9 && scale < 1.1 {
|
||||||
|
UIView.animate(withDuration: 0.2) {
|
||||||
|
scrollView.setZoomScale(1.0, animated: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func handleDoubleTap(_ gesture: UITapGestureRecognizer) {
|
||||||
|
guard let scrollView = scrollView else { return }
|
||||||
|
|
||||||
|
let currentScale = scrollView.zoomScale
|
||||||
|
|
||||||
|
if abs(currentScale - 1.0) < 0.01 {
|
||||||
|
// Currently at 100%, zoom in to 1.5x
|
||||||
|
let location = gesture.location(in: canvasView)
|
||||||
|
let zoomRect = zoomRect(for: 1.5, center: location)
|
||||||
|
scrollView.zoom(to: zoomRect, animated: true)
|
||||||
|
} else {
|
||||||
|
// Not at 100%, zoom back to 100%
|
||||||
|
scrollView.setZoomScale(1.0, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func zoomRect(for scale: CGFloat, center: CGPoint) -> CGRect {
|
||||||
|
guard let scrollView = scrollView else { return .zero }
|
||||||
|
|
||||||
|
var zoomRect = CGRect.zero
|
||||||
|
zoomRect.size.width = scrollView.frame.size.width / scale
|
||||||
|
zoomRect.size.height = scrollView.frame.size.height / scale
|
||||||
|
|
||||||
|
// Center on the tap location
|
||||||
|
zoomRect.origin.x = center.x - (zoomRect.size.width / 2.0)
|
||||||
|
zoomRect.origin.y = center.y - (zoomRect.size.height / 2.0)
|
||||||
|
|
||||||
|
return zoomRect
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user