遇到一个新需求,需要在富文本上添加gif或者其他动态图,原来的使用的富文本不支持动态图,就研究下了国外大牛的demo,其实还是很简单的,主要原理就是把动态图切成一帧帧图片,然后利用NSTimer和动态图的周期循环在UILabel上画图片。。。但是亲测了一下不会引起cpu和内存方面的担忧,所以我们可以在第三方的富文本源码上进行修改或者重新封装一下,废话不多少,直接上代码。
1.拿gif为例,首先处理gif成图片数字和得到动画周期
//测试处理消息区gif 得到 图片数组和动画周期 _images _duration
-(void)getImagesAndDuration{
NSString*path = [[NSBundlemainBundle] pathForResource:@"test"ofType:@"gif"];
NSData*data = [NSDatadataWithContentsOfFile:path];
CGImageSourceRefsource =CGImageSourceCreateWithData((__bridgeCFDataRef)data, NULL);
size_tcount =CGImageSourceGetCount(source);
_images= [NSMutableArrayarray];
_duration=0.0f;
for(size_ti =0; i < count; i++) {
CGImageRefimage =CGImageSourceCreateImageAtIndex(source, i,NULL);
_duration+= [selfsd_frameDurationAtIndex:isource:source];
[_imagesaddObject:[UIImageimageWithCGImage:imagescale:[UIScreenmainScreen].scaleorientation:UIImageOrientationUp]];
CGImageRelease(image);
}
}
-(float)sd_frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source {
floatframeDuration =0.1f;
CFDictionaryRefcfFrameProperties =CGImageSourceCopyPropertiesAtIndex(source, index,nil);
NSDictionary*frameProperties = (__bridgeNSDictionary*)cfFrameProperties;
NSDictionary*gifProperties = frameProperties[(NSString*)kCGImagePropertyGIFDictionary];
NSNumber*delayTimeUnclampedProp = gifProperties[(NSString*)kCGImagePropertyGIFUnclampedDelayTime];
if(delayTimeUnclampedProp) {
frameDuration = [delayTimeUnclampedPropfloatValue];
}
else{
NSNumber*delayTimeProp = gifProperties[(NSString*)kCGImagePropertyGIFDelayTime];
if(delayTimeProp) {
frameDuration = [delayTimePropfloatValue];
}
}
// Many annoying ads specify a 0 duration to make an image flash as quickly as possible.
// We follow Firefox's behavior and use a duration of 100 ms for any frames that specify
// a duration of <= 10 ms. See <rdar://problem/7689300> and <http://webkit.org/b/36082>
// for more information.
if(frameDuration <0.011f) {
frameDuration =0.100f;
}
CFRelease(cfFrameProperties);
returnframeDuration;
}
2.绘制gif 我们的项目用的是TTTAttributedLabel,绘制图片代码基本都差不多,根据自己项目的需要,传入相应的数据,在绘制图片处进行修改
- (void)drawStrike:(CTFrameRef)frame
inRect:(CGRect)rect
context:(CGContextRef)c
{
[superdrawStrike:frame inRect:rectcontext:c];
//PS:这个是在TTT里drawFramesetter....方法最后做了修改的基础上。
CGFloatemojiWith =self.font.lineHeight*kEmojiWidthRatioWithLineHeight;
CGFloatemojiOriginYOffset =self.font.lineHeight*kEmojiOriginYOffsetRatioWithLineHeight;
//修正绘制offset,根据当前设置的textAlignment
CGFloatflushFactor =TTTFlushFactorForTextAlignment(self.textAlignment);
CFArrayReflines =CTFrameGetLines(frame);
NSIntegernumberOfLines =self.numberOfLines>0?MIN(self.numberOfLines, CFArrayGetCount(lines)) :CFArrayGetCount(lines);
CGPointlineOrigins[numberOfLines];
CTFrameGetLineOrigins(frame,CFRangeMake(0, numberOfLines), lineOrigins);
BOOLtruncateLastLine = (self.lineBreakMode==NSLineBreakByTruncatingHead||self.lineBreakMode==NSLineBreakByTruncatingMiddle||self.lineBreakMode==NSLineBreakByTruncatingTail);
CFRangetextRange =CFRangeMake(0, (CFIndex)[self.attributedTextlength]);
for(CFIndexlineIndex =0; lineIndex < numberOfLines; lineIndex++) {
CTLineRefline =CFArrayGetValueAtIndex(lines, lineIndex);
//这里其实是能获取到当前行的真实origin.x,根据textAlignment,而lineBounds.origin.x其实是默认一直为0的(不会受textAlignment影响)
CGFloatpenOffset = (CGFloat)CTLineGetPenOffsetForFlush(line, flushFactor, rect.size.width);
CFIndextruncationAttributePosition = -1;
//检测如果是最后一行,是否有替换...
if(lineIndex == numberOfLines -1&& truncateLastLine) {
// Check if the range of text in the last line reaches the end of the full attributed string
CFRangelastLineRange =CTLineGetStringRange(line);
if(!(lastLineRange.length==0&& lastLineRange.location==0) && lastLineRange.location+ lastLineRange.length< textRange.location+ textRange.length) {
// Get correct truncationType and attribute position
truncationAttributePosition = lastLineRange.location;
NSLineBreakModelineBreakMode =self.lineBreakMode;
// Multiple lines, only use UILineBreakModeTailTruncation
if(numberOfLines !=1) {
lineBreakMode =NSLineBreakByTruncatingTail;
}
switch(lineBreakMode) {
caseNSLineBreakByTruncatingHead:
break;
caseNSLineBreakByTruncatingMiddle:
truncationAttributePosition += (lastLineRange.length/2);
break;
caseNSLineBreakByTruncatingTail:
default:
truncationAttributePosition += (lastLineRange.length-1);
break;
}
//如果要在truncationAttributePosition这个位置画表情需要忽略
}
}
//找到当前行的每一个要素,姑且这么叫吧。可以理解为有单独的attr属性的各个range。
for(idglyphRunin(__bridgeNSArray*)CTLineGetGlyphRuns(line)) {
//找到此要素所对应的属性
NSDictionary*attributes = (__bridgeNSDictionary*)CTRunGetAttributes((__bridgeCTRunRef) glyphRun);
//判断是否有图像,如果有就绘制上去
NSString*imageName = attributes[kCustomGlyphAttributeImageName];
if(imageName) {
CFRangeglyphRange =CTRunGetStringRange((__bridgeCTRunRef)glyphRun);
if(glyphRange.location== truncationAttributePosition) {
//这里因为glyphRange的length肯定为1,所以只做这一个判断足够
continue;
}
CGRectrunBounds =CGRectZero;
CGFloatrunAscent =0.0f;
CGFloatrunDescent =0.0f;
runBounds.size.width= (CGFloat)CTRunGetTypographicBounds((__bridgeCTRunRef)glyphRun, CFRangeMake(0,0), &runAscent, &runDescent,NULL);
if(runBounds.size.width!=emojiWith) {
//这一句是为了在某些情况下,例如单行省略号模式下,默认行为会将个别表情的runDelegate改变,也就改变了其大小。这时候会引起界面上错乱,这里做下检测(浮点数做等于判断似乎有点操蛋啊。。)
continue;
}
runBounds.size.height= runAscent + runDescent;
CGFloatxOffset =0.0f;
switch(CTRunGetStatus((__bridgeCTRunRef)glyphRun)) {
casekCTRunStatusRightToLeft:
xOffset =CTLineGetOffsetForStringIndex(line, glyphRange.location+ glyphRange.length,NULL);
break;
default:
xOffset =CTLineGetOffsetForStringIndex(line, glyphRange.location,NULL);
break;
}
runBounds.origin.x= penOffset + xOffset;
runBounds.origin.y= lineOrigins[lineIndex].y;
runBounds.origin.y-= runDescent;
//上面代码千篇一律 重点在这里 self.rankGifImageArray self.duration 是图片数组和动画周期
NSUserDefaults* nextImage = [NSUserDefaultsstandardUserDefaults];
intimgIndex = [[nextImageobjectForKey:@"location"]intValue];
UIImage*image;
if(imgIndex < [self.rankGifImageArraycount]-1) {
imgIndex++;
}else{
imgIndex=0;
}
image = [self.rankGifImageArrayobjectAtIndex: imgIndex];
[nextImagesetObject:[NSStringstringWithFormat:@"%d",imgIndex]forKey:@"location"];
runBounds.origin.y+=3;//稍微矫正下。
runBounds.size.width=28;
runBounds.size.height=13;
CGContextDrawImage(c, runBounds, image.CGImage);
[selfstartGif:self.duration/self.rankGifImageArray.count];
}
- (void)startGif:(NSTimeInterval)time
{
if(!gifTimer) {
gifTimer= [NSTimertimerWithTimeInterval:timetarget:selfselector:@selector(gifAnimate:)userInfo:nilrepeats:YES];
[[NSRunLoopmainRunLoop]addTimer:gifTimerforMode:NSRunLoopCommonModes];
}
}
- (void)gifAnimate:(NSTimer*)timer
{
[supersetNeedsDisplay];
}
总结:我们要做的就是在富文本处理图片的基础上进行修改,达到实现动态图效果。