For Tahrir I wanted to add a new feature in the new major update. I thought implementing a snap to edge might help users to align texts with each other, on small screen and with fingers it would be difficult without it.

The general idea is storing edges ( frame.origin ) of all existing views in an array, then calculate the distance of current view’s edges, which the user is moving, and if it’s smaller than a threshold you activate the snap.

The snap works, as you surely have noticed in apps like Instagram, like this: when the view gets near an edge it grips the edge until you push it further to release the grip and move freely again. Sounds simple, right?

I post pieces of the code here and you can get the rest from github.

var squareItems = [Int: CGPoint]()

This variable keeps record of the edges of the existing views. The key is hash value of the view, which is unique. We update it every time a new view is added or when the user is done with moving the view.

Let’s calculate the distances:

for item in squareItems { if item.key != senderView.hash { let leftDistance = abs(senderView.frame.origin.x + translation.x - item.value.x) if leftDistance < 5 { snapOnLeftEdge = true shouldSnap = true horizontalDifference = senderView.frame.origin.x + translation.x - item.value.x } let topDistance = abs(senderView.frame.origin.y + translation.y - item.value.y) if topDistance < 5 { snapOnTopEdge = true shouldSnap = true verticalDiffecence = senderView.frame.origin.y + translation.y - item.value.y } } }

leftDistance and topDistance do the trick, the griping part.

And here it applies the trick, shows a line on the screen and actives the haptic, lightly.

if snapOnLeftEdge { var _frame = senderView.frame _frame.origin.x = _frame.origin.x + translation.x - horizontalDifference _frame.origin.y = _frame.origin.y + translation.y senderView.frame = _frame if feedbackIsAllowed { feedbackIsAllowed = false let generator = UIImpactFeedbackGenerator(style: .light) generator.impactOccurred() if let _rulersGuid = setupRulerGuidesView(.horizontal(_frame.origin.x)) { self.view.addSubview(_rulersGuid) _rulersGuid.layer.zPosition = 2 } } } else if snapOnTopEdge { var _frame = senderView.frame _frame.origin.x = _frame.origin.x + translation.x _frame.origin.y = _frame.origin.y + translation.y - verticalDiffecence senderView.frame = _frame if feedbackIsAllowed { feedbackIsAllowed = false let generator = UIImpactFeedbackGenerator(style: .light) generator.impactOccurred() if let _rulersGuid = setupRulerGuidesView(.vertical(_frame.origin.y)) { self.view.addSubview(_rulersGuid) _rulersGuid.layer.zPosition = 2 } } }

I think you have grasped the idea about how snap works, you can download the whole code, as an app, from the github repo.