iOS让GIF动起来
用webView来进行展示gif图片
UIWebView *webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
webView.scalesPageToFit = YES;
WKWebViewConfiguration *conf = [[WKWebViewConfiguration alloc] init];
WKWebView *wkwebView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:conf];
[wkwebView loadRequest:[NSURLRequest requestWithURL:fileURL]];
这两者之间的区别:
在拖动webView的时候,gif动画效果「会」出现暂停的效果。
在拖动wkwebView的时候,gif动画效果「不会」出现暂停的效果。wkWebView的性能高于uiwebView
了解框架ImageIO
1.GIF图片实质:
GIF图片其实一组图片,这一组图片进行播放实现了gif的动画效果。
2.代码:
NSURL *fileURL = [NSURL fileURLWithPath:filePath];
//根据URL获取ImageSource对象,因为创建方法含有create,如果不需要对象的时候需要进行手动释放
CGImageSourceRef source = CGImageSourceCreateWithURL((__bridge CFURLRef)fileURL, NULL);
//获取有几张图片。如果静态图,那么是1,如果是gif图片可能包含多张图
size_t count = CGImageSourceGetCount(source);
NSMutableArray *images = [NSMutableArray arrayWithCapacity:count];
NSMutableArray *delayTimes = [NSMutableArray arrayWithCapacity:count];
for (int i = 0 ; i < count ; i ++) {
//根据索引创建图片
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, i, NULL);
//获取每张图片的属性
NSDictionary *dic = CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(source, i, NULL));
//获取图片的停留时间
NSNumber *delay = dic[(__bridge NSString *)kCGImagePropertyGIFDictionary][(__bridge NSString *)kCGImagePropertyGIFUnclampedDelayTime];
if ([delay floatValue] <= 0) {
delay = dic[(__bridge NSString *)kCGImagePropertyGIFDictionary][(__bridge NSString *)kCGImagePropertyGIFDelayTime];
}
/*
If a time of 50 milliseconds or less is specified, then the actual delay time stored in this parameter is 100 miliseconds. See
官方解释,如果停留时间小于0.02s,停留时间应该是0.1s
*/
if ([delay floatValue] - 0.02 < FLT_EPSILON) {
delay = @(0.1);
}
_totalDelayTime += [delay floatValue];
[images addObject:(__bridge id)imageRef];
[delayTimes addObject:delay];
CFRelease(imageRef);
}
_images = images.copy;
_currentIndex = 0;
self.layer.contents = images.firstObject;
_isPlaying = NO;
CFRelease(source);
}
上面的代码解释了利用框架来解析出来gif图片中包含的图片
3.根据获取的图片来进行播放。
拿到图片我要进行播放我们想到就是循环切换这些图片,那么我们自然想起了NSTimer
dispatch_source
(不讲解,因为没有做),CADisplayLink
,这些是关于时间的调度器,还有一种播放图片的方式是关键帧动画CAKeyFrameAnimation
,下面依次实现,并且说一下此种方式的不足
3.1NSTimer
if (_timer == nil || _timer.valid == NO) {
// timeInterval,应该设置成我们从ImageSource中获取的delaytime
_timer = [NSTimer scheduledTimerWithTimeInterval:0.25f target:self selector:@selector(playTimer) userInfo:nil repeats:YES];
}
[self.timer fire];
- (void)playTimer {
self.currentIndex ++;
if (self.currentIndex >= self.images.count) {
self.currentIndex = 0;
}
//展示图片,用的是contents,这个属性
self.layer.contents = self.images[self.currentIndex];
}
灵活性比较小,不能控制每个张图片停留的时间
3.2 CAKeyFrameAnimation
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"contents"];
animation.values = self.images;
CGFloat time = 0.0f;
NSMutableArray<NSNumber *> *keyTimes = [NSMutableArray arrayWithCapacity:self.delayTimes.count];
for (int i = 0 ; i < self.delayTimes.count; i ++) {
CGFloat keyTime = time / self.totalDelayTime;
[keyTimes addObject:@(keyTime)];
keyTime += [self.delayTimes[i] floatValue];
}
animation.keyTimes = keyTimes;
animation.duration = self.totalDelayTime;
animation.repeatCount = NSIntegerMax;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
[self.layer addAnimation:animation forKey:@"gif"];
我们是以contents
来做动画,而且我们可以根据keyTimes
来设置每张图片的提留时间。相对timer是更加灵活一些。
我们以NSTimer的这种形式来展示图片,缺点是无法设置每张图片的停留时间,但是这种形式有一个中好处:我们可以不用把gif图片中的所有图片都保存在内存中,这样可以减少内存。当让我前面的NSTimer代码没有体现出来,(接下来我会在CADisPlayLink中显示出来)。而Animation的有点我们可以知晓:我们可以单独的控制每张图片的停留时间通过keyTimes属性。但是我们必须把所有到的图片放在内存中,因为属性values。这样会导致内存很大,而这种方式就是我们还有介绍的CADisplayLink,他即可以控制每张图停留的时间,又可以不用加载所有的图片。
3.3CADisplayLink
3.3.1CADisplayLink如何实现减少内存。
其实这个很简单。举例子说明:
现在有个gif图片,其中包含了30张图片,那么我会通过ImageSource
来获取到图片个数和每一张图片,并且把图片按照获取的顺序放在一个数组中。而我们在时间触发器的方法中,根据保存数组下标来获取下一个图片并且把它展示出来,伪代码如下
- (void)doTimerAction {
//gifImages:保存图片的数组[1,2,3,4,5,6,7,8 ........]一直到30,因为有30张照片
//currentIndex: 保存图片顺序,也就是下标
UIImage *image = [self.gifImages objectAtIndex:self.currentIndex];
self.layer.contents = image.cgimage;
//下标应该 +1
self.currentIndex += 1;
}
如何减少内存?就是减少数组gifImages
中包含的图片的实例,但是有点我们应该要注意,我们是减少了数组中图片的数量,但是这个数组的长度还必须是gif图片中包含的图片的数量。举例说明就是:
以前数组中包含的情况:
gifimages = [image1,image2,image3,image4,image5,image6,.......image30 ]
现在减少内存
gifimages = [image1,image2,image3,image4,image5,image6,...image10,null, null, ....null ],但是长度还是30,为什么长度还是30,「那是因为如果不是30那就是自己给自己的代码增加逻辑。」
这就是减少内存的想法,其实是很简单的。我们要一直维持数组中只有十张图片,这个数量是可以自己控制的。那么我们应该怎么维持数组呢,举例子:
gifimages = [image1,image2,image3,image4,image5,image6,...image10,null, null, ....null ];
//现在获取idx = 0 ;
i = gifimages[idx]; // i = image1;
//之后显示
contents = i;
//此时第一张图片暂时是不需要了。那么我们就可以把image1消除
gifimages = [null,image2..........image10....null];
//在这个时候我们应该生成11张图
//获取第11张图
image11 = imagesourcegetimage(11);
//把image11,放在数组中,并且位置就是11
gifimages = [null,image2..........image10,image11....null];
到这就介绍完内存减少的方法了。
对应的实际代码:
- (UIImage *)getGIFImageWithIndex:(NSUInteger)idx {
UIImage *image = [self.gifImages objectAtIndex:idx];
if ([image isEqual:[NSNull null]]) {
return nil;
}
//消除刚才取出的Image
dispatch_semaphore_wait(self.lock, DISPATCH_TIME_FOREVER);
[self.gifImages replaceObjectAtIndex:idx withObject:[NSNull null]];
dispatch_semaphore_signal(self.lock);
//新增之后的Image
NSUInteger nextIndex = (idx + capacity) % _imagesCount;
dispatch_async(self.imageQueue, ^{
NSLog(@"%@", [NSThread currentThread]);
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(_imageSourceRef, nextIndex, NULL);
UIImage *nextImage = [UIImage imageWithCGImage:imageRef];
CFRelease(imageRef);
dispatch_semaphore_wait(self.lock, DISPATCH_TIME_FOREVER);
[self.gifImages replaceObjectAtIndex:nextIndex withObject:nextImage];
dispatch_semaphore_signal(self.lock);
});
return image;
}
方便大家查找
3.3.2CADisplayLink实现控制每张图片的停留时间
CADisplayLink
这个类是一个以屏幕刷新频率,来触发绑定的事件。所以我们就知道就有了时间这个概念。每次调用绑定方法是16.67ms。那么我们就可以使用以16.67ms为单位。做一个时间累计器。举例子说明:
- (void)绑定方法{
如果image1的delaytime = 30ms,现在展示的是image1
contents = image1
acculator 这个是一个时间计时器,初始化为 0;
acculator += displaylink.duration;
如果acdulator > image1.delaytime,那么说明image1的展示时间已经过去了,所以index +1 往下循环此时:
accluator 应该减去 image1.delaytime,
content = image2.
循环上面的步骤就可以了
}
对应的实际代码:
- (void)displayKeyFrame{
// NSLog(@"\ntimestamp:%f \ntarget:%f\nduration: %f", self.displayLink.timestamp,self.displayLink.targetTimestamp, self.displayLink.duration);
self.accumulator += fmin(self.displayLink.duration, 1.f);
while (self.accumulator >= self.gifimage.frameDurations[self.currentIndex]) {
self.accumulator -= self.gifimage.frameDurations[self.currentIndex];
self.currentIndex ++;
if (self.currentIndex >= self.gifimage.imagesCount) {
self.currentIndex = 0;
}
[self.layer setNeedsDisplay];
}
}
方便大家查找
4.总结
总结一下这两天写的代码。主要还是看的别的博客。
iOS-加载gif的四种方式
本人代码如果感觉代码写的不好,可以查看上面博客中代码。而且最后一种方法的实现,yykit也是这样实现的,你可以查看一下大神的代码