You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 06:33:10