Skip to content

Instantly share code, notes, and snippets.

@cerupcat
Last active January 29, 2022 16:09
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cerupcat/0c6bbfb7369206e54596f898dbf84cc7 to your computer and use it in GitHub Desktop.
Save cerupcat/0c6bbfb7369206e54596f898dbf84cc7 to your computer and use it in GitHub Desktop.
Turn any UIView into a tappable button that animates and calls a selector
import Foundation
import UIKit
@propertyWrapper
class Tappable<T: UIView>: NSObject, UIGestureRecognizerDelegate {
// MARK: - Public Properties
public var wrappedValue: T? {
willSet {
removeGesture()
}
didSet {
addGesture()
}
}
var tapAction: (() -> Void)?
// MARK: - Private Properties
private let pressedDownTransform = CGAffineTransform.identity.scaledBy(x: 0.98, y: 0.98)
private lazy var longPressRecognizer = UILongPressGestureRecognizer(target: self,
action: #selector(didTapLongPress))
private lazy var tapGestureRecognizer = UITapGestureRecognizer(target: self,
action: #selector(didTap))
private func addGesture() {
longPressRecognizer.minimumPressDuration = 0.05
longPressRecognizer.delegate = self
wrappedValue?.addGestureRecognizer(longPressRecognizer)
tapGestureRecognizer.numberOfTapsRequired = 1
tapGestureRecognizer.delegate = self
wrappedValue?.addGestureRecognizer(tapGestureRecognizer)
}
private func removeGesture() {
wrappedValue?.removeGestureRecognizer(longPressRecognizer)
wrappedValue?.removeGestureRecognizer(tapGestureRecognizer)
}
@objc private func didTapLongPress(sender: UILongPressGestureRecognizer) {
guard let view = wrappedValue else {
return
}
if sender.state == .began || sender.state == .changed {
onDown(view: view)
} else {
onUp(view: view)
}
}
@objc private func didTap(sender _: UILongPressGestureRecognizer) {
tapAction?()
}
private func onDown(view: UIView) {
animate(view, to: pressedDownTransform)
}
private func onUp(view: UIView) {
animate(view, to: .identity)
}
private func animate(_ view: UIView, to transform: CGAffineTransform) {
UIView.animate(withDuration: 0.4,
delay: 0,
usingSpringWithDamping: 0.4,
initialSpringVelocity: 3,
options: [.curveEaseInOut, .allowUserInteraction],
animations: {
view.transform = transform
}, completion: nil)
}
// MARK: UIGestureRecognizerDelegate
func gestureRecognizer(_: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith _: UIGestureRecognizer) -> Bool {
return true
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
// ignore buttons and other UIControls
if touch.view is UIControl {
return false
}
return true
}
}
@cerupcat
Copy link
Author

cerupcat commented Jan 26, 2022

Usage:

class HomeTaskCell: UICollectionViewCell {

    @Tappable @IBOutlet private var selectionView: UIView!

    override func awakeFromNib() {
        super.awakeFromNib()

        // tap
        _selectionView.tapAction = { in
            // do something
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment