在NSAttributedString中保存自定义属性

10
我需要在 NSTextView 中添加自定义属性来选择文本。所以,我可以通过获取所选文本的带属性字符串,添加自定义属性,并用我的新带属性字符串替换所选内容来实现这一点。
现在,我将文本视图的带属性字符串作为NSData获取并写入文件。但是当我稍后打开该文件并将其还原到文本视图中时,我的自定义属性不见了!在为我的自定义属性设计整个方案之后,我发现系统不会保存你的自定义属性。请查看此处的重要说明:http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/AttributedStrings/Tasks/RTFAndAttrStrings.html 因此,我不知道如何保存和恢复具有此自定义属性的文档。有什么帮助吗?
2个回答

17

保存 NSAttributedString 的常规方法是使用 RTF,而 RTF 数据是由 NSAttributedString-dataFromRange:documentAttributes:error: 方法生成的。

然而,RTF 格式不支持自定义属性。相反,您应该使用 NSCoding 协议来归档您的带属性字符串,这将保留自定义属性:

//asssume attributedString is your NSAttributedString
//encode the string as NSData
NSData* stringData = [NSKeyedArchiver archivedDataWithRootObject:attributedString];
[stringData writeToFile:pathToFile atomically:YES];

//read the data back in and decode the string
NSData* newStringData = [NSData dataWithContentsOfFile:pathToFile];
NSAttributedString* newString = [NSKeyedUnarchiver unarchiveObjectWithData:newStringData];

1
当然,这些数据不会以RTF格式存在,因此您不应该将文件命名为.rtf扩展名。最好制定一个新的扩展名和UTI,并称其为新格式。 - Peter Hosey
这是一个很棒的答案!谢谢Rob和Peter。输出不是rtf或rtfd我也可以接受。我想在制定新方案后我没有考虑清楚...我应该自己想到NSCoder。 - regulus6633
这在我的iOS设备上不太起作用。我在NSCFType encodeWithCoder:上遇到了崩溃,其中类型对象看起来像这样:<CGColor 0x111beb70> [<CGColorSpace 0xb2b4df0> (kCGColorSpaceDeviceRGB)] ( 0.576471 0.576471 0.560784 1 ) -- 如果您恰好知道如何编码这些内容,那将非常有帮助。 - Paul Shapiro
那是因为CGColorRef不符合NSCoding。请参考这个答案:http://stackoverflow.com/a/10558963/50122 - Rob Keniger
CGColorRef 不是一个 Objective-C 对象,它是一个 C 结构体,因此类别的概念并不真正适用。 - Rob Keniger

6

使用Cocoa可以将自定义属性保存至RTF。这是因为RTF是一种文本格式,即使您不知道所有RTF规则并且没有自定义的RTF读写器,也可以将其作为字符串进行操作。我在下面概述的过程中对RTF进行了后处理,我个人已经使用了这种技术。需要非常小心的一件事情是,插入到RTF中的文本仅使用7位ASCII和没有未转义的控制字符,包括“\ { } ”。

以下是将数据编码的方法:

NSData *GetRtfFromAttributedString(NSAttributedString *text)
{
    NSData *rtfData = nil;
    NSMutableString *rtfString = nil;
    NSString *customData = nil, *encodedData = nil;
    NSRange range;
    NSUInteger dataLocation;

// Convert the attributed string to RTF
    if ((rtfData = [text RTFFromRange:NSMakeRange(0, [text length]) documentAttributes:nil]) == nil)
        return(nil);

// Find and encode your custom attributes here. In this example the data is a string and there's at most one of them
    if ((customData = [text attribute:@"MyCustomData" atIndex:0 effectiveRange:&range]) == nil)
        return(rtfData); // No custom data, return RTF as is
    dataLocation = range.location;

// Get a string representation of the RTF
    rtfString = [[NSMutableString alloc] initWithData:rtfData encoding:NSASCIIStringEncoding];

// Find the anchor where we'll put our data, namely just before the first paragraph property reset
    range = [rtfString rangeOfString:@"\\pard" options:NSLiteralSearch];
    if (range.location == NSNotFound)
        {
        NSLog(@"Custom data dropped; RTF has no paragraph properties");
        [rtfString release];
        return(rtfData);
        }

// Insert the starred group containing the custom data and its location
    encodedData = [NSString stringWithFormat:@"{\\*\\my_custom_keyword %d,%@}\n", dataLocation, customData];
    [rtfString insertString:encodedData atIndex:range.location];

// Convert the amended RTF back to a data object    
    rtfData = [rtfString dataUsingEncoding:NSASCIIStringEncoding];
    [rtfString release];
    return(rtfData);
}

这种技术的原理是,所有符合标准的RTF阅读器都会忽略它们无法识别的“星号组”。因此,您要确保您的控制字符不会被任何其他阅读器识别出来,所以使用一些独特的前缀,比如您公司或产品的名称,这样更容易区分。如果您的数据很复杂、二进制或可能包含非法的RTF字符,而你又不想转义,那么可以使用base64编码。记得在关键字后面加上一个空格。

类似地,在读取RTF文件时,您需要搜索您的控制字符,提取数据并恢复属性。这个程序需要传入带属性的字符串和其来源的RTF文件作为参数。

void RestoreCustomAttributes(NSMutableAttributedString *text, NSData *rtfData)
{
    NSString *rtfString = [[NSString alloc] initWithData:rtfData encoding:NSASCIIStringEncoding];
    NSArray *components = nil;
    NSRange range, endRange;

// Find the custom data and its end
    range = [rtfString rangeOfString:@"{\\*\\my_custom_keyword " options:NSLiteralSearch];
    if (range.location == NSNotFound)
        {
        [rtfString release];
        return;
        }
    range.location += range.length;

    endRange = [rtfString rangeOfString:@"}" options:NSLiteralSearch
        range:NSMakeRange(range.location, [rtfString length] - endRange.location)];
    if (endRange.location == NSNotFound)
        {
        [rtfString release];
        return;
        }

// Get the location and the string data, which are separated by a comma
    range.length = endRange.location - range.location;
    components = [[rtfString substringWithRange:range] componentsSeparatedByString:@","];
    [rtfString release];

// Assign the custom data back to the attributed string. You should do range checking here (omitted for clarity)
    [text addAttribute:@"MyCustomData" value:[components objectAtIndex:1]
        range:NSMakeRange([[components objectAtIndex:0] integerValue], 1)];
}

我有一个包含文本和一个或多个NSTextAttachmentNSAttributedString. 如果NSAttributedString包含NSTextAttachment,那么是否可以编辑RTF流(如上所述)并将其转换回RTF? 我一直在尝试使用rtfData = [rtfString dataUsingEncoding: NSASCIIStringEncoding];但是没有成功。 - MAH

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接