Last active
August 25, 2023 09:58
-
-
Save IniongunIsaac/f07eaeb7b3557e2510be28d7e4d882ba to your computer and use it in GitHub Desktop.
UIView Constraint Extensions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
typealias VoidAction = (() -> Void) | |
class UIViewTapGestureRecognizer: UITapGestureRecognizer { | |
var action: VoidAction? = nil | |
} | |
struct AnchoredConstraints { | |
var top, leading, bottom, trailing, width, height: NSLayoutConstraint? | |
} | |
extension UIView { | |
@discardableResult | |
func anchor( | |
top: NSLayoutYAxisAnchor? = nil, | |
leading: NSLayoutXAxisAnchor? = nil, | |
bottom: NSLayoutYAxisAnchor? = nil, | |
trailing: NSLayoutXAxisAnchor? = nil, | |
padding: UIEdgeInsets = .zero | |
) -> AnchoredConstraints { | |
translatesAutoresizingMaskIntoConstraints = false | |
var anchoredConstraints = AnchoredConstraints() | |
if let top = top { | |
anchoredConstraints.top = topAnchor.constraint(equalTo: top, constant: padding.top) | |
} | |
if let leading = leading { | |
anchoredConstraints.leading = leadingAnchor.constraint(equalTo: leading, constant: padding.left) | |
} | |
if let bottom = bottom { | |
anchoredConstraints.bottom = bottomAnchor.constraint(equalTo: bottom, constant: -padding.bottom) | |
} | |
if let trailing = trailing { | |
anchoredConstraints.trailing = trailingAnchor.constraint(equalTo: trailing, constant: -padding.right) | |
} | |
[ | |
anchoredConstraints.top, | |
anchoredConstraints.leading, | |
anchoredConstraints.bottom, | |
anchoredConstraints.trailing, | |
anchoredConstraints.width, | |
anchoredConstraints.height | |
].forEach { $0?.isActive = true } | |
return anchoredConstraints | |
} | |
func fillSuperview(padding: UIEdgeInsets = .zero) { | |
translatesAutoresizingMaskIntoConstraints = false | |
if let superviewTopAnchor = superview?.topAnchor { | |
topAnchor.constraint(equalTo: superviewTopAnchor, constant: padding.top).isActive = true | |
} | |
if let superviewBottomAnchor = superview?.bottomAnchor { | |
bottomAnchor.constraint(equalTo: superviewBottomAnchor, constant: -padding.bottom).isActive = true | |
} | |
if let superviewLeadingAnchor = superview?.leadingAnchor { | |
leadingAnchor.constraint(equalTo: superviewLeadingAnchor, constant: padding.left).isActive = true | |
} | |
if let superviewTrailingAnchor = superview?.trailingAnchor { | |
trailingAnchor.constraint(equalTo: superviewTrailingAnchor, constant: -padding.right).isActive = true | |
} | |
} | |
func centerInSuperview(size: CGSize = .zero) { | |
translatesAutoresizingMaskIntoConstraints = false | |
if let superviewCenterXAnchor = superview?.centerXAnchor { | |
centerXAnchor.constraint(equalTo: superviewCenterXAnchor).isActive = true | |
} | |
if let superviewCenterYAnchor = superview?.centerYAnchor { | |
centerYAnchor.constraint(equalTo: superviewCenterYAnchor).isActive = true | |
} | |
if size.width != 0 { | |
widthAnchor.constraint(equalToConstant: size.width).isActive = true | |
} | |
if size.height != 0 { | |
heightAnchor.constraint(equalToConstant: size.height).isActive = true | |
} | |
} | |
func centerXInSuperview() { | |
translatesAutoresizingMaskIntoConstraints = false | |
if let superViewCenterXAnchor = superview?.centerXAnchor { | |
centerXAnchor.constraint(equalTo: superViewCenterXAnchor).isActive = true | |
} | |
} | |
func centerYInSuperview() { | |
translatesAutoresizingMaskIntoConstraints = false | |
if let centerY = superview?.centerYAnchor { | |
centerYAnchor.constraint(equalTo: centerY).isActive = true | |
} | |
} | |
func constraintWidth(constant: CGFloat) { | |
translatesAutoresizingMaskIntoConstraints = false | |
widthAnchor.constraint(equalToConstant: constant).isActive = true | |
} | |
@discardableResult func constraintHeight(constant: CGFloat) -> NSLayoutConstraint { | |
translatesAutoresizingMaskIntoConstraints = false | |
let height = heightAnchor.constraint(equalToConstant: constant) | |
height.isActive = true | |
return height | |
} | |
func constraintSize(height: CGFloat, width: CGFloat) { | |
translatesAutoresizingMaskIntoConstraints = false | |
heightAnchor.constraint(equalToConstant: height).isActive = true | |
widthAnchor.constraint(equalToConstant: width).isActive = true | |
} | |
func constraintSize(constant: CGFloat) { | |
translatesAutoresizingMaskIntoConstraints = false | |
heightAnchor.constraint(equalToConstant: constant).isActive = true | |
widthAnchor.constraint(equalToConstant: constant).isActive = true | |
} | |
func constraintSize(size: CGSize) { | |
translatesAutoresizingMaskIntoConstraints = false | |
heightAnchor.constraint(equalToConstant: size.height).isActive = true | |
widthAnchor.constraint(equalToConstant: size.width).isActive = true | |
} | |
func addSubviews(_ views: UIView...) { | |
views.forEach { addSubview($0) } | |
} | |
func addSubviews(_ views: [UIView]) { | |
views.forEach { addSubview($0) } | |
} | |
@discardableResult func withRadius(_ constant: CGFloat) -> Self { | |
cornerRadius = constant | |
return self | |
} | |
@discardableResult func withHeight(_ constant: CGFloat) -> Self { | |
constraintHeight(constant: constant) | |
return self | |
} | |
@discardableResult func withWidth(_ constant: CGFloat) -> Self { | |
constraintWidth(constant: constant) | |
return self | |
} | |
@discardableResult func withSize(width: CGFloat, height: CGFloat) -> Self { | |
constraintSize(height: height, width: width) | |
return self | |
} | |
@discardableResult func withSize(_ size: CGFloat) -> Self { | |
constraintSize(height: size, width: size) | |
return self | |
} | |
@discardableResult func withSize(_ size: CGSize) -> Self { | |
constraintSize(height: size.height, width: size.width) | |
return self | |
} | |
func addRoundCorners(_ corners: UIRectCorner, radius: CGFloat) { | |
if #available(iOS 11, *) { | |
self.clipsToBounds = true | |
self.layer.cornerRadius = radius | |
var masked = CACornerMask() | |
if corners.contains(.topLeft) { masked.insert(.layerMinXMinYCorner) } | |
if corners.contains(.topRight) { masked.insert(.layerMaxXMinYCorner) } | |
if corners.contains(.bottomLeft) { masked.insert(.layerMinXMaxYCorner) } | |
if corners.contains(.bottomRight) { masked.insert(.layerMaxXMaxYCorner) } | |
self.layer.maskedCorners = masked | |
} else { | |
let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) | |
let mask = CAShapeLayer() | |
mask.path = path.cgPath | |
layer.mask = mask | |
} | |
} | |
var cornerRadius: CGFloat { | |
get { | |
return layer.cornerRadius | |
} | |
set { | |
layer.cornerRadius = newValue | |
layer.masksToBounds = newValue > 0 | |
} | |
} | |
var borderWidth: CGFloat { | |
get { | |
return layer.borderWidth | |
} | |
set { | |
layer.borderWidth = newValue | |
} | |
} | |
var borderColor: UIColor? { | |
get { | |
return UIColor(cgColor: layer.borderColor ?? UIColor.clear.cgColor) | |
} | |
set { | |
layer.borderColor = newValue?.cgColor | |
} | |
} | |
func addTapGesture(action: @escaping () -> Void){ | |
let tap = UIViewTapGestureRecognizer(target: self , action: #selector(self.handleTap(_:))) | |
tap.action = action | |
tap.numberOfTapsRequired = 1 | |
self.addGestureRecognizer(tap) | |
self.isUserInteractionEnabled = true | |
} | |
@objc func handleTap(_ sender: UIViewTapGestureRecognizer) { | |
sender.action?() | |
} | |
func enableUserInteraction(_ enable: Bool = true, alpha: CGFloat = 1) { | |
isUserInteractionEnabled = enable | |
self.alpha = alpha | |
} | |
func breathe( | |
scaleX: CGFloat = 1.2, | |
scaleY: CGFloat = 1.2, | |
duration: Double = 0.7, | |
options: UIView.AnimationOptions = [.autoreverse, .repeat, .allowUserInteraction], | |
completion: VoidAction? | |
) { | |
UIView.animate(withDuration: duration, delay: 0, options: options) { | |
self.transform = CGAffineTransform(scaleX: scaleX, y: scaleY) | |
} completion: { _ in | |
completion?() | |
} | |
} | |
func stopBreathing() { | |
UIView.animate(withDuration: 0.5 ) { | |
self.transform = CGAffineTransform.identity | |
} | |
} | |
func onclickAnimation(scaleX: CGFloat = 0.94, scaleY: CGFloat = 1, duration: Double = 0.1) { | |
breathe(scaleX: scaleX, scaleY: scaleY, duration: duration, options: []) { [weak self] in | |
guard let self = self else { return } | |
self.stopBreathing() | |
} | |
} | |
func animateOnTap(completion: VoidAction? = nil) { | |
addTapGesture { | |
self.onclickAnimation() | |
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.15) { | |
completion?() | |
} | |
} | |
} | |
func applyShadow(radius: CGFloat = 5) { | |
layer.shadowColor = UIColor(red: 0.00, green: 0.00, blue: 0.00, alpha: 0.09).cgColor | |
layer.shadowOpacity = 0.8 | |
layer.shadowRadius = radius | |
layer.shadowOffset = .init(width: 0, height: radius) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment