Snap to edge for UIView

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. 

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.