I like animation a lot. Sometimes we would like to animate some complex movement of objects, and writing animation from the scratch can be a time-consuming process. As result, we can use an animated image - gif.
There are a lot of engines (free and paid) like Lottie that can help a lot within playing such a format. But, sometimes we don’t want to use a plane for crossing a road, and a small and elegant solution can help a lot.
Luckily for us, iOS has a quite good engine (finally!) that can help a lot within gif - ImageIO.
Withs ImageIO we have all the tools we need for such operations - we can do everything on our own, or use one of the available func for automated gif animation.
automated animations come into play starting from iOS 13
Manual animating
This option requires a bit more work from our side, but, at the same moment, we control every aspect of the process. This is great.
Of cause, we will work with CoreGraphics objects such as CGImage, CFData, CGImageSource, and others. Be ready to make your hands dirty - as usual, with great functionality, Apple gives poor documentation, so we will dive into the code and experiment a bit.
We can start by getting data that contains gif information. This data can be obtained in different ways - from a network or disk. Let’s start by assuming that we have our gif on the hard drive. So all that needs to be done - read the data of the gif file as Data and convert it into CFData (simple case as CFData), then, we can create CGImageSource - object that abstract the data-reading task:
Now, using CGImageSource, we can get all required information - duration and frames. To do so, we should find the number of frames
then, for each frame we should get CGImage by calling CGImageSourceCreateImageAtIndex and duration for frame. To get duration, we should read properties from data and find the desired key with a value.
With this information, we have all the components for proper animation. The question - is how to do this animation. One way - is to calculate the total gif duration and use UIImage type method animatedImage(with:duration:).
The possible code for this may look like next:
UIKit
The logic part is completed. But We still should somehow display this on UI. In UIKit, we can simply create UIImageView and set an image for it:
UIKit is great, but, now we have a deal with SwiftUI. The naive approach for using the code above can be a simple View that conforms to UIViewRepresentable:
This approach works, but, we can’t resize the gif, and we can’t control animating speed. So such an approach is not very useful.
SwiftUI
A good solution for us should provide an option to be used in SwiftUI and provide an option to control its speed.
First of all, we should wrap the logic, related to extracting info from gif data into a separate component GifDataProvider that can extract all required data as a simple model GifData:
The next step - is to create a ViewModifier, that can animate change of the image using data from a provider. To make something animatable, we can use Animatable protocol.
If we use ViewModifier and Animatable we can simply use AnimatableModifier. To create the one, that can animate images from the gif, we need a few components: images, duration, and progress. Using these 3 components, we can animate change of images:
.resizable() is needed for correctly responds to frame change
And the last components - is a View that wraps for our usage of this GifAnimatableModifier:
Here u can see a small trick, that allows repeat animation forever (as gifs do). I also add additional Duration enum, that helps to customize the playing duration of the gif.
The final usage will be next:
And the result:
We also has an option to control speed of the gif:
Looks good. We of cause can improve a bit the process of timing - for now, I assumed that the delay between each frame is the same. But, in the real world, this can be different. In this case, we should modify our GifDataProvider and provide pair of images and delay for each frame. Of cause, in this case, the logic of frame selection in AnimatedModifier will bring some additional complexity.
Automated animations
The good news is that starting from iOS 13 ImageIO has additional tools, that allow doing part of the work above automatically.
The interesting part for use is placed in ImageIO.CGImageAnimation header. There u can find CGAnimateImageData... functions that are specifically created for animating gif and apng formats. They also allow pausing the animation.
The initial part of the work is still the same - we should obtain gif data and then, process it. The processing can be done as follow:
where dictionary() - is a set of settings needed for animation:
We can pass nil as options - if no options are provided - then, a default will be used.
The complete code for this gif animator is next:
Now, we should somehow observe the changes produced by CGAnimateImageDataWithBlock. And here, Combine can be used:
And of cause, we can wrap it into reusable SwiftUIView: