如何在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,要实现拆分,需要在代理方法中手动解析文本内容,按类型拆分并返回对应元素,同时正确设置元素的范围:
- 解析当前位置的内容类型:从textStorage中获取当前位置对应的字符/附件,判断是普通文本还是附件
- 创建对应NSTextElement并设置范围:根据内容类型创建元素,明确元素覆盖的字符范围,让contentManager能确定下一个元素的起始位置
- 处理附件的布局逻辑:确保自定义的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
相关产品推荐
相关产品推荐

