iOS不支持播放GIF格式的动图,但是我们可以通过ImageIO框架来实现它。
先介绍一下GIF的几个概念:
- frame(帧):一个GIF可以简单认为是多张图片组成的动画,一帧就是其中一张图片;
- frameCount(帧数):表示GIF有多少帧;
- loopCount(播放次数):有些GIF播放到一定次数就停止了,如果为0就代表GIF一直循环播放;
- delayTime(延迟时间):每一帧播放的时间。
实现方法
通过ImageIO的CGImageSourceRef读取GIF图片数据,将每一帧图片及对应的播放时间解析出来,最后通过以下方法生成一张UIImage,从而实现GIF动态图的播放。
+ (UIImage *)animatedImageWithImages:(NSArray<UIImage *> *)images duration:(NSTimeInterval)duration
1.源码解析之获取每一帧的播放时间(delayTime)
参数:
- index:每一帧所对应的下标
- source:通过CGImageSourceRef获取图片数据
+ (float)sd_frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source {
float frameDuration = 0.1f;
①通过CGImageSourceCopyPropertiesAtIndex方法获取到该帧图片的属性字典;
CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil);
②通过__bridge将Core Foundation中(以下简称为CF)的CFDictionaryRef类型转为Foundation中对应的NSDictionary类型;
NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties;
③获取该帧图片中的GIF相关的属性字典(key=kCGImagePropertyGIFDictionary)
NSDictionary *gifProperties = frameProperties[(NSString *)kCGImagePropertyGIFDictionary];
④获取该帧图片的播放时间(key=kCGImagePropertyGIFUnclampedDelayTime);
NSNumber *delayTimeUnclampedProp = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime];
if (delayTimeUnclampedProp) {
frameDuration = [delayTimeUnclampedProp floatValue];
}
else {
⑤如果通过kCGImagePropertyGIFUnclampedDelayTime没有获取到播放时长,就通过kCGImagePropertyGIFDelayTime来获取,两者的含义是相同的;
NSNumber *delayTimeProp = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime];
if (delayTimeProp) {
frameDuration = [delayTimeProp floatValue];
}
}
⑥对于播放时间低于0.011s的,重新指定时长为0.100s;
if (frameDuration < 0.011f) {
frameDuration = 0.100f;
}
⑦CF对象和OC对象进行相互转化,在ARC环境下,编译器不会自动管理CF对象的内存,因此,创建一个CF对象以后,我们需要使用CFRelease将其手动释放。
CFRelease(cfFrameProperties);
return frameDuration;
}
2.源码解析之由图片数据(data)生成UIImage动图
参数:
- data:图片数据
+ (UIImage *)sd_animatedGIFWithData:(NSData *)data {
if (!data) {
return nil;
}
①使用__bridge将Foundation对象NSData转为对应的Core Foundation对象CFDataRef,再通过CGImageSourceCreateWithData生成CGImageSourceRef用以获取图片数据;
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
②从source中获取帧数(使用size_t是考虑到可移植性和程序效率,可参考http://jeremybai.github.io/blog/2014/09/10/size-t);
size_t count = CGImageSourceGetCount(source);
UIImage *animatedImage;
③处理帧数<=1的情况
if (count <= 1) {
animatedImage = [[UIImage alloc] initWithData:data];
}
④处理帧数大于1的情况
else {
NSMutableArray *images = [NSMutableArray array];
NSTimeInterval duration = 0.0f;
for (size_t i = 0; i < count; i++) {
④ a.取出索引对应的图片
CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, NULL);
if (!image) {
continue;
}
④ b.将该帧的播放时间累加到总时间duration中;
duration += [self sd_frameDurationAtIndex:i source:source];
④ c.由CGImageRef生成UIImage,并存入图片数组中;
[images addObject:[UIImage imageWithCGImage:image scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp]];
④ d.手动释放CF的CGImageRef对象
CGImageRelease(image);
}
if (!duration) {
duration = (1.0f / 10.0f) * count;
}
④ e.由图片数组生成可播放的UIImage动图
animatedImage = [UIImage animatedImageWithImages:images duration:duration];
}
⑤手动释放CF的CGImageSourceRef对象
CFRelease(source);
return animatedImage;
}
3.源码解析之绘制指定size的图片(重绘图片的宽高比例保持不变)

参数:
- size:要求的图片大小
- (UIImage *)sd_animatedImageByScalingAndCroppingToSize:(CGSize)size {
①如果size和原图大小相等或者size为零,则返回原图;
if (CGSizeEqualToSize(self.size, size) || CGSizeEqualToSize(size, CGSizeZero)) {
return self;
}
②设置缩放图片的size初始值为原图大小;
CGSize scaledSize = size;
③设置绘制的起点为(0,0);
CGPoint thumbnailPoint = CGPointZero;
④计算宽高各自的缩放比例,选择比例较大的一个作为缩放比例;
CGFloat widthFactor = size.width / self.size.width;
CGFloat heightFactor = size.height / self.size.height;
CGFloat scaleFactor = (widthFactor > heightFactor) ? widthFactor : heightFactor;
⑤根据缩放比重新计算缩放图片的size
scaledSize.width = self.size.width * scaleFactor;
scaledSize.height = self.size.height * scaleFactor;
⑥重新计算绘制的起点,以缩放图片的中心为中心进行裁剪
if (widthFactor > heightFactor) {
thumbnailPoint.y = (size.height - scaledSize.height) * 0.5;
}
else if (widthFactor < heightFactor) {
thumbnailPoint.x = (size.width - scaledSize.width) * 0.5;
}
NSMutableArray *scaledImages = [NSMutableArray array];
⑦使用UIGraphics对image中的每张图片进行绘制
for (UIImage *image in self.images) {
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
[image drawInRect:CGRectMake(thumbnailPoint.x, thumbnailPoint.y, scaledSize.width, scaledSize.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
[scaledImages addObject:newImage];
UIGraphicsEndImageContext();
}
⑧根据图片数组生成UIImage图片
return [UIImage animatedImageWithImages:scaledImages duration:self.duration];
}