Skip to content

Instantly share code, notes, and snippets.

@shakked
Last active September 8, 2021 17:36
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shakked/91c9f5354cffccca1cb1e3c985e0d32b to your computer and use it in GitHub Desktop.
Save shakked/91c9f5354cffccca1cb1e3c985e0d32b to your computer and use it in GitHub Desktop.
Supercharged UIButton
import UIKit
class PowerButton: UIButton {
@IBInspectable var cornerRadius: CGFloat = 0.0 {
didSet {
layer.cornerRadius = cornerRadius
}
}
@IBInspectable var borderColor: UIColor? = nil {
didSet {
layer.borderColor = borderColor?.cgColor
}
}
@IBInspectable var borderWidth: CGFloat = 0.0 {
didSet {
layer.borderWidth = borderWidth
}
}
@IBInspectable var shadowColor: UIColor? {
didSet {
layer.shadowColor = shadowColor?.cgColor
}
}
@IBInspectable var shadowOffset: CGSize = .zero {
didSet {
layer.shadowOffset = shadowOffset
}
}
@IBInspectable var shadowRadius: CGFloat = 0.0 {
didSet {
layer.shadowRadius = shadowRadius
}
}
@IBInspectable var shadowOpacity: Float = 0.0 {
didSet {
layer.shadowOpacity = shadowOpacity
}
}
private var activityIndicator: UIActivityIndicatorView?
private var temporaryTitle: String?
private var temporaryImage: UIImage?
var greedyTouches = true
private var isLoading: Bool = false
private var didTriggerStopLoading = false
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
override var tintColor: UIColor! {
didSet {
titleLabel?.textColor = tintColor
activityIndicator?.color = tintColor
}
}
func setup() {
translatesAutoresizingMaskIntoConstraints = false
addTarget(self, action: #selector(touchDown), for: .touchDown)
addTarget(self, action: #selector(touchUpInside), for: .touchUpInside)
addTarget(self, action: #selector(touchDragExit), for: .touchDragExit)
addTarget(self, action: #selector(touchDragExit), for: .touchCancel)
addTarget(self, action: #selector(logTouchUpOutside), for: .touchUpOutside)
addTarget(self, action: #selector(logTouchDownRepeat), for: .touchDownRepeat)
addTarget(self, action: #selector(logAllTouchEvents), for: .allTouchEvents)
}
@objc private func logTouchDragEnter() {
print("touchDragEnter")
}
@objc private func logTouchUpOutside() {
print("touchUpOutSide")
}
@objc private func logTouchDownRepeat() {
print("touchDownRepeat")
}
@objc private func logAllTouchEvents() {
}
@objc private func touchDragExit() {
let duration = 0.4
let scale: CGFloat = 1
UIView.animate(withDuration: duration) {
self.titleLabel?.alpha = 1.0
self.imageView?.alpha = 1.0
self.transform = CGAffineTransform(scaleX: scale, y: scale)
}
}
@objc private func touchDown() {
let duration = 0.2
let damping: CGFloat = 1
let scale: CGFloat = 0.9
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: damping, initialSpringVelocity: 0, options: [.allowUserInteraction, .curveEaseInOut], animations: {
self.transform = CGAffineTransform(scaleX: scale, y: scale)
}, completion: nil)
self.titleLabel?.alpha = 0.32
self.imageView?.alpha = 0.32
UIImpactFeedbackGenerator(style: .light).impactOccurred(intensity: 0.6)
}
@objc private func touchUpInside() {
let duration = 0.4
let damping: CGFloat = 0.3
let scale: CGFloat = 1
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: damping, initialSpringVelocity: 0, options: [.allowUserInteraction, .curveEaseInOut], animations: {
self.transform = CGAffineTransform(scaleX: scale, y: scale)
self.titleLabel?.alpha = 1.0
self.imageView?.alpha = 1.0
}, completion: nil)
UIImpactFeedbackGenerator(style: .light).impactOccurred(intensity: 0.9)
}
func startLoading() {
didTriggerStopLoading = false
isLoading = true
isEnabled = false
temporaryTitle = titleLabel?.text
temporaryImage = imageView?.image
UIView.animate(withDuration: 0.1) {
self.titleLabel?.alpha = 0.0
self.imageView?.alpha = 0.0
} completion: { _ in
if !self.didTriggerStopLoading {
self.setTitle(nil, for: .normal)
self.setImage(nil, for: .normal)
}
}
let activityIndicator = UIActivityIndicatorView()
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
activityIndicator.style = .medium
activityIndicator.color = tintColor
activityIndicator.startAnimating()
addSubview(activityIndicator)
activityIndicator.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
activityIndicator.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
self.activityIndicator = activityIndicator
}
func stopLoading() {
didTriggerStopLoading = true
isEnabled = true
if let temporaryTitle = temporaryTitle {
setTitle(temporaryTitle, for: .normal)
}
if let temporaryImage = temporaryImage {
setImage(temporaryImage, for: .normal)
}
UIView.animate(withDuration: 0.15) {
self.titleLabel?.alpha = 1.0
self.imageView?.alpha = 1.0
}
self.activityIndicator?.stopAnimating()
self.activityIndicator?.removeFromSuperview()
self.activityIndicator = nil
isLoading = false
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.isHidden || !self.isUserInteractionEnabled || self.alpha < 0.01 { return nil }
let inset: CGFloat = greedyTouches ? -15 : -10
let largerFrame = self.bounds.insetBy(dx: inset, dy: inset)
return (largerFrame.contains(point)) ? self : nil
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment