I was hoping to find a Swift-based solution but there seems to be none. For whatever reason, both the AFathi's GIF generator and my APNG adaptation of it simply doesn't work. I suspect it has something to do with the UTType mess after kUTType was deprecated in iOS 15.
I went ahead and adapted the Obj-C solution by Radif Sharafullin at https://github.com/radif/MSSticker-Images. I've included the updated code that has been confirmed to be working with iOS 17.2 below.
The code is, as far as I can tell, basically the same as the Swift code, so I'm baffled as to why the Swift version doesn't work. If anyone figures out why, please let us know here! I won't mark this thread as having an answer since my stop-gap solution isn't Swift-based:
#import "mcbAnimatedImagePersister.h"
#import <ImageIO/ImageIO.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
@implementation mcbAnimatedImagePersister
+(instancetype)shared{
static mcbAnimatedImagePersister * instance=nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance=mcbAnimatedImagePersister.new;
});
return instance;
}
-(void)persistAnimatedImageSequenceGIF:(NSArray<UIImage *> *)images frameDelay:(CGFloat)frameDelay numberOfLoops:(NSInteger)numberOfLoops toURL:(NSURL *)toURL{
NSDictionary *fileProperties = @{
(__bridge id)kCGImagePropertyGIFDictionary: @{
(__bridge id) kCGImagePropertyGIFLoopCount: @(numberOfLoops),
}
};
NSDictionary *frameProperties = @{
(__bridge id)kCGImagePropertyGIFDictionary: @{
(__bridge id)kCGImagePropertyGIFDelayTime: @(frameDelay),
}
};
CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef)toURL, (__bridge CFStringRef)UTTypeGIF.identifier, images.count, NULL);
CGImageDestinationSetProperties(destination, (__bridge CFDictionaryRef)fileProperties);
for (UIImage * image in images) {
CGImageDestinationAddImage(destination, image.CGImage, (__bridge CFDictionaryRef)frameProperties);
}
if (!CGImageDestinationFinalize(destination)) {
NSLog(@"failed to finalize image destination");
}
CFRelease(destination);
}
-(void)persistAnimatedImageSequenceAPNG:(NSArray<UIImage *> *)images frameDelay:(CGFloat)frameDelay numberOfLoops:(NSInteger)numberOfLoops toURL:(NSURL *)toURL{
NSDictionary *fileProperties = @{
(__bridge id)kCGImagePropertyPNGDictionary: @{
(__bridge id)kCGImagePropertyAPNGLoopCount: @(numberOfLoops),
}
};
NSDictionary *frameProperties = @{
(__bridge id)kCGImagePropertyPNGDictionary: @{
(__bridge id)kCGImagePropertyAPNGDelayTime: @(frameDelay),
}
};
CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef)toURL, (__bridge CFStringRef)UTTypePNG.identifier, images.count, NULL);
CGImageDestinationSetProperties(destination, (__bridge CFDictionaryRef)fileProperties);
for (UIImage * image in images) {
CGImageDestinationAddImage(destination, image.CGImage, (__bridge CFDictionaryRef)frameProperties);
}
if (!CGImageDestinationFinalize(destination)) {
NSLog(@"failed to finalize image destination");
}
CFRelease(destination);
}
@end