iOS开发:如何用NSTextAttachment让UITextView中GIF动起来?
在UITextView中嵌入GIF并实现播放的解决方案
我之前也碰到过一模一样的问题!你现在的代码只是把GIF的第一帧静态显示出来了,核心原因是NSTextAttachment本身不支持动画播放——它只会渲染UIImage的当前静态内容,不会主动去遍历GIF的帧并刷新视图。SDWebImage生成的带动画的UIImage确实包含了所有帧数据,但没人帮它驱动帧切换啊。
下面给你一套可行的方案,完全基于UITextView和SDWebImage,不需要YYKit:
第一步:自定义支持动画的TextAttachment
我们需要写一个继承自NSTextAttachment的子类,用CADisplayLink来定时切换GIF的帧,并且通知文本视图刷新显示。
@interface AnimatedGIFTextAttachment : NSTextAttachment @property (nonatomic, strong) CADisplayLink *displayLink; @property (nonatomic, assign) NSUInteger currentFrameIndex; @property (nonatomic, strong) UIImage *animatedImage; @end @implementation AnimatedGIFTextAttachment - (instancetype)initWithAnimatedImage:(UIImage *)animatedImage { self = [super init]; if (self) { self.animatedImage = animatedImage; // 先显示第一帧 self.image = [animatedImage sd_imageAtIndex:0]; [self setupDisplayLink]; } return self; } - (void)setupDisplayLink { // 用CADisplayLink同步屏幕刷新率来切换帧 self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateFrame)]; [self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; } - (void)updateFrame { if (!self.animatedImage || self.animatedImage.sd_frameCount == 0) { [self.displayLink invalidate]; return; } // 循环切换帧索引 self.currentFrameIndex = (self.currentFrameIndex + 1) % self.animatedImage.sd_frameCount; UIImage *currentFrame = [self.animatedImage sd_imageAtIndex:self.currentFrameIndex]; self.image = currentFrame; // 通知布局管理器刷新对应的文本区域 if (self.textContainer) { NSRange glyphRange = [self.textContainer.layoutManager glyphRangeForTextContainer:self.textContainer]; [self.textContainer.layoutManager invalidateDisplayForCharacterRange:glyphRange]; } } - (void)dealloc { // 销毁时记得释放displayLink,避免内存泄漏 [self.displayLink invalidate]; } @end
第二步:替换原有代码使用自定义Attachment
把你原来创建NSTextAttachment的代码替换成这个自定义类,同时注意设置正确的bounds来调整GIF的显示大小和对齐方式:
// 加载GIF数据(建议异步加载,避免主线程阻塞,这里按你的原有代码写) NSData *gifData = [NSData dataWithContentsOfFile:imagePath]; UIImage *gifImg = [UIImage sd_animatedGIFWithData:gifData]; // 使用自定义的动画Attachment AnimatedGIFTextAttachment *atc = [[AnimatedGIFTextAttachment alloc] initWithAnimatedImage:gifImg]; // 调整GIF的显示大小和垂直对齐(根据你的需求修改scale和y偏移) CGFloat scale = 0.6; // 缩放比例,让GIF适配文本大小 CGSize imageSize = gifImg.size; // y偏移是为了让GIF和文本垂直居中,比如字体大小16的话,计算一下偏移量 CGFloat verticalOffset = -(imageSize.height * scale - 16) / 2; atc.bounds = CGRectMake(0, verticalOffset, imageSize.width * scale, imageSize.height * scale); // 生成带Attachment的富文本并替换到文本视图中 NSAttributedString *textAttachmentString = [NSAttributedString attributedStringWithAttachment:atc]; [emojiText replaceCharactersInRange:range withAttributedString:textAttachmentString]; MyTextView.attributedText = emojiText; // 注意这里是attributedText,不是attributedString(你原来的代码写错了)
一些注意事项
- 内存管理:如果UITextView会频繁更新文本,记得在文本替换前遍历原有的
AnimatedGIFTextAttachment,调用[attachment.displayLink invalidate]来释放资源,避免内存泄漏。 - 异步加载:你现在用
dataWithContentsOfFile是同步加载,建议换成SDWebImage的异步方法加载GIF数据,避免卡顿主线程。 - UILabel适配:如果要在UILabel中使用,逻辑基本一致,只是在刷新帧的时候需要调用
[label setNeedsDisplay]来触发重绘。 - SDWebImage版本:确保你的SDWebImage版本支持
sd_animatedGIFWithData、sd_frameCount、sd_imageAtIndex这些API,最新版本都没问题。
内容的提问来源于stack exchange,提问作者L.wind
相关产品推荐
相关产品推荐

