Thanks for reading, it was a fun and challenging tutorial to create.
To implement grid snap think about what you want to achieve. You want the node to snap into position once the drag ends. You want to round up or down the final x-y value for the position change to your chosen granularity (10). SurfaceView
is where you deal with drag events. onEnded
is where you conclude the drag event of DragGesture
.onEnded { value in
self.processDragEnd(value)
})
processDragEnd
eventually falls through to SurfaceView.processNodeTranslation
for a translation op which in turn calls Mesh.processNodeTranslation
Mesh.processNodeTranslation
is the ultimate setting point for the node position.
func processNodeTranslation(_ translation: CGSize, nodes: [DragInfo]) {
nodes.forEach({ draginfo in
if let node = nodeWithID(draginfo.id) {
let nextPosition = draginfo.originalPosition.translatedBy(x: translation.width, y: translation.height)
positionNode(node, position: nextPosition)
}
})
}
Both onChange
on onEnded
call here and you don’t want onChange
to round to grid as that will cause the drag to be jaggy. You can change the call chain to include a grid snap boolean.
In Surface.swift
change
func processNodeTranslation(_ translation: CGSize)
to
func processNodeTranslation(_ translation: CGSize, snapToGrid: Bool = false)
then in Mesh.swift
do the same change.
func processNodeTranslation(_ translation: CGSize, nodes: [DragInfo])
to
func processNodeTranslation(_ translation: CGSize, nodes: [DragInfo], snapToGrid: Bool = false)
Chain that boolean inside Surface.swift
.
In processDragEnd(_ value: DragGesture.Value)
change the call:
processNodeTranslation(value.translation)
to
processNodeTranslation(value.translation, snapToGrid: true)
Then in processNodeTranslation
glue that into the call to Mesh.processNodeTranslation
change:
mesh.processNodeTranslation(scaledTranslation,
nodes: selection.draggingNodes,
snapToGrid: snapToGrid)
After plumbing snapToGrid
down through the call chain, Mesh.processNodeTranslation
has enough info to decide whether or not to snap to grid.
In Mesh
change nextPosition
from let
to var
then do a rounding to 10 by:
- dividing by 10
- round to closest integral
- multiply by 10
var nextPosition = draginfo.originalPosition.translatedBy(x: translation.width, y: translation.height)
if snapToGrid {
let granularity: CGFloat = 10.0
nextPosition.x = (nextPosition.x/granularity).rounded(.toNearestOrEven) * granularity
nextPosition.y = (nextPosition.y/granularity).rounded(.toNearestOrEven) * granularity
}
In summary you change the final source of truth for node position. UX for drag is kept smooth but the final position set operation rounds out to your chosen granularity.