Commit 062c21f8 by Daniel Dahan

editor: issue-588: removing Pulse animation memory leaks

parent f7dbffeb
......@@ -30,17 +30,17 @@
import UIKit
open class Button: UIButton {
open class Button: UIButton, Pulsable {
/**
A CAShapeLayer used to manage elements that would be affected by
the clipToBounds property of the backing layer. For example, this
allows the dropshadow effect on the backing layer, while clipping
the image to a desired shape within the visualLayer.
*/
open private(set) lazy var visualLayer = CAShapeLayer()
open internal(set) lazy var visualLayer = CAShapeLayer()
/// A Pulse reference.
internal private(set) lazy var pulse: Pulse = Pulse()
internal internal(set) lazy var pulse: Pulse = Pulse()
/// PulseAnimation value.
open var pulseAnimation: PulseAnimation {
......@@ -181,15 +181,15 @@ open class Button: UIButton {
*/
open func pulse(point: CGPoint? = nil) {
let p = nil == point ? CGPoint(x: CGFloat(width / 2), y: CGFloat(height / 2)) : point!
Motion.pulseExpandAnimation(layer: layer, visualLayer: visualLayer, point: p, width: width, height: height, pulse: &pulse)
var s = self
MotionPulseAnimation<Button>.pulseExpandAnimation(&s, point: p)
Motion.delay(time: 0.35) { [weak self] in
guard let s = self else {
guard var s = self else {
return
}
Motion.pulseContractAnimation(layer: s.layer, visualLayer: s.visualLayer, pulse: &s.pulse)
MotionPulseAnimation<Button>.pulseContractAnimation(&s)
}
bringImageViewToFront()
}
/**
......@@ -200,9 +200,8 @@ open class Button: UIButton {
*/
open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
Motion.pulseExpandAnimation(layer: layer, visualLayer: visualLayer, point: layer.convert(touches.first!.location(in: self), from: layer), width: width, height: height, pulse: &pulse)
bringImageViewToFront()
var s = self
MotionPulseAnimation<Button>.pulseExpandAnimation(&s, point: layer.convert(touches.first!.location(in: s), from: layer))
}
/**
......@@ -213,7 +212,8 @@ open class Button: UIButton {
*/
open override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
Motion.pulseContractAnimation(layer: layer, visualLayer: visualLayer, pulse: &pulse)
var s = self
MotionPulseAnimation<Button>.pulseContractAnimation(&s)
}
/**
......@@ -224,9 +224,10 @@ open class Button: UIButton {
*/
open override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesCancelled(touches, with: event)
Motion.pulseContractAnimation(layer: layer, visualLayer: visualLayer, pulse: &pulse)
var s = self
MotionPulseAnimation<Button>.pulseContractAnimation(&s)
}
open func bringImageViewToFront() {
guard let v = imageView else {
return
......
......@@ -31,17 +31,17 @@
import UIKit
@objc(CollectionReusableView)
open class CollectionReusableView: UICollectionReusableView {
open class CollectionReusableView: UICollectionReusableView, Pulsable {
/**
A CAShapeLayer used to manage elements that would be affected by
the clipToBounds property of the backing layer. For example, this
allows the dropshadow effect on the backing layer, while clipping
the image to a desired shape within the visualLayer.
*/
open private(set) lazy var visualLayer = CAShapeLayer()
open internal(set) lazy var visualLayer = CAShapeLayer()
/// A Pulse reference.
internal private(set) lazy var pulse: Pulse = Pulse()
internal internal(set) lazy var pulse: Pulse = Pulse()
/// PulseAnimation value.
open var pulseAnimation: PulseAnimation {
......@@ -235,12 +235,14 @@ open class CollectionReusableView: UICollectionReusableView {
*/
open func pulse(point: CGPoint? = nil) {
let p = nil == point ? CGPoint(x: CGFloat(width / 2), y: CGFloat(height / 2)) : point!
Motion.pulseExpandAnimation(layer: layer, visualLayer: visualLayer, point: p, width: width, height: height, pulse: &pulse)
var s = self
MotionPulseAnimation<CollectionReusableView>.pulseExpandAnimation(&s, point: p)
Motion.delay(time: 0.35) { [weak self] in
guard let s = self else {
guard var s = self else {
return
}
Motion.pulseContractAnimation(layer: s.layer, visualLayer: s.visualLayer, pulse: &s.pulse)
MotionPulseAnimation<CollectionReusableView>.pulseContractAnimation(&s)
}
}
......@@ -252,7 +254,8 @@ open class CollectionReusableView: UICollectionReusableView {
*/
open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
Motion.pulseExpandAnimation(layer: layer, visualLayer: visualLayer, point: layer.convert(touches.first!.location(in: self), from: layer), width: width, height: height, pulse: &pulse)
var s = self
MotionPulseAnimation<CollectionReusableView>.pulseExpandAnimation(&s, point: layer.convert(touches.first!.location(in: s), from: layer))
}
/**
......@@ -263,7 +266,8 @@ open class CollectionReusableView: UICollectionReusableView {
*/
open override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
Motion.pulseContractAnimation(layer: layer, visualLayer: visualLayer, pulse: &pulse)
var s = self
MotionPulseAnimation<CollectionReusableView>.pulseContractAnimation(&s)
}
/**
......@@ -274,7 +278,8 @@ open class CollectionReusableView: UICollectionReusableView {
*/
open override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesCancelled(touches, with: event)
Motion.pulseContractAnimation(layer: layer, visualLayer: visualLayer, pulse: &pulse)
var s = self
MotionPulseAnimation<CollectionReusableView>.pulseContractAnimation(&s)
}
/**
......
......@@ -31,17 +31,17 @@
import UIKit
@objc(CollectionViewCell)
open class CollectionViewCell: UICollectionViewCell {
open class CollectionViewCell: UICollectionViewCell, Pulsable {
/**
A CAShapeLayer used to manage elements that would be affected by
the clipToBounds property of the backing layer. For example, this
allows the dropshadow effect on the backing layer, while clipping
the image to a desired shape within the visualLayer.
*/
open private(set) lazy var visualLayer = CAShapeLayer()
open internal(set) lazy var visualLayer = CAShapeLayer()
/// A Pulse reference.
internal private(set) lazy var pulse: Pulse = Pulse()
internal internal(set) lazy var pulse: Pulse = Pulse()
/// PulseAnimation value.
open var pulseAnimation: PulseAnimation {
......@@ -242,12 +242,14 @@ open class CollectionViewCell: UICollectionViewCell {
*/
open func pulse(point: CGPoint? = nil) {
let p = nil == point ? CGPoint(x: CGFloat(width / 2), y: CGFloat(height / 2)) : point!
Motion.pulseExpandAnimation(layer: layer, visualLayer: visualLayer, point: p, width: width, height: height, pulse: &pulse)
var s = self
MotionPulseAnimation<CollectionViewCell>.pulseExpandAnimation(&s, point: p)
Motion.delay(time: 0.35) { [weak self] in
guard let s = self else {
guard var s = self else {
return
}
Motion.pulseContractAnimation(layer: s.layer, visualLayer: s.visualLayer, pulse: &s.pulse)
MotionPulseAnimation<CollectionViewCell>.pulseContractAnimation(&s)
}
}
......@@ -259,7 +261,8 @@ open class CollectionViewCell: UICollectionViewCell {
*/
open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
Motion.pulseExpandAnimation(layer: layer, visualLayer: visualLayer, point: layer.convert(touches.first!.location(in: self), from: layer), width: width, height: height, pulse: &pulse)
var s = self
MotionPulseAnimation<CollectionViewCell>.pulseExpandAnimation(&s, point: layer.convert(touches.first!.location(in: s), from: layer))
}
/**
......@@ -270,7 +273,8 @@ open class CollectionViewCell: UICollectionViewCell {
*/
open override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
Motion.pulseContractAnimation(layer: layer, visualLayer: visualLayer, pulse: &pulse)
var s = self
MotionPulseAnimation<CollectionViewCell>.pulseContractAnimation(&s)
}
/**
......@@ -281,7 +285,8 @@ open class CollectionViewCell: UICollectionViewCell {
*/
open override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesCancelled(touches, with: event)
Motion.pulseContractAnimation(layer: layer, visualLayer: visualLayer, pulse: &pulse)
var s = self
MotionPulseAnimation<CollectionViewCell>.pulseContractAnimation(&s)
}
/**
......
......@@ -38,7 +38,7 @@ open class Layer: CAShapeLayer {
allows the dropshadow effect on the backing layer, while clipping
the image to a desired shape within the visualLayer.
*/
open private(set) lazy var visualLayer = CAShapeLayer()
open internal(set) lazy var visualLayer = CAShapeLayer()
/**
A property that manages an image for the visualLayer's contents
......
......@@ -30,7 +30,7 @@
import UIKit
internal class MaterialLayer {
internal struct MaterialLayer {
/// A reference to the CALayer.
internal weak var layer: CALayer?
......
......@@ -30,6 +30,11 @@
import UIKit
internal protocol Pulsable {
var pulse: Pulse { get set }
var visualLayer: CAShapeLayer { get set }
}
internal struct Pulse {
/// An Array of layers.
internal lazy var layers = [CAShapeLayer]()
......
......@@ -42,62 +42,61 @@ public enum PulseAnimation: Int {
case pointWithBacking
}
internal extension Motion {
internal struct MotionPulseAnimation<T: UIView> where T: Pulsable {
/**
Triggers the expanding animation.
- Parameter layer: Container CALayer.
- Parameter visualLayer: A CAShapeLayer.
- Parameter _ view: A Reference to the view to add the
animations too.
- Parameter point: A point to pulse from.
- Parameter width: Container width.
- Parameter height: Container height.
- Parameter duration: Animation duration.
- Parameter pulse: A Pulse instance.
*/
internal static func pulseExpandAnimation(layer: CALayer, visualLayer: CALayer, point: CGPoint, width: CGFloat, height: CGFloat, pulse: inout Pulse) {
guard .none != pulse.animation else {
internal static func pulseExpandAnimation(_ view: inout T, point: CGPoint) {
guard .none != view.pulse.animation else {
return
}
let n = .center == pulse.animation ? width < height ? width : height : width < height ? height : width
let w = view.width
let h = view.height
let n = .center == view.pulse.animation ? w < h ? w : h : w < h ? h : w
let bLayer = CAShapeLayer()
let pLayer = CAShapeLayer()
bLayer.addSublayer(pLayer)
pulse.layers.insert(bLayer, at: 0)
visualLayer.addSublayer(bLayer)
view.pulse.layers.insert(bLayer, at: 0)
view.visualLayer.addSublayer(bLayer)
bLayer.zPosition = 0
pLayer.zPosition = 0
visualLayer.masksToBounds = !(.centerRadialBeyondBounds == pulse.animation || .radialBeyondBounds == pulse.animation)
view.visualLayer.masksToBounds = !(.centerRadialBeyondBounds == view.pulse.animation || .radialBeyondBounds == view.pulse.animation)
Motion.disable(animations: { [visualLayer = visualLayer, pulse = pulse] in
bLayer.frame = visualLayer.bounds
Motion.disable(animations: { [view = view] in
bLayer.frame = view.visualLayer.bounds
pLayer.bounds = CGRect(x: 0, y: 0, width: n, height: n)
switch pulse.animation {
switch view.pulse.animation {
case .center, .centerWithBacking, .centerRadialBeyondBounds:
pLayer.position = CGPoint(x: width / 2, y: height / 2)
pLayer.position = CGPoint(x: w / 2, y: h / 2)
default:
pLayer.position = point
}
pLayer.cornerRadius = n / 2
pLayer.backgroundColor = pulse.color.withAlphaComponent(pulse.opacity).cgColor
pLayer.backgroundColor = view.pulse.color.withAlphaComponent(view.pulse.opacity).cgColor
pLayer.transform = CATransform3DMakeAffineTransform(CGAffineTransform(scaleX: 0, y: 0))
})
bLayer.setValue(false, forKey: "animated")
let duration: CFTimeInterval = .center == pulse.animation ? 0.16125 : 0.325
let duration: CFTimeInterval = .center == view.pulse.animation ? 0.16125 : 0.325
switch pulse.animation {
switch view.pulse.animation {
case .centerWithBacking, .backing, .pointWithBacking:
bLayer.add(Motion.backgroundColor(color: pulse.color.withAlphaComponent(pulse.opacity / 2), duration: duration), forKey: nil)
bLayer.add(Motion.backgroundColor(color: view.pulse.color.withAlphaComponent(view.pulse.opacity / 2), duration: duration), forKey: nil)
default:break
}
switch pulse.animation {
switch view.pulse.animation {
case .center, .centerWithBacking, .centerRadialBeyondBounds, .radialBeyondBounds, .point, .pointWithBacking:
pLayer.add(Motion.scale(by: 1, duration: duration), forKey: nil)
default:break
......@@ -110,12 +109,12 @@ internal extension Motion {
/**
Triggers the contracting animation.
- Parameter layer: Container CALayer.
- Parameter visualLayer: A CAShapeLayer.
- Parameter _ view: A Reference to the view to add the
animations too.
- Parameter pulse: A Pulse instance.
*/
internal static func pulseContractAnimation(layer: CALayer, visualLayer: CALayer, pulse: inout Pulse) {
guard let bLayer = pulse.layers.popLast() else {
internal static func pulseContractAnimation(_ view: inout T) {
guard let bLayer = view.pulse.layers.popLast() else {
return
}
......@@ -123,24 +122,24 @@ internal extension Motion {
return
}
Motion.delay(time: animated ? 0 : 0.15) { [pulse = pulse] in
Motion.delay(time: animated ? 0 : 0.15) { [view = view] in
guard let pLayer = bLayer.sublayers?.first as? CAShapeLayer else {
return
}
let duration = 0.325
switch pulse.animation {
switch view.pulse.animation {
case .centerWithBacking, .backing, .pointWithBacking:
bLayer.add(Motion.backgroundColor(color: pulse.color.withAlphaComponent(0), duration: duration), forKey: nil)
bLayer.add(Motion.backgroundColor(color: view.pulse.color.withAlphaComponent(0), duration: duration), forKey: nil)
default:break
}
switch pulse.animation {
switch view.pulse.animation {
case .center, .centerWithBacking, .centerRadialBeyondBounds, .radialBeyondBounds, .point, .pointWithBacking:
pLayer.add(Motion.animate(group: [
Motion.scale(by: .center == pulse.animation ? 1 : 1.325),
Motion.backgroundColor(color: pulse.color.withAlphaComponent(0))
Motion.scale(by: .center == view.pulse.animation ? 1 : 1.325),
Motion.backgroundColor(color: view.pulse.color.withAlphaComponent(0))
], duration: duration), forKey: nil)
default:break
}
......
......@@ -30,9 +30,9 @@
import UIKit
open class PulseView: View {
open class PulseView: View, Pulsable {
/// A Pulse reference.
internal private(set) lazy var pulse: Pulse = Pulse()
internal internal(set) lazy var pulse: Pulse = Pulse()
/// PulseAnimation value.
open var pulseAnimation: PulseAnimation {
......@@ -66,52 +66,57 @@ open class PulseView: View {
}
}
/**
Triggers the pulse animation.
- Parameter point: A Optional point to pulse from, otherwise pulses
from the center.
*/
open func pulse(point: CGPoint? = nil) {
/**
Triggers the pulse animation.
- Parameter point: A Optional point to pulse from, otherwise pulses
from the center.
*/
open func pulse(point: CGPoint? = nil) {
let p = nil == point ? CGPoint(x: CGFloat(width / 2), y: CGFloat(height / 2)) : point!
Motion.pulseExpandAnimation(layer: layer, visualLayer: visualLayer, point: p, width: width, height: height, pulse: &pulse)
Motion.delay(time: 0.35) { [weak self] in
guard let s = self else {
var s = self
MotionPulseAnimation<PulseView>.pulseExpandAnimation(&s, point: p)
Motion.delay(time: 0.35) { [weak self] in
guard var s = self else {
return
}
Motion.pulseContractAnimation(layer: s.layer, visualLayer: s.visualLayer, pulse: &s.pulse)
}
}
/**
MotionPulseAnimation<PulseView>.pulseContractAnimation(&s)
}
}
/**
A delegation method that is executed when the view has began a
touch event.
- Parameter touches: A set of UITouch objects.
- Parameter event: A UIEvent object.
*/
open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
Motion.pulseExpandAnimation(layer: layer, visualLayer: visualLayer, point: layer.convert(touches.first!.location(in: self), from: layer), width: width, height: height, pulse: &pulse)
}
/**
open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
var s = self
MotionPulseAnimation<PulseView>.pulseExpandAnimation(&s, point: layer.convert(touches.first!.location(in: s), from: layer))
}
/**
A delegation method that is executed when the view touch event has
ended.
- Parameter touches: A set of UITouch objects.
- Parameter event: A UIEvent object.
*/
open override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
Motion.pulseContractAnimation(layer: layer, visualLayer: visualLayer, pulse: &pulse)
}
/**
open override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
var s = self
MotionPulseAnimation<PulseView>.pulseContractAnimation(&s)
}
/**
A delegation method that is executed when the view touch event has
been cancelled.
- Parameter touches: A set of UITouch objects.
- Parameter event: A UIEvent object.
*/
open override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesCancelled(touches, with: event)
Motion.pulseContractAnimation(layer: layer, visualLayer: visualLayer, pulse: &pulse)
}
open override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesCancelled(touches, with: event)
var s = self
MotionPulseAnimation<PulseView>.pulseContractAnimation(&s)
}
}
......@@ -30,17 +30,17 @@
import UIKit
open class TableViewCell: UITableViewCell {
open class TableViewCell: UITableViewCell, Pulsable {
/**
A CAShapeLayer used to manage elements that would be affected by
the clipToBounds property of the backing layer. For example, this
allows the dropshadow effect on the backing layer, while clipping
the image to a desired shape within the visualLayer.
*/
open private(set) lazy var visualLayer = CAShapeLayer()
open internal(set) lazy var visualLayer = CAShapeLayer()
/// A Pulse reference.
internal private(set) lazy var pulse: Pulse = Pulse()
internal internal(set) lazy var pulse: Pulse = Pulse()
/// PulseAnimation value.
open var pulseAnimation: PulseAnimation {
......@@ -119,12 +119,14 @@ open class TableViewCell: UITableViewCell {
*/
open func pulse(point: CGPoint? = nil) {
let p = nil == point ? CGPoint(x: CGFloat(width / 2), y: CGFloat(height / 2)) : point!
Motion.pulseExpandAnimation(layer: layer, visualLayer: visualLayer, point: p, width: width, height: height, pulse: &pulse)
var s = self
MotionPulseAnimation<TableViewCell>.pulseExpandAnimation(&s, point: p)
Motion.delay(time: 0.35) { [weak self] in
guard let s = self else {
guard var s = self else {
return
}
Motion.pulseContractAnimation(layer: s.layer, visualLayer: s.visualLayer, pulse: &s.pulse)
MotionPulseAnimation<TableViewCell>.pulseContractAnimation(&s)
}
}
......@@ -136,7 +138,8 @@ open class TableViewCell: UITableViewCell {
*/
open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
Motion.pulseExpandAnimation(layer: layer, visualLayer: visualLayer, point: layer.convert(touches.first!.location(in: self), from: layer), width: width, height: height, pulse: &pulse)
var s = self
MotionPulseAnimation<TableViewCell>.pulseExpandAnimation(&s, point: layer.convert(touches.first!.location(in: s), from: layer))
}
/**
......@@ -147,7 +150,8 @@ open class TableViewCell: UITableViewCell {
*/
open override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
Motion.pulseContractAnimation(layer: layer, visualLayer: visualLayer, pulse: &pulse)
var s = self
MotionPulseAnimation<TableViewCell>.pulseContractAnimation(&s)
}
/**
......@@ -158,7 +162,8 @@ open class TableViewCell: UITableViewCell {
*/
open override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesCancelled(touches, with: event)
Motion.pulseContractAnimation(layer: layer, visualLayer: visualLayer, pulse: &pulse)
var s = self
MotionPulseAnimation<TableViewCell>.pulseContractAnimation(&s)
}
/**
......
......@@ -37,7 +37,7 @@ open class View: UIView {
allows the dropshadow effect on the backing layer, while clipping
the image to a desired shape within the visualLayer.
*/
open fileprivate(set) lazy var visualLayer = CAShapeLayer()
open internal(set) lazy var visualLayer = CAShapeLayer()
/**
A property that manages an image for the visualLayer's contents
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment