遇到一个新需求,需要在富文本上添加gif或者其他动态图,原来的使用的富文本不支持动态图,就研究下了国外大牛的demo,其实还是很简单的,主要原理就是把动态图切成一帧帧图片,然后利用NSTimer和动态图的周期循环在UILabel上画图片。。。但是亲测了一下不会引起cpu和内存方面的担忧,所以我们可以在第三方的富文本源码上进行修改或者重新封装一下,废话不多少,直接上代码。
1.拿gif为例,首先处理gif成图片数字和得到动画周期
//测试 处理消息区 gif 得到 图片数组和动画周期 _images _duration
-(void)getImagesAndDuration{
NSString *path = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"gif"];
NSData *data = [NSData dataWithContentsOfFile:path];
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
size_t count = CGImageSourceGetCount(source);
_images = [NSMutableArray array];
_duration = 0.0f;
for (size_t i = 0; i < count; i++) {
CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, NULL);
_duration += [self sd_frameDurationAtIndex:i source:source];
[_images addObject:[UIImage imageWithCGImage:image scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp]];
CGImageRelease(image);
}
}
-(float)sd_frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source {
float frameDuration = 0.1f;
CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil);
NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties;
NSDictionary *gifProperties = frameProperties[(NSString *)kCGImagePropertyGIFDictionary];
NSNumber *delayTimeUnclampedProp = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime];
if (delayTimeUnclampedProp) {
frameDuration = [delayTimeUnclampedProp floatValue];
}
else {
NSNumber *delayTimeProp = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime];
if (delayTimeProp) {
frameDuration = [delayTimeProp floatValue];
}
}
// 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);
return frameDuration;
}
2.绘制gif 我们的项目用的是TTTAttributedLabel,绘制图片代码基本都差不多,根据自己项目的需要,传入相应的数据,在绘制图片处进行修改
- (void)drawStrike:(CTFrameRef)frame
inRect:(CGRect)rect
context:(CGContextRef)c
{
[super drawStrike:frame inRect:rect context:c];
//PS:这个是在TTT里drawFramesetter....方法最后做了修改的基础上。
CGFloat emojiWith = self.font.lineHeight*kEmojiWidthRatioWithLineHeight;
CGFloat emojiOriginYOffset = self.font.lineHeight*kEmojiOriginYOffsetRatioWithLineHeight;
//修正绘制offset,根据当前设置的textAlignment
CGFloat flushFactor = TTTFlushFactorForTextAlignment(self.textAlignment);
CFArrayRef lines = CTFrameGetLines(frame);
NSInteger numberOfLines = self.numberOfLines > 0 ? MIN(self.numberOfLines, CFArrayGetCount(lines)) : CFArrayGetCount(lines);
CGPoint lineOrigins[numberOfLines];
CTFrameGetLineOrigins(frame, CFRangeMake(0, numberOfLines), lineOrigins);
BOOL truncateLastLine = (self.lineBreakMode == NSLineBreakByTruncatingHead || self.lineBreakMode == NSLineBreakByTruncatingMiddle || self.lineBreakMode == NSLineBreakByTruncatingTail);
CFRange textRange = CFRangeMake(0, (CFIndex)[self.attributedText length]);
for (CFIndex lineIndex = 0; lineIndex < numberOfLines; lineIndex++) {
CTLineRef line = CFArrayGetValueAtIndex(lines, lineIndex);
//这里其实是能获取到当前行的真实origin.x,根据textAlignment,而lineBounds.origin.x其实是默认一直为0的(不会受textAlignment影响)
CGFloat penOffset = (CGFloat)CTLineGetPenOffsetForFlush(line, flushFactor, rect.size.width);
CFIndex truncationAttributePosition = -1;
//检测如果是最后一行,是否有替换...
if (lineIndex == numberOfLines - 1 && truncateLastLine) {
// Check if the range of text in the last line reaches the end of the full attributed string
CFRange lastLineRange = 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;
NSLineBreakMode lineBreakMode = self.lineBreakMode;
// Multiple lines, only use UILineBreakModeTailTruncation
if (numberOfLines != 1) {
lineBreakMode = NSLineBreakByTruncatingTail;
}
switch (lineBreakMode) {
case NSLineBreakByTruncatingHead:
break;
case NSLineBreakByTruncatingMiddle:
truncationAttributePosition += (lastLineRange.length / 2);
break;
case NSLineBreakByTruncatingTail:
default:
truncationAttributePosition += (lastLineRange.length - 1);
break;
}
//如果要在truncationAttributePosition这个位置画表情需要忽略
}
}
//找到当前行的每一个要素,姑且这么叫吧。可以理解为有单独的attr属性的各个range。
for (id glyphRun in (__bridge NSArray *)CTLineGetGlyphRuns(line)) {
//找到此要素所对应的属性
NSDictionary *attributes = (__bridge NSDictionary *)CTRunGetAttributes((__bridge CTRunRef) glyphRun);
//判断是否有图像,如果有就绘制上去
NSString *imageName = attributes[kCustomGlyphAttributeImageName];
if (imageName) {
CFRange glyphRange = CTRunGetStringRange((__bridge CTRunRef)glyphRun);
if (glyphRange.location == truncationAttributePosition) {
//这里因为glyphRange的length肯定为1,所以只做这一个判断足够
continue;
}
CGRect runBounds = CGRectZero;
CGFloat runAscent = 0.0f;
CGFloat runDescent = 0.0f;
runBounds.size.width = (CGFloat)CTRunGetTypographicBounds((__bridge CTRunRef)glyphRun, CFRangeMake(0, 0), &runAscent, &runDescent, NULL);
if (runBounds.size.width!=emojiWith) {
//这一句是为了在某些情况下,例如单行省略号模式下,默认行为会将个别表情的runDelegate改变,也就改变了其大小。这时候会引起界面上错乱,这里做下检测(浮点数做等于判断似乎有点操蛋啊。。)
continue;
}
runBounds.size.height = runAscent + runDescent;
CGFloat xOffset = 0.0f;
switch (CTRunGetStatus((__bridge CTRunRef)glyphRun)) {
case kCTRunStatusRightToLeft:
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 = [NSUserDefaults standardUserDefaults];
int imgIndex = [[nextImage objectForKey:@"location"] intValue];
UIImage *image;
if (imgIndex < [self.rankGifImageArray count]-1) {
imgIndex++;
}else{
imgIndex= 0;
}
image = [self.rankGifImageArray objectAtIndex: imgIndex];
[nextImage setObject:[NSString stringWithFormat:@"%d",imgIndex] forKey: @"location"];
runBounds.origin.y += 3; //稍微矫正下。
runBounds.size.width = 28;
runBounds.size.height = 13;
CGContextDrawImage(c, runBounds, image.CGImage);
[self startGif:self.duration/self.rankGifImageArray.count];
}
- (void)startGif:(NSTimeInterval)time
{
if (!gifTimer) {
gifTimer = [NSTimer timerWithTimeInterval:time target:self selector:@selector(gifAnimate:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:gifTimer forMode:NSRunLoopCommonModes];
}
}
- (void)gifAnimate:(NSTimer *)timer
{
[super setNeedsDisplay];
}
总结:我们要做的就是在富文本处理图片的基础上进行修改,达到实现动态图效果。
403

被折叠的 条评论
为什么被折叠?



