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.