在 CoreData 中存储 NSMutableAttributedString 的简单方法

Posted

技术标签:

【中文标题】在 CoreData 中存储 NSMutableAttributedString 的简单方法【英文标题】:Simple way to store NSMutableAttributedString in CoreData 【发布时间】:2013-12-18 02:03:55 【问题描述】:

我正在尝试将NSMutableAttributedString 存储在CoreData 中,但我遇到了问题,因为我的NSMutableAttributedString 的某些属性包含无法归档的Core Foundation 对象。有没有一种简单的方法可以让这个对象存储在 CoreData 中,而不必手动做一些乱七八糟的事情?

【问题讨论】:

您建议如何存储无法归档的内容? @DuncanGroenewald - 这就是我要问的,猜这是不可能的? 您在属性中包含的具体内容是什么?您不能将它们子类化并将归档添加到您自己的子类中吗? @DuncanGroenewald - 它是核心基础的东西,如 CGColorRef、CTFontRef、CTParagraphStyleRef。我发现我必须创建自己的子类并将所有这些值转换为 UIKit。猜猜这真的不是一个简单的方法。 嗯,你不能使用标准属性来实现同样的事情吗? 【参考方案1】:

NSMutableAttributedString 符合NSCoding,这意味着它知道如何将自己转换为NSData,并通过Core Data 知道如何使用的协议来实现。

使属性“可转换”,然后将属性字符串分配给它。由于它是可转换的,Core Data 将在您分配值时使用NSCoding 将其转换为NSData,并在您读取它时将其转换回属性字符串。

请注意,您将无法使用谓词来过滤此字段的结果。但是存储和检索它很简单。

【讨论】:

它确实符合 NSCoding,但它的属性可能不符合(属性)。【参考方案2】:

虽然上面的答案是对的,但它有一个很大的缺点:

无法构建查询 NSAttributedString 对象内容的获取请求/谓词!

这样的谓词在执行时会引发异常:

[NSPredicate predicateWithFormat:@"(content CONTAINS[cd] %@)", @"test"]];

要在 Core Data 中存储一个“可获取”的 NSAttributedString,需要将 NSAttributedString 分成两部分:一个 NSString 端(可以获取)和一个 NSData 端,它存储属性。

这种拆分可以通过在Core Data实体中创建三个属性来实现:

    影子 NSString 属性 ('contentString') 影子 NSData 属性('contentAttributes') “未定义”瞬态属性(“内容”)

在自定义实体类中,从其阴影创建的“内容”属性和对属性的更改也反映到其阴影。

头文件:

/**
 MMTopic
*/
@interface MMTopic : _MMTopic 

@property (strong, nonatomic) NSAttributedString*   content;

@end

实现文件:

/**
MMTopic (PrimitiveAccessors)

*/

@interface MMTopic (PrimitiveAccessors)

- (NSAttributedString *)primitiveContent;
- (void)setPrimitiveContent:(NSAttributedString *)pContent;

@end


/**
 MMTopic

 */
@implementation MMTopic    

static NSString const*  kAttributesDictionaryKey =  @"AttributesDictionary";
static NSString const*  kAttributesRangeKey =       @"AttributesRange";

/*
 awakeFromFetch

 */
- (void)awakeFromFetch 

    [super awakeFromFetch];

    // Build 'content' from its shadows 'contentString' and 'contentAttributes'
    NSString*                   string = self.contentString;
    NSMutableAttributedString*  content = [[NSMutableAttributedString alloc] initWithString:string];

    NSData*                     attributesData = self.contentAttributes;
    NSArray*                    attributesArray = nil;
    if (attributesData) 
        NSKeyedUnarchiver*  decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:attributesData];
        attributesArray = [[NSArray alloc] initWithCoder:decoder];
    

    if ((content) &&
        (attributesArray.count)) 

        for (NSDictionary* attributesDictionary in attributesArray) 
            //NSLog(@"%@: %@", NSStringFromRange(((NSValue*)attributesDictionary[kAttributesRangeKey]).rangeValue), attributesDictionary[kAttributesDictionaryKey]);
            [content addAttributes:attributesDictionary[kAttributesDictionaryKey]
                             range:((NSValue*)attributesDictionary[kAttributesRangeKey]).rangeValue];
        

        [self setPrimitiveContent:content];
    


/*
 content

 */
@dynamic content;

/*
 content (getter)

 */
- (NSAttributedString *)content 

    [self willAccessValueForKey:@"content"];
    NSAttributedString* content = [self primitiveContent];
    [self didAccessValueForKey:@"content"];

    return content;


/*
 content (setter)

 */
- (void)setContent:(NSAttributedString *)pContent 

    [self willChangeValueForKey:@"content"];
    [self setPrimitiveValue:pContent forKey:@"content"];
    [self didChangeValueForKey:@"content"];

    // Update the shadows
    // contentString
    [self setValue:pContent.string
            forKey:@"contentString"];

    // contentAttributes
    NSMutableData*      data = [NSMutableData data];
    NSKeyedArchiver*    coder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
    NSMutableArray*     attributesArray = [NSMutableArray array];
    [pContent enumerateAttributesInRange:NSMakeRange(0, pContent.length)
                                 options:0
                              usingBlock:^(NSDictionary* pAttributesDictionary, NSRange pRange, BOOL* prStop) 
                                  //NSLog(@"%@: %@", NSStringFromRange(pRange), pAttributesDictionary);
                                  [attributesArray addObject:@
                                                               kAttributesDictionaryKey:    pAttributesDictionary,
                                                               kAttributesRangeKey:     [NSValue valueWithRange:pRange],
                                                               ];
                              ];
    [attributesArray encodeWithCoder:coder];
    [coder finishEncoding];

    [self setValue:data
            forKey:@"contentAttributes"];


@end

现在可以通过以下方式获取:

[NSPredicate predicateWithFormat:@"(contentString CONTAINS[cd] %@)", @"test"]];

虽然对 NSAttributedString 的任何访问都是这样的:

textField.attributedText = pTopic.content;

在 Core Data 中使用“非标准属性”的规则记录在此:Apple docs

【讨论】:

【参考方案3】:

好吧,我不确定您要对属性字符串做什么,但如果它是格式化文本,那么您不能使用 NSFont 等。

看这里http://ossh.com.au/design-and-technology/software-development,我发布了一些关于使用 uitextview 和 nstextview 格式化样式和图像的内容,但主要是关于属性字符串。

这些东西都存储在核心数据中。

【讨论】:

【参考方案4】:

我在 ios5 出来时开始使用CoreText,因此使用 Core Foundation 值作为属性。但是我现在意识到,自从iOS6出来后,我现在可以在属性字典中使用NSForegroundColorAttributeNameNSParagraphStyleAttributeNameNSFontAttributeName等,并且这些键伴随着UIColorNSMutableParagraphStyle等对象UIFont 可以存档。

【讨论】:

以上是关于在 CoreData 中存储 NSMutableAttributedString 的简单方法的主要内容,如果未能解决你的问题,请参考以下文章

在内存中缓存 CoreData 存储

将 CoreData 存储在 iCloud 中

在 CoreData 中存储和获取字典数组

为啥 NSDate() 在 CoreData 中存储一个奇怪的值

在 CoreData 中存储 NSMutableAttributedString 的简单方法

CoreData:持久和临时存储