Reputation: 14527
I'm trying to store an NSMutableAttributedString
in CoreData
, but am running into problems since some of the attributes of my NSMutableAttributedString
contain Core Foundation objects that can't be archived. Is there an easy way to get this object to store in CoreData without having to do some messy stuff manually?
Upvotes: 5
Views: 2767
Reputation: 2033
While the above answer is right, it has one big disadvantage:
It is not possible to build a fetch request / predicate that queries the content of the NSAttributedString object!
A predicate like this will cause an exception when executed:
[NSPredicate predicateWithFormat:@"(content CONTAINS[cd] %@)", @"test"]];
To store an 'fetchable' NSAttributedString in Core Data is is needed to spilt the NSAttributedString into two parts: A NSString side (which can be fetched) and a NSData side, which stores the attributes.
This split can be achieved by creating three attributes in the Core Data entity:
In the custom entities class the 'content' attributed the created from its shadows and changes to the attribute are also mirrored to its shadows.
Header file:
/**
MMTopic
*/
@interface MMTopic : _MMTopic {}
@property (strong, nonatomic) NSAttributedString* content;
@end
Implementation file:
/**
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
Fetching can now be done by:
[NSPredicate predicateWithFormat:@"(contentString CONTAINS[cd] %@)", @"test"]];
While any access to the NSAttributedString goes like this:
textField.attributedText = pTopic.content;
The rules for working with 'Non-Standard attributes' in Core Data are documented here: Apple docs
Upvotes: 5
Reputation: 14527
I started using CoreText
when iOS5 was out, and thus used the Core Foundation values as attributes. However I now realize that since iOS6 came out, I can now use NSForegroundColorAttributeName
, NSParagraphStyleAttributeName
, NSFontAttributeName
, etc. in the attributes dictionary, and those keys are accompanied by objects like UIColor
, NSMutableParagraphStyle
, and UIFont
which can be archived.
Upvotes: 1
Reputation: 8988
Well I am not sure what you are trying to do with the attributed string, but if it's formatted text then can't you use NSFont, etc..
Take a look here http://ossh.com.au/design-and-technology/software-development, I posted some stuff on formatting styles and images with uitextview and nstextview, but mostly it's about attributed strings.
This stuff is all stored in core data.
Upvotes: 1
Reputation: 70946
NSMutableAttributedString
conforms to NSCoding
, which means that it knows how to convert itself to/from an NSData
and does so via a protocol that Core Data knows how to use.
Make the attribute "transformable", and then just assign attributed strings to it. Since it's transformable, Core Data will use NSCoding
to convert it to NSData
when you assign a value, and to convert it back to an attributed string when you read it.
Note, you won't be able to use a predicate to filter results on this field. But storing and retrieving it is simple.
Upvotes: 5