Animating Views With UILongPressGestureRecognizer

Creating a button in iOS is simple but if you want a more responsive button like the ones Apple Uses in their app store, you need to get creative with gestures.

Feel free to reproduce and implement this gist however you see fit. https://gist.github.com/willconno/dd891db6764a82bbc6e9633e9ec7b67c

UILongPressGestureRecognizer

In order to be notified when the user first presses on the screen you must look into the state variable of UILongPressGestureRecognizer. By default though, there is a delay before UIGestureRecognizer.State.began is called. You can easily reduce this though by settings delay to 0.

let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(onLongPressRecognised(_:)))

longPressGesture.minimumPressDuration = 0

Delegate

The above code works great in a simple interface but if you nest these buttons in a UIScrollView the gesture absorbs all touches and prevents scrolling. A simple workaround is not to block other UIGesturesRecognizer‘s (which UIScrollView uses under-the-hood to manage scrolling) by overriding the delegate on the UILongPressGestureRecognizer we created above and implementing the following delegate method: 

class Button: UIView, UIGestureRecognizerDelegate {

longPressGesture.delegate = self

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
}

By returning true we’re allowing any touches beneath our UILongPressGestureRecognizer to still be called.


Smoothing Animations

We’ve got our button animating now as soon as the user begins a touch down, we’re not stopping our screen from scrolling but now our we can’t stop our button .ended notification from being called. To fix this we simply guard against the function being called after the user begins to scroll:

var safeArea: CGFloat { return 5.0 }

@objc func onLongPressRecognised(_ sender: UILongPressGestureRecognizer) {
           
switch sender.state {
        
case .began:
            
    self.initialPos = sender.location(in: parent)
    UIView.animate(withDuration: 0.2, delay: 0.0, options: [.allowUserInteraction], animations: {
        self.animatedView?.transform = .init(scaleX: 0.95, y: 0.95)
    })

case .changed:
    let loc = sender.location(in: parent)
    
    if loc.x < (initialPos.x - safeArea)
                || loc.x > (initialPos.x + safeArea)
                || loc.y < (initialPos.y - safeArea)
                || loc.y > (initialPos.y + safeArea) {
                self.isCancelled = true
                sender.state = .ended
            }
case .ended:
    UIView.animate(withDuration: 0.3, delay: 0.0, options: [.allowUserInteraction], animations: {
        self.animatedView?.transform = .identity
    })
    if isCancelled { 
        isCancelled = false
    } else {
        .. // do stuff
    }
}}

You can see I’ve used a safe area here to make the cancellation of this gesture less sensitive and allow a better user experience.


Conclusion

Smooth button animations create an entertaining, interactive user experience. Although Apple loves to use these animations in their apps, they don’t make it readily known to developers how they can implement similar features. UILongPressGestureRecognizer is a very flexible class with a lot of room for experimentation and creativity in creating a rich user interface.

Leave a Reply

Your email address will not be published. Required fields are marked *