@onekeyfe/react-native-skeleton 1.1.11 → 1.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -132,4 +132,8 @@ class HybridSkeleton(val context: ThemedReactContext) : HybridSkeletonSpec() {
132
132
  DEFAULT_GRADIENT_COLORS[0]
133
133
  }
134
134
  }
135
+
136
+ override fun dispose() {
137
+ stopShimmer()
138
+ }
135
139
  }
@@ -6,17 +6,22 @@ let DEFAULT_GRADIENT_COLORS: [UIColor] = [UIColor(red: 210.0/255.0, green: 210.0
6
6
 
7
7
  class HybridSkeleton : HybridSkeletonSpec {
8
8
 
9
- // Shimmer layer
9
+ // Shimmer layers
10
10
  private var shimmerLayer: CAGradientLayer?
11
+ private var skeletonLayer: CALayer?
11
12
  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?
12
17
 
13
18
  var shimmerGradientColors: [String]? {
14
19
  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()
20
25
  }
21
26
  }
22
27
  }
@@ -26,7 +31,11 @@ class HybridSkeleton : HybridSkeletonSpec {
26
31
 
27
32
  var shimmerSpeed: Double? {
28
33
  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
+ }
30
39
  }
31
40
  }
32
41
 
@@ -35,22 +44,72 @@ class HybridSkeleton : HybridSkeletonSpec {
35
44
  setupView()
36
45
  }
37
46
 
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
+
38
70
  private func setupView() {
39
71
  view.clipsToBounds = true
40
72
 
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
+
41
84
  // 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 }
43
87
  self.startShimmer()
44
88
  }
45
89
  }
46
90
 
47
91
  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
+
48
99
  stopShimmer()
49
100
 
50
101
  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
51
109
  // 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()
54
113
  }
55
114
  return
56
115
  }
@@ -59,12 +118,14 @@ class HybridSkeleton : HybridSkeletonSpec {
59
118
  let backgroundColor = colors[0].cgColor
60
119
  let highlightColor = colors[1].cgColor
61
120
 
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
67
127
 
128
+ // Create gradient layer for animation
68
129
  let gradientLayer = CAGradientLayer()
69
130
  gradientLayer.colors = [backgroundColor, highlightColor, backgroundColor]
70
131
  gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
@@ -72,13 +133,16 @@ class HybridSkeleton : HybridSkeletonSpec {
72
133
  gradientLayer.frame = view.bounds
73
134
  gradientLayer.name = "ShimmerLayer"
74
135
 
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
77
138
  view.layer.addSublayer(gradientLayer)
78
139
  view.clipsToBounds = true
79
140
 
141
+ // Store references
142
+ skeletonLayer = newSkeletonLayer
80
143
  shimmerLayer = gradientLayer
81
144
 
145
+ // Create animation
82
146
  let width = view.bounds.width
83
147
  let animation = CABasicAnimation(keyPath: "transform.translation.x")
84
148
  animation.duration = shimmerSpeed ?? 3
@@ -87,23 +151,48 @@ class HybridSkeleton : HybridSkeletonSpec {
87
151
  animation.repeatCount = .infinity
88
152
  animation.autoreverses = false
89
153
  animation.fillMode = CAMediaTimingFillMode.forwards
154
+ animation.isRemovedOnCompletion = false // Keep animation when completed
90
155
 
156
+ // Use weak self pattern for potential future animation delegates/completion handlers
91
157
  gradientLayer.add(animation, forKey: "shimmerAnimation")
92
158
  }
93
159
 
94
160
  func stopShimmer() {
95
- shimmerLayer?.removeAnimation(forKey: "shimmerAnimation")
161
+ // Remove animation from shimmer layer
162
+ shimmerLayer?.removeAllAnimations() // Remove all animations, more comprehensive
96
163
  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
97
179
  shimmerLayer = nil
180
+ skeletonLayer = nil
98
181
  }
99
182
 
100
183
  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
+ }
102
189
  }
103
190
 
104
191
  func restartShimmer() {
192
+ guard isActive else { return }
105
193
  stopShimmer()
106
- startShimmer()
194
+ retryCount = 0 // Reset retry count on restart
195
+ startShimmerInternal()
107
196
  }
108
197
 
109
198
  func hexStringToUIColor(hexColor: String) -> UIColor {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-skeleton",
3
- "version": "1.1.11",
3
+ "version": "1.1.12",
4
4
  "description": "react-native-skeleton",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",