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

如何在NSTextContentStorage中控制NSTextElement创建以拆分文本与附件?

问题描述

现有包含附件的NSAttributedString,代码如下:

NSMutableParagraphStyle* ps = [NSMutableParagraphStyle defaultParagraphStyle];
NSMutableAttributedString* attri = [[NSMutableAttributedString alloc] initWithString:@"12345" attributes:@{NSForegroundColorAttributeName:[UIColor redColor],NSParagraphStyleAttributeName:ps}];
ImageAttachment* attachment = [ImageAttachment new];
attachment.textLayoutManager = self.layoutManager;

[attri appendAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]];

希望通过TextKit2自定义UIView,将富文本布局为:

12345
image attachment

计划为文本“12345”和附件分别创建独立的NSTextElement,但将富文本设置到NSTextContentStorage后调用enumerateTextElementsFromLocation:时,代理方法textContentManager:textElementAtLocation:仅被调用一次(位置为0),无法拆分出两个元素。

解决方案

TextKit2默认会将连续的文本与附件合并为单个NSTextElement,要实现拆分,需要在代理方法中手动解析文本内容,按类型拆分并返回对应元素,同时正确设置元素的范围:

  1. 解析当前位置的内容类型:从textStorage中获取当前位置对应的字符/附件,判断是普通文本还是附件
  2. 创建对应NSTextElement并设置范围:根据内容类型创建元素,明确元素覆盖的字符范围,让contentManager能确定下一个元素的起始位置
  3. 处理附件的布局逻辑:确保自定义的ImageAttachment正确实现NSTextAttachment的布局相关方法

修改后的代理方法示例:

- (nullable NSTextElement *)textContentManager:(NSTextContentManager *)textContentManager textElementAtLocation:(id <NSTextLocation>)location {
    NSTextStorage *textStorage = self.contentStorage.textStorage;
    NSInteger startIndex = [textStorage offsetFromLocation:textStorage.beginningOfDocument toLocation:location];
    
    // 边界判断:超出文本范围返回nil
    if (startIndex >= textStorage.length) {
        return nil;
    }
    
    NSTextElement *element = nil;
    NSRange elementRange;
    
    // 检查当前位置是否是附件
    NSAttributedString *subStr = [textStorage attributedSubstringFromRange:NSMakeRange(startIndex, 1)];
    NSTextAttachment *attachment = [subStr attribute:NSAttachmentAttributeName atIndex:0 effectiveRange:nil];
    
    if (attachment) {
        // 处理附件元素,范围为单个字符(附件占位符)
        elementRange = NSMakeRange(startIndex, 1);
        element = [[NSTextElement alloc] initWithTextContentManager:textContentManager];
        // 可以给附件元素设置自定义标识,方便后续布局时识别
        [element setValue:@(YES) forKey:@"isAttachmentElement"];
    } else {
        // 处理文本元素,找到连续的普通文本范围
        NSRange textRange = [textStorage.string rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"\n"] options:NSBackwardsSearch range:NSMakeRange(startIndex, textStorage.length - startIndex)];
        if (textRange.location == NSNotFound) {
            textRange = NSMakeRange(startIndex, textStorage.length - startIndex);
        } else {
            textRange = NSMakeRange(startIndex, textRange.location - startIndex);
        }
        elementRange = textRange;
        element = [[NSTextElement alloc] initWithTextContentManager:textContentManager];
        [element setValue:@(NO) forKey:@"isAttachmentElement"];
    }
    
    // 设置元素的范围,这是让后续枚举继续调用代理的关键
    id <NSTextLocation> startLoc = [textStorage locationFromLocation:textStorage.beginningOfDocument offset:elementRange.location];
    id <NSTextLocation> endLoc = [textStorage locationFromLocation:startLoc offset:elementRange.length];
    element.elementRange = NSTextRangeRange(startLoc, endLoc);
    
    return element;
}

额外注意事项

  • 自定义的ImageAttachment需要实现boundsForTextContainer:proposedLineFragment:glyphPosition:characterIndex:方法,返回附件的显示尺寸,确保布局正确
  • 在后续的文本布局阶段(比如自定义NSTextLayoutManagerDelegate的方法),可以通过元素的自定义标识区分文本和附件,分别处理绘制逻辑

内容的提问来源于stack exchange,提问作者ximmyxiao

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 20:26:16