@@ -21,41 +21,54 @@ class BlurEffectView: UIVisualEffectView {
2121 }
2222
2323 func updateBlur( style: UIBlurEffect . Style , intensity: Double ) {
24+ // Skip expensive animator recreation when nothing changed.
25+ // During FlashList recycling, updateUIView fires on every layout pass
26+ // even when props are identical, causing jank (issue #100).
27+ guard style != self . blurStyle || intensity != self . intensity else { return }
2428 self . blurStyle = style
2529 self . intensity = intensity
2630 setupBlur ( )
2731 }
2832
33+ override func didMoveToWindow( ) {
34+ super. didMoveToWindow ( )
35+ guard window != nil else { return }
36+ // UIKit resumes paused CAAnimations when a view re-joins a window
37+ // (e.g. after modal dismiss + re-present). If the animation plays
38+ // toward its end state the blur drifts to full intensity. Re-pause
39+ // and re-set the fraction here to lock it back to our intended value.
40+ // pausesOnCompletion = true (set in setupBlur) ensures the animator
41+ // stays .active even if it reaches fraction 1.0, so this is always safe.
42+ animator? . pauseAnimation ( )
43+ animator? . fractionComplete = intensity
44+ }
45+
2946 private func setupBlur( ) {
30- // Clean up existing animator
31- if let animator = animator {
32- animator. stopAnimation ( true )
33- animator. finishAnimation ( at: . current)
47+ if let existing = animator, existing. state == . active {
48+ existing. stopAnimation ( true )
3449 }
3550 animator = nil
3651
37- // Reset effect
3852 effect = nil
3953
40- // Create new animator
41- animator = UIViewPropertyAnimator ( duration: 1 , curve: . linear)
42- animator? . addAnimations { [ weak self] in
54+ let newAnimator = UIViewPropertyAnimator ( duration: 1 , curve: . linear)
55+ newAnimator. addAnimations { [ weak self] in
4356 self ? . effect = UIBlurEffect ( style: self ? . blurStyle ?? . systemMaterial)
4457 }
45-
46- // Set intensity
47- animator ? . fractionComplete = intensity
48- // Stop the animation at the current state
49- DispatchQueue . main . async { [ weak self ] in
50- self ? . animator ? . stopAnimation ( true )
51- self ? . animator ? . finishAnimation ( at : . current )
52- }
58+ // pausesOnCompletion: if UIKit ever resumes and runs this to the end,
59+ // the animator stays .active (paused at 1.0) instead of going .inactive.
60+ // This guarantees didMoveToWindow can always call pauseAnimation() safely.
61+ newAnimator . pausesOnCompletion = true
62+ newAnimator . startAnimation ( )
63+ newAnimator . pauseAnimation ( )
64+ newAnimator . fractionComplete = intensity
65+ animator = newAnimator
5366 }
5467
5568 deinit {
56- guard let animator = animator, animator. state == . active else { return }
57- animator. stopAnimation ( true )
58- animator . finishAnimation ( at : . current )
69+ if let animator = animator, animator. state == . active {
70+ animator. stopAnimation ( true )
71+ }
5972 }
6073}
6174
0 commit comments