@@ -6,17 +6,22 @@ let DEFAULT_GRADIENT_COLORS: [UIColor] = [UIColor(red: 210.0/255.0, green: 210.0
66
77class HybridSkeleton : HybridSkeletonSpec {
88
9- // Shimmer layer
9+ // Shimmer layers
1010 private var shimmerLayer : CAGradientLayer ?
11+ private var skeletonLayer : CALayer ?
1112 private var customGradientColors : [ UIColor ] ?
13+ private var isActive : Bool = true
14+ private var retryCount : Int = 0
15+ private let maxRetryCount : Int = 10
16+ private var viewObserver : NSKeyValueObservation ?
1217
1318 var shimmerGradientColors : [ String ] ? {
1419 didSet {
15- Task {
16- customGradientColors = shimmerGradientColors ? . map { hexStringToUIColor ( hexColor : $0 ) }
17- await MainActor . run {
18- restartShimmer ( )
19- }
20+ guard isActive else { return }
21+ DispatchQueue . main . async { [ weak self ] in
22+ guard let self = self , self . isActive else { return }
23+ self . customGradientColors = shimmerGradientColors ? . map { self . hexStringToUIColor ( hexColor : $0 ) }
24+ self . restartShimmer ( )
2025 }
2126 }
2227 }
@@ -26,7 +31,11 @@ class HybridSkeleton : HybridSkeletonSpec {
2631
2732 var shimmerSpeed : Double ? {
2833 didSet {
29- restartShimmer ( )
34+ guard isActive else { return }
35+ DispatchQueue . main. async { [ weak self] in
36+ guard let self = self , self . isActive else { return }
37+ self . restartShimmer ( )
38+ }
3039 }
3140 }
3241
@@ -35,22 +44,72 @@ class HybridSkeleton : HybridSkeletonSpec {
3544 setupView ( )
3645 }
3746
47+ deinit {
48+ // Mark as inactive to prevent any new operations
49+ isActive = false
50+
51+ // Clean up observer
52+ viewObserver? . invalidate ( )
53+ viewObserver = nil
54+
55+ // Ensure all cleanup happens on main thread synchronously
56+ if Thread . isMainThread {
57+ stopShimmer ( )
58+ } else {
59+ DispatchQueue . main. sync {
60+ self . stopShimmer ( )
61+ }
62+ }
63+
64+ // Clear view reference and ensure no dangling animations
65+ view. layer. removeAllAnimations ( )
66+ view. layer. sublayers? . removeAll ( )
67+ view. layer. mask = nil
68+ }
69+
3870 private func setupView( ) {
3971 view. clipsToBounds = true
4072
73+ // Observe view hierarchy changes for cleanup
74+ viewObserver = view. observe ( \. superview, options: [ . new] ) { [ weak self] view, change in
75+ guard let self = self else { return }
76+ if change. newValue == nil {
77+ // View was removed from hierarchy, clean up
78+ DispatchQueue . main. async { [ weak self] in
79+ self ? . stopShimmer ( )
80+ }
81+ }
82+ }
83+
4184 // Start animation when view is ready
42- DispatchQueue . main. async {
85+ DispatchQueue . main. async { [ weak self] in
86+ guard let self = self , self . isActive else { return }
4387 self . startShimmer ( )
4488 }
4589 }
4690
4791 func startShimmer( ) {
92+ retryCount = 0 // Reset retry count when starting new shimmer
93+ startShimmerInternal ( )
94+ }
95+
96+ private func startShimmerInternal( ) {
97+ guard isActive else { return }
98+
4899 stopShimmer ( )
49100
50101 guard !view. bounds. isEmpty else {
102+ // Check if we have exceeded max retry count
103+ guard retryCount < maxRetryCount else {
104+ print ( " ⚠️ Skeleton shimmer: Max retry count reached. View bounds are still empty. " )
105+ return
106+ }
107+
108+ retryCount += 1
51109 // Retry after a short delay if bounds are not ready
52- DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.1 ) {
53- self . startShimmer ( )
110+ DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.1 ) { [ weak self] in
111+ guard let self = self , self . isActive else { return }
112+ self . startShimmerInternal ( )
54113 }
55114 return
56115 }
@@ -59,26 +118,31 @@ class HybridSkeleton : HybridSkeletonSpec {
59118 let backgroundColor = colors [ 0 ] . cgColor
60119 let highlightColor = colors [ 1 ] . cgColor
61120
62- let skeletonLayer = CALayer ( )
63- skeletonLayer. backgroundColor = backgroundColor
64- skeletonLayer. name = " SkeletonLayer "
65- skeletonLayer. anchorPoint = . zero
66- skeletonLayer. frame = view. bounds
121+ // Create skeleton layer for masking
122+ let newSkeletonLayer = CALayer ( )
123+ newSkeletonLayer. backgroundColor = backgroundColor
124+ newSkeletonLayer. name = " SkeletonLayer "
125+ newSkeletonLayer. anchorPoint = . zero
126+ newSkeletonLayer. frame = view. bounds
67127
128+ // Create gradient layer for animation
68129 let gradientLayer = CAGradientLayer ( )
69130 gradientLayer. colors = [ backgroundColor, highlightColor, backgroundColor]
70131 gradientLayer. startPoint = CGPoint ( x: 0.0 , y: 0.5 )
71132 gradientLayer. endPoint = CGPoint ( x: 1.0 , y: 0.5 )
72133 gradientLayer. frame = view. bounds
73134 gradientLayer. name = " ShimmerLayer "
74135
75- view . layer . mask = skeletonLayer
76- view. layer. addSublayer ( skeletonLayer )
136+ // Set mask and add gradient layer (don't add skeleton layer as sublayer)
137+ view. layer. mask = newSkeletonLayer
77138 view. layer. addSublayer ( gradientLayer)
78139 view. clipsToBounds = true
79140
141+ // Store references
142+ skeletonLayer = newSkeletonLayer
80143 shimmerLayer = gradientLayer
81144
145+ // Create animation
82146 let width = view. bounds. width
83147 let animation = CABasicAnimation ( keyPath: " transform.translation.x " )
84148 animation. duration = shimmerSpeed ?? 3
@@ -87,23 +151,48 @@ class HybridSkeleton : HybridSkeletonSpec {
87151 animation. repeatCount = . infinity
88152 animation. autoreverses = false
89153 animation. fillMode = CAMediaTimingFillMode . forwards
154+ animation. isRemovedOnCompletion = false // Keep animation when completed
90155
156+ // Use weak self pattern for potential future animation delegates/completion handlers
91157 gradientLayer. add ( animation, forKey: " shimmerAnimation " )
92158 }
93159
94160 func stopShimmer( ) {
95- shimmerLayer? . removeAnimation ( forKey: " shimmerAnimation " )
161+ // Remove animation from shimmer layer
162+ shimmerLayer? . removeAllAnimations ( ) // Remove all animations, more comprehensive
96163 shimmerLayer? . removeFromSuperlayer ( )
164+
165+ // Clear mask reference before setting to nil
166+ if view. layer. mask === skeletonLayer {
167+ view. layer. mask = nil
168+ }
169+
170+ // Clean up all sublayers that might be shimmer layers
171+ view. layer. sublayers? . forEach { layer in
172+ if layer. name == " ShimmerLayer " {
173+ layer. removeAllAnimations ( )
174+ layer. removeFromSuperlayer ( )
175+ }
176+ }
177+
178+ // Nil out references
97179 shimmerLayer = nil
180+ skeletonLayer = nil
98181 }
99182
100183 func afterUpdate( ) {
101- restartShimmer ( )
184+ guard isActive else { return }
185+ DispatchQueue . main. async { [ weak self] in
186+ guard let self = self , self . isActive else { return }
187+ self . restartShimmer ( )
188+ }
102189 }
103190
104191 func restartShimmer( ) {
192+ guard isActive else { return }
105193 stopShimmer ( )
106- startShimmer ( )
194+ retryCount = 0 // Reset retry count on restart
195+ startShimmerInternal ( )
107196 }
108197
109198 func hexStringToUIColor( hexColor: String ) -> UIColor {
0 commit comments