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也是这样实现的,你可以查看一下大神的代码

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

  • 第十一章:神秘之地 他们不知不觉来到了沙漠,这里荒无人烟,天气炎热,没有食物,皮球说:“你们赶紧找一找...
    9986丶BH阅读 106评论 0 3
  • 因为 在我们不懂婚姻时就走进了家庭 在我们不懂教育时就拥有了孩子 在我们不懂健康时就拥有了生命 在我们不懂为人处事...
    我的语录笔记Z阅读 625评论 0 0
  • 我觉得婆媳之间是否能像母女一样,主要看你老公怎么去协调。 其实,很多家庭,婆媳之间的矛盾明明可以化解,但是作为最好...
    吕海波阅读 163评论 0 0