Skip to content

Instantly share code, notes, and snippets.

@p-larson
Created June 14, 2019 01:13
Show Gist options
  • Save p-larson/ca23d9596f2979effd9cc8fa9e73c893 to your computer and use it in GitHub Desktop.
Save p-larson/ca23d9596f2979effd9cc8fa9e73c893 to your computer and use it in GitHub Desktop.
OverlayLayer.swift Fix
import UIKit
// Bug Recreation Playground
// Peter Larson
// Util
extension UIColor {
func lighter(by percentage: CGFloat = 25.0) -> UIColor {
return self.adjust(by: abs(percentage) ) ?? self
}
func darker(by percentage: CGFloat = 25.0) -> UIColor {
return self.adjust(by: -abs(percentage)) ?? self
}
func adjust(by percentage: CGFloat = 30.0) -> UIColor? {
var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0
if self.getRed(&red, green: &green, blue: &blue, alpha: &alpha) {
return UIColor(
red: min(red + percentage/100, 1.0),
green: min(green + percentage/100, 1.0),
blue: min(blue + percentage/100, 1.0),
alpha: alpha)
} else {
return nil
}
}
}
final public class OverlayLayer: CALayer {
@objc var color: UIColor = #colorLiteral(red: 0.9607843161, green: 0.7058823705, blue: 0.200000003, alpha: 1) {
didSet {
self.setNeedsDisplay()
}
}
@objc public var defaultShadowHeight: CGFloat = 12.0
@objc public var shadowHeight: CGFloat = 12.0
init(frame: CGRect, color: UIColor) {
super.init()
self.frame = frame
self.color = color
self.common()
}
public override init(layer: Any) {
super.init(layer: layer)
self.common()
guard let overlay = layer as? OverlayLayer else {
return
}
self.color = overlay.color
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.common()
}
public override init() {
super.init()
self.common()
}
private func common() {
self.drawsAsynchronously = true
self.setNeedsDisplay()
}
public func animate(pressed: Bool, duration: TimeInterval = 0.5) {
let animation = CABasicAnimation(keyPath: #keyPath(OverlayLayer.shadowHeight))
animation.toValue = pressed ? 0.0 : defaultShadowHeight
animation.fromValue = pressed ? shadowHeight : 0.0
animation.timingFunction = CAMediaTimingFunction(name: .easeOut)
animation.duration = duration
self.shadowHeight = animation.toValue as! CGFloat
self.add(animation, forKey: "press")
}
override public class func needsDisplay(forKey key: String) -> Bool {
if key == #keyPath(OverlayLayer.shadowHeight) {
return true
}
return super.needsDisplay(forKey: key)
}
public override func draw(in ctx: CGContext) {
let shadowBackground = CGRect.init(origin: .zero, size: .init(width: frame.width, height: frame.height))
let fillBackground = CGRect.init(origin: .zero, size: .init(width: frame.width, height: frame.height - shadowHeight))
ctx.setFillColor(self.color.darker().cgColor)
ctx.addPath(UIBezierPath(roundedRect: shadowBackground, cornerRadius: cornerRadius).cgPath)
ctx.fillPath()
ctx.setFillColor(self.color.cgColor)
ctx.closePath()
ctx.beginPath()
ctx.addPath(UIBezierPath(roundedRect: fillBackground, cornerRadius: cornerRadius).cgPath)
ctx.fillPath()
super.draw(in: ctx)
}
}
final public class TestView: UIView {
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
overlayLayer?.animate(pressed: true)
}
public override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
overlayLayer?.animate(pressed: false)
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
public override init(frame: CGRect) {
super.init(frame: frame)
}
override public class var layerClass: AnyClass {
return OverlayLayer.self
}
public var overlayLayer: OverlayLayer? {
return layer as? OverlayLayer
}
public init(frame: CGRect, color: UIColor) {
super.init(frame: frame)
// color not being set in layer?
overlayLayer?.color = color
}
}
let view = TestView(frame: CGRect(origin: .zero, size: .init(width: 250, height: 250)), color: #colorLiteral(red: 0.3411764801, green: 0.6235294342, blue: 0.1686274558, alpha: 1))
// The first display uses the correct color, but when the animation is run after being triggered by user interaction,
// it uses the default value instead of what should be the current value.
import PlaygroundSupport
PlaygroundPage.current.liveView = view
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment