1
1
import UIKit
2
2
3
3
/// Responsible for parsing GIF data and decoding the individual frames.
4
+ @MainActor
4
5
public class Animator {
5
6
/// Total duration of one animation loop
6
7
var loopDuration : TimeInterval {
@@ -20,7 +21,7 @@ public class Animator {
20
21
private var displayLinkInitialized : Bool = false
21
22
22
23
/// A delegate responsible for displaying the GIF frames.
23
- private weak var delegate : ( any GIFAnimatable ) !
24
+ private weak var delegate : ( any GIFAnimatable ) ?
24
25
25
26
/// Callback for when all the loops of the animation are done (never called for infinite loops)
26
27
private var animationBlock : ( ( ) -> Void ) ? = nil
@@ -31,6 +32,7 @@ public class Animator {
31
32
/// Responsible for starting and stopping the animation.
32
33
private lazy var displayLink : CADisplayLink = { [ unowned self] in
33
34
self . displayLinkInitialized = true
35
+
34
36
let display = CADisplayLink (
35
37
target: DisplayLinkProxy ( target: self ) ,
36
38
selector: #selector( DisplayLinkProxy . onScreenUpdate)
@@ -71,7 +73,7 @@ public class Animator {
71
73
72
74
store. shouldChangeFrame ( with: displayLink. duration) {
73
75
if $0 {
74
- delegate. animatorHasNewFrame ( )
76
+ delegate? . animatorHasNewFrame ( )
75
77
if store. isLoopFinished, let loopBlock {
76
78
loopBlock ( )
77
79
}
@@ -88,8 +90,12 @@ public class Animator {
88
90
/// - parameter loopCount: Desired number of loops, <= 0 for infinite loop.
89
91
/// - parameter completionHandler: Completion callback function
90
92
func prepareForAnimation(
91
- withGIFNamed imageName: String , inBundle bundle: Bundle = . main, size: CGSize ,
92
- contentMode: UIView . ContentMode , loopCount: Int = 0 , completionHandler: ( ( ) -> Void ) ? = nil
93
+ withGIFNamed imageName: String ,
94
+ inBundle bundle: Bundle = . main,
95
+ size: CGSize ,
96
+ contentMode: UIView . ContentMode ,
97
+ loopCount: Int = 0 ,
98
+ completionHandler: ( @Sendable ( ) -> Void ) ? = nil
93
99
) {
94
100
guard let extensionRemoved = imageName. components ( separatedBy: " . " ) [ safe: 0 ] ,
95
101
let imagePath = bundle. url ( forResource: extensionRemoved, withExtension: " gif " ) ,
@@ -101,7 +107,8 @@ public class Animator {
101
107
size: size,
102
108
contentMode: contentMode,
103
109
loopCount: loopCount,
104
- completionHandler: completionHandler)
110
+ completionHandler: completionHandler
111
+ )
105
112
}
106
113
107
114
/// Prepares the animator instance for animation.
@@ -116,7 +123,7 @@ public class Animator {
116
123
size: CGSize ,
117
124
contentMode: UIView . ContentMode ,
118
125
loopCount: Int = 0 ,
119
- completionHandler: ( ( ) -> Void ) ? = nil
126
+ completionHandler: ( @ Sendable ( ) -> Void ) ? = nil
120
127
) {
121
128
frameStore = FrameStore (
122
129
data: imageData,
@@ -136,12 +143,18 @@ public class Animator {
136
143
displayLink. add ( to: . main, forMode: RunLoop . Mode. common)
137
144
}
138
145
139
- deinit {
140
- if displayLinkInitialized {
146
+ private func invalidateDisplayLink ( ) {
147
+ Task { [ displayLink ] in
141
148
displayLink. invalidate ( )
142
149
}
143
150
}
144
151
152
+ deinit {
153
+ MainActor . assumeIsolated {
154
+ invalidateDisplayLink ( )
155
+ }
156
+ }
157
+
145
158
/// Start animating.
146
159
func startAnimating( ) {
147
160
if frameStore? . isAnimatable ?? false {
@@ -164,9 +177,13 @@ public class Animator {
164
177
/// - parameter animationBlock: Callback for when all the loops of the animation are done (never called for infinite loops)
165
178
/// - parameter loopBlock: Callback for when a loop is done (at the end of each loop)
166
179
func animate(
167
- withGIFNamed imageName: String , size: CGSize , contentMode: UIView . ContentMode ,
168
- loopCount: Int = 0 , preparationBlock: ( ( ) -> Void ) ? = nil , animationBlock: ( ( ) -> Void ) ? = nil ,
169
- loopBlock: ( ( ) -> Void ) ? = nil
180
+ withGIFNamed imageName: String ,
181
+ size: CGSize ,
182
+ contentMode: UIView . ContentMode ,
183
+ loopCount: Int = 0 ,
184
+ preparationBlock: ( @Sendable ( ) -> Void ) ? = nil ,
185
+ animationBlock: ( @Sendable ( ) -> Void ) ? = nil ,
186
+ loopBlock: ( @Sendable ( ) -> Void ) ? = nil
170
187
) {
171
188
self . animationBlock = animationBlock
172
189
self . loopBlock = loopBlock
@@ -175,7 +192,8 @@ public class Animator {
175
192
size: size,
176
193
contentMode: contentMode,
177
194
loopCount: loopCount,
178
- completionHandler: preparationBlock)
195
+ completionHandler: preparationBlock
196
+ )
179
197
startAnimating ( )
180
198
}
181
199
@@ -189,9 +207,13 @@ public class Animator {
189
207
/// - parameter animationBlock: Callback for when all the loops of the animation are done (never called for infinite loops)
190
208
/// - parameter loopBlock: Callback for when a loop is done (at the end of each loop)
191
209
func animate(
192
- withGIFData imageData: Data , size: CGSize , contentMode: UIView . ContentMode , loopCount: Int = 0 ,
193
- preparationBlock: ( ( ) -> Void ) ? = nil , animationBlock: ( ( ) -> Void ) ? = nil ,
194
- loopBlock: ( ( ) -> Void ) ? = nil
210
+ withGIFData imageData: Data ,
211
+ size: CGSize ,
212
+ contentMode: UIView . ContentMode ,
213
+ loopCount: Int = 0 ,
214
+ preparationBlock: ( @Sendable ( ) -> Void ) ? = nil ,
215
+ animationBlock: ( @Sendable ( ) -> Void ) ? = nil ,
216
+ loopBlock: ( @Sendable ( ) -> Void ) ? = nil
195
217
) {
196
218
self . animationBlock = animationBlock
197
219
self . loopBlock = loopBlock
@@ -200,7 +222,8 @@ public class Animator {
200
222
size: size,
201
223
contentMode: contentMode,
202
224
loopCount: loopCount,
203
- completionHandler: preparationBlock)
225
+ completionHandler: preparationBlock
226
+ )
204
227
startAnimating ( )
205
228
}
206
229
@@ -232,5 +255,5 @@ private class DisplayLinkProxy {
232
255
init ( target: Animator ) { self . target = target }
233
256
234
257
/// Lets the target update the frame if needed.
235
- @objc func onScreenUpdate( ) { target? . updateFrameIfNeeded ( ) }
258
+ @MainActor @ objc func onScreenUpdate( ) { target? . updateFrameIfNeeded ( ) }
236
259
}
0 commit comments