Home telegram - navigationBackgroundNode
Post
Cancel

telegram - navigationBackgroundNode

protocol AnyValueProviding

1
var anyValue: ControlledTransitionProperty.AnyValue { get }

class ControlledTransitionProperty

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
final class AnyValue: Equatable, CustomStringConvertible {
    let value: Any
    let nsValue: NSValue
    let stringValue: () -> String
    let isEqual: (AnyValue) -> Bool
    let interpolate: (AnyValue, CGFloat) -> AnyValue

    init(
        value: Any,
        nsValue: NSValue,
        stringValue: @escaping () -> String,
        isEqual: @escaping (AnyValue) -> Bool,
        interpolate: @escaping (AnyValue, CGFloat) -> AnyValue
    ) {
        self.value = value
        self.nsValue = nsValue
        self.stringValue = stringValue
        self.isEqual = isEqual
        self.interpolate = interpolate
    }

    var description: String {
        return self.stringValue()
    }

    static func ==(lhs: AnyValue, rhs: AnyValue) -> Bool {
        if lhs.isEqual(rhs) {
            return true
        } else {
            return false
        }
    }
}

let layer: CALayer
let path: String
var fromValue: AnyValue
let toValue: AnyValue
private let completion: ((Bool) -> Void)?

init<T: Equatable>(layer: CALayer, path: String, fromValue: T, toValue: T, completion: ((Bool) -> Void)?) where T:
AnyValueProviding {
    self.layer = layer
    self.path = path
    self.fromValue = fromValue.anyValue
    self.toValue = toValue.anyValue
    self.completion = completion
    
    self.update(at: 0.0)
}

deinit {
    self.layer.removeAnimation(forKey: "MyCustomAnimation_\(Unmanaged.passUnretained(self).toOpaque())")
}

func update(at fraction: CGFloat) {
    let value = self.fromValue.interpolate(toValue, fraction)
    
    let animation = CABasicAnimation(keyPath: self.path)
    animation.speed = 0.0
    animation.beginTime = CACurrentMediaTime() + 1000.0
    animation.timeOffset = 0.01
    animation.duration = 1.0
    animation.fillMode = .both
    animation.fromValue = value.nsValue
    animation.toValue = value.nsValue
    animation.timingFunction = CAMediaTimingFunction(name: .linear)
    animation.isRemovedOnCompletion = false
    self.layer.add(animation, forKey: "MyCustomAnimation_\(Unmanaged.passUnretained(self).toOpaque())")
}

func complete(atEnd: Bool) {
    self.completion?(atEnd)
}

protocol ControlledTransitionAnimator

1
2
3
4
5
6
7
8
9
10
11
12
13
var duration: Double { get }

func startAnimation()
func setAnimationProgress(_ progress: CGFloat)
func finishAnimation()

func updateAlpha(layer: CALayer, alpha: CGFloat, completion: ((Bool) -> Void)?)
func updateScale(layer: CALayer, scale: CGFloat, completion: ((Bool) -> Void)?)
func animateScale(layer: CALayer, from fromValue: CGFloat, to toValue: CGFloat, completion: ((Bool) -> Void)?)
func updatePosition(layer: CALayer, position: CGPoint, completion: ((Bool) -> Void)?)
func updateBounds(layer: CALayer, bounds: CGRect, completion: ((Bool) -> Void)?)
func updateFrame(layer: CALayer, frame: CGRect, completion: ((Bool) -> Void)?)
func updateCornerRadius(layer: CALayer, cornerRadius: CGFloat, completion: ((Bool) -> Void)?)

class NavigationBackgroundNode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
private var _color: UIColor
private var enableBlur: Bool

private var effectView: UIVisualEffectView?
private let backgroundNode: ASDisplayNode

private var validLayout: (CGSize, CGFloat)?

public init(color: UIColor, enableBlur: Bool = true) {
    self._color = .clear
    self.enableBlur = enableBlur
    self.backgroundNode = ASDisplayNode()
    super.init()
    self.addSubnode(self.backgroundNode)
    self.updateColor(color: color, transition: .immediate)
}

public override func didLoad() {
    super.didLoad()
    
    if self.scheduledUpdate {
        self.scheduledUpdate = false
        self.updateBackgroundBlur(forceKeepBlur: false)
    }
}

private var scheduledUpdate = false

// update background blure
private func updateBackgroundBlur(forceKeepBlur: Bool) {
    guard self.isNodeLoaded else {
        self.scheduledUpdate = true
        return
    }
    if self.enableBlur && !sharedIsReduceTransparencyEnabled && ((self._color.alpha > .ulpOfOne && self._color.alpha < 0.95) || forceKeepBlur) {
        if self.effectView == nil {
            let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .light))

          // hack solution
            for subview in effectView.subviews {
                if subview.description.contains("VisualEffectSubview") {
                    subview.isHidden = true
                }
            }

            if let sublayer = effectView.layer.sublayers?[0], let filters = sublayer.filters {
                sublayer.backgroundColor = nil
                sublayer.isOpaque = false
                let allowedKeys: [String] = [
                    "colorSaturate",
                    "gaussianBlur"
                ]
                sublayer.filters = filters.filter { filter in
                    guard let filter = filter as? NSObject else {
                        return true
                    }
                    let filterName = String(describing: filter)
                    if !allowedKeys.contains(filterName) {
                        return false
                    }
                    return true
                }
            }

            if let (size, cornerRadius) = self.validLayout {
                effectView.frame = CGRect(origin: CGPoint(), size: size)
                ContainedViewLayoutTransition.immediate.updateCornerRadius(layer: effectView.layer, cornerRadius: cornerRadius)
                effectView.clipsToBounds = !cornerRadius.isZero
            }
            self.effectView = effectView
            self.view.insertSubview(effectView, at: 0)
        }
    } else if let effectView = self.effectView {
        self.effectView = nil
        effectView.removeFromSuperview()
    }
}

// update clolor and blur
public func updateColor(color: UIColor, enableBlur: Bool? = nil, forceKeepBlur: Bool = false, transition:
ContainedViewLayoutTransition) {
    let effectiveEnableBlur = enableBlur ?? self.enableBlur
    if self._color.isEqual(color) && self.enableBlur == effectiveEnableBlur {
        return
    }
    self._color = color
    self.enableBlur = effectiveEnableBlur
    if sharedIsReduceTransparencyEnabled {// without transparency
        transition.updateBackgroundColor(node: self.backgroundNode, color: self._color.withAlphaComponent(1.0))
    } else {
        transition.updateBackgroundColor(node: self.backgroundNode, color: self._color)
    }
    self.updateBackgroundBlur(forceKeepBlur: forceKeepBlur)
}

// update size, cornerRadius
public func update(size: CGSize, cornerRadius: CGFloat = 0.0, transition: ContainedViewLayoutTransition) {
    self.validLayout = (size, cornerRadius)
    let contentFrame = CGRect(origin: CGPoint(), size: size)
    transition.updateFrame(node: self.backgroundNode, frame: contentFrame, beginWithCurrentState: true)
    if let effectView = self.effectView, effectView.frame != contentFrame {
        transition.updateFrame(layer: effectView.layer, frame: contentFrame, beginWithCurrentState: true)
        if let sublayers = effectView.layer.sublayers {
            for sublayer in sublayers {
                transition.updateFrame(layer: sublayer, frame: contentFrame, beginWithCurrentState: true)
            }
        }
    }
    transition.updateCornerRadius(node: self.backgroundNode, cornerRadius: cornerRadius)
    if let effectView = self.effectView {
        transition.updateCornerRadius(layer: effectView.layer, cornerRadius: cornerRadius)
        effectView.clipsToBounds = !cornerRadius.isZero
    }
}

public func update(size: CGSize, cornerRadius: CGFloat = 0.0, animator: ControlledTransitionAnimator) {
    self.validLayout = (size, cornerRadius)
    let contentFrame = CGRect(origin: CGPoint(), size: size)
    animator.updateFrame(layer: self.backgroundNode.layer, frame: contentFrame, completion: nil)
    if let effectView = self.effectView, effectView.frame != contentFrame {
        animator.updateFrame(layer: effectView.layer, frame: contentFrame, completion: nil)
        if let sublayers = effectView.layer.sublayers {
            for sublayer in sublayers {
                animator.updateFrame(layer: sublayer, frame: contentFrame, completion: nil)
            }
        }
    }
    animator.updateCornerRadius(layer: self.backgroundNode.layer, cornerRadius: cornerRadius, completion: nil)
    if let effectView = self.effectView {
        animator.updateCornerRadius(layer: effectView.layer, cornerRadius: cornerRadius, completion: nil)
        effectView.clipsToBounds = !cornerRadius.isZero
    }
}
This post is licensed under CC BY 4.0 by the author.