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.
