首先写个空的协议
protocol Pulseable {}
然后,扩展Pulseable协议
在这里,说明一下。一般我们在为协议添加,实现默认参数时,有一个坑,需要注意下:
protocol AAAA {
var name: String { get set }
var label: String { get set }
}
通常我们会这样给AAAA协议添加默认参数name。这种做法在处理状态时,即返回的描述物体的状态值---(Int,Double,枚举值....),这是没有任何问题的。
extension AAAA {
var name: String {
return "Sarla"
}
但是下面这种,返回的对象类型时,会出现不可预估的错误---即每次用到(.label)的label都是不一样的。
extension AAAA {
var lable: UILabel {
return UILabel()
}
}
这是因为,我们只给它实现了get,而每次get的时候,都是直接生成一个新的UILabel对象,然后返回的。
我们可以打印下对象地址查看下,每次用到的时候是不一样的
print(Unmanaged.passUnretained(label).toOpaque())
正确的返回对象默认实现是下面这种的
private var pulseLayerKey: Void?
private let externalLayerName = "YGLayerName"
private let pulseKey = "PulseKey"
private let radarKey = "RadarKey"
extension Pulseable where Self: UIView {
/// 动画layer
var animationLayer: CALayer {
get {
if let object = objc_getAssociatedObject(self, &pulseLayerKey) as? CALayer {
return object
}
let externalBorder = CALayer()
externalBorder.frame = frame
externalBorder.cornerRadius = layer.cornerRadius
externalBorder.name = externalLayerName
objc_setAssociatedObject(self, &pulseLayerKey, externalBorder, .OBJC_ASSOCIATION_RETAIN)
return externalBorder
}
set {
objc_setAssociatedObject(self, &pulseLayerKey, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
/// 添加脉冲动画
///
/// - Parameters:
/// - color: 外层颜色
/// - startScale: 动画起始值
/// - finishScale: 动画到达值---默认是1.4
/// - frequency: 频率---默认是1.0
/// - opacity: 外层透明度---默认是0.7
/// - mode: 动画类型
@discardableResult
func addPulse(with color: UIColor, startScale: CGFloat, finishScale: CGFloat = 1.4, frequency: CFTimeInterval = 1.0, opacity: Float = 0.7, mode: PulseAnimationMode) -> Self {
animationLayer.backgroundColor = color.cgColor
animationLayer.opacity = opacity
layer.masksToBounds = false
layer.superlayer?.insertSublayer(animationLayer, below: layer)
let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
scaleAnimation.fromValue = startScale
scaleAnimation.toValue = finishScale
scaleAnimation.autoreverses = mode == .regular
scaleAnimation.duration = frequency
scaleAnimation.repeatCount = Float.greatestFiniteMagnitude
animationLayer.add(scaleAnimation, forKey: pulseKey)
if mode == .radar {
let opacityAnimation = CABasicAnimation(keyPath: "opacity")
opacityAnimation.fromValue = opacity
opacityAnimation.toValue = 0.0
opacityAnimation.autoreverses = false
opacityAnimation.duration = frequency
opacityAnimation.repeatCount = Float.greatestFiniteMagnitude
animationLayer.add(opacityAnimation, forKey: radarKey)
}
// 这里先不让动画启动
animationLayer.speed = 0.0
return self
}
/// 恢复/启动动画
func resumePulse() {
let pausedTime = animationLayer.timeOffset
animationLayer.speed = 1.0
animationLayer.timeOffset = 0.0
animationLayer.beginTime = 0.0
let timeSincePauce = animationLayer.convertTime(CACurrentMediaTime(), from: nil) - pausedTime
animationLayer.beginTime = timeSincePauce
}
/// 暂停动画
func suspendPulse() {
let pausedTime = animationLayer.convertTime(CACurrentMediaTime(), from: nil)
animationLayer.speed = 0.0
animationLayer.timeOffset = pausedTime
}
/// 移除动画
func cancelPause() {
layer.removeAnimation(forKey: pulseKey)
layer.removeAnimation(forKey: radarKey)
animationLayer.removeFromSuperlayer()
}
}
关于swift的链式写法---非常简单,只要方法返回Self(或者对象)就行,类似与下面的写法
@discardableResult
func aaa() -> Self {
`doing some`
return self
}
最后让UIView遵守协议
extension UIView: Pulseable {}
最终调用api效果
- 添加并启动动画
button.addPulse(with: #colorLiteral(red: 0.4666666687, green: 0.7647058964, blue: 0.2666666806, alpha: 1), mode: .radar).resumePulse()
2.暂停动画
button.suspendPulse()
3.移除动画
button.cancelPause()