24

I am trying to store an NSAttributedString to a Core Data SQL store.

I have the property set as a "transformable", it is optional and it is NOT transient or indexed and the value transformer name is set to default "NSKeyedUnarchiveFromData". In the .xcdatamodel and generated the managed object class which has this in the .h:

@property (nonatomic, retain) id Text; (I have tried changing id to NSAttributedString *Text)

and this in the .m:

@dynamic Text;

I look through and set the ".text" property of my NSManagedObject to the attributed string then when completed I do:

NSError *error = nil;
[managedObjectContext save:&error];

This through is causing this error in the output:

[NSCFType encodeWithCoder:]: unrecognized selector sent to instance 0xc04edb0 Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* -[NSCFType encodeWithCoder:]: unrecognized selector sent to instance 0xc04edb0'

I have checked the class of what I am storing to the property and it is NSAttributedString also I check responsesToSelector @selector(:) and this returns true so very confused as this is contrary to the error message?

Please advise.

Thanks James

Georg Schölly
  • 124,188
  • 49
  • 220
  • 267
jodm
  • 2,607
  • 3
  • 25
  • 40
  • I had this before, but it was a memory issue. Try to run the app with NSZombieEnabled = YES – Alfonso Nov 24 '10 at 11:30
  • We do already have NSZombieEnabled enabled. Thanks James – jodm Nov 24 '10 at 11:33
  • Do I need to have transient ticked? I am getting conflicting messages online? – jodm Nov 24 '10 at 11:34
  • Where in your stack trace does this happen? It could be possible that one of the attributes of the string does not support NSCoding. – Alfonso Nov 24 '10 at 11:39
  • It happens directly after save on the managed object context and I get no NSLog's after that? We build our NSAttributedString up using small chunks of Attributed Strings which are converted to CFAttributedStrings to add styling then added back to the NSAttributedString. We have NSLogged the attributed string and the 2 things it contains are NSFont and NSParagraphStyle (both of which conform to NSCoding?). – jodm Nov 24 '10 at 11:44
  • Before saving try to loop over the attributes of the string and check if they respond to encodeWithCoder: to check if the error is caused by a string attribute. Building the string using multiple other strings should not be an issue. – Alfonso Nov 24 '10 at 11:46
  • The NSAttributedString class is only available on 4.x devices - are you running it on a 3.x device? – deanWombourne Nov 24 '10 at 11:56
  • Hmm, NSFont and NSParagraphStyle both conform to NSCoding (at least according to the docs). I'm sorry but I guess I cannot help you further. After a quick googling it seems though that errors involving NSCFType most commonly arise from memory management issues, so you probably should double check that your string does not get accidentally released too soon. – Alfonso Nov 24 '10 at 11:58
  • We have looked at the building of the NSAttributedString and try commenting out paragraph and fonts but this made no difference. Please could you tell us how to loop over the attributes of the string at the other end? We are basically reading HTML files in and parsing them into attributed strings. – jodm Nov 24 '10 at 11:59
  • @deanWombourne this is an iPad project which is running 3.2 and NSAttributedString is available there. – jodm Nov 24 '10 at 12:01
  • @frenetisch applaudierend when we pass it to the .text property we are now using [attributedstring retain] and we are still getting the error. – jodm Nov 24 '10 at 12:05
  • I was going to suggest enumerateAttributesInRange:options:usingBlock: but this is only available on iOS 4.0 and later. I am no expert in NSAttributedString but it seems that there is some way to access the attributes of the string also on 3.2. Otherwise check the attributes when you are creating them, although I don't really think this is the problem. – Alfonso Nov 24 '10 at 12:15
  • We have tried parsing an NSAttributedString with no style elements within it, and it saves to the managedObjectContext fine. However, as soon as we put in *any* NSFont or NSParagraphStyle element, it refuses to save and crashes. Any ideas? – jowie Nov 24 '10 at 14:53
  • My comment on my answer (http://stackoverflow.com/questions/4266226/storing-nsattributedstring-core-data/4269516#4269516) which has a link to a Developer Forums thread now has an answer to your question. – Joshua Dec 03 '10 at 07:12

7 Answers7

12

For anyone experiencing this problem I found the easiest solution:

In Core Data add an attribute, let's call it attributedText. Then define its type as Transformable. After you create the .h file, change the data type of attributedText from NSDictionary to NSAttributedString.

Now you can save the NSAttributedString in Core Data with no modification needed.

Recalling it is as easy as going:

myObject.attributedText

which will return your NSAttributedString!

Hope this helps someone.

pob21
  • 1,918
  • 2
  • 16
  • 17
  • This does not solve the problem of attributes not being NSCoding compliant – Berik Jun 25 '14 at 10:19
  • This helped me add a NSAttributedString to core data and seems to work well. In swift too. Thanks! (I am not using NSCoding though) – yuzer May 26 '15 at 22:33
  • @yuzer, do you mind sharing on the swift equivalent? – ToddJCrane Jun 09 '15 at 08:24
  • if you follow exactly what pob21 said to create a column of Transformable type. Then create your NSManagedObject Subclass, which will in your case be a swift file. Then simply change the type of the column in the file so that you have something like `@NSManaged var name: NSAttributedString` – yuzer Jun 09 '15 at 12:41
  • For anyone from the future, this won't work starting with iOS 11 when using binary stores (https://forums.developer.apple.com/thread/84588). I believe that the best solution is to use a value transformer. – zneak Dec 25 '19 at 16:32
6

I was checking the Apple Developer Forums and found a thread almost exactly the same as this question, one person had done this but unfortunately did not share the code. All they said was the following:

"In Core Data i have an transformable in the database and i us my own NSVauleTransformer. This is a subclass of NSValueTransformer and creates an attributed string from the data object and back.

Therefore i created a class called PersistableAttributedString which is NSCoding compliant. This class has a string and an array of attributes and builds the attributed string. I also created a class for the possible text attributes which is NSCoding compliant. Together the string and the attributes are all NSCoding compliant.

The class NSAttributedString is also NSCoding compliant, but the attributes are not, that's the problem."

Hope that might help.

Joshua
  • 15,200
  • 21
  • 100
  • 172
6

In Xcode 10 with automatic code generation this is a lot simpler than the other suggestions.

  1. Select the name of the Attribute and open the Data Model inspector (Command+Option+3)
  2. Set Attribute Type to Transformable
  3. Set Custom Class to NSAttributedString

Screenshot of Xcode attribute inspector

And that's it, now you can just save your data in your Swift code as you'd expect, e.g.:

detailItem.content = textView.attributedText

rodhan
  • 633
  • 7
  • 12
  • 1
    Unfortunately this results in a warning `'NSKeyedUnarchiveFromData' should not be used to for un-archiving and will be removed in a future release`, when using this approach on recent OS versions. – Ely Feb 23 '21 at 09:48
2

Another idea would be to create a Custom Class of NSAttributedString and somewhere use enumerateAttributesInRange:options:usingBlock: to get all the attributes of the string and then save the NSDictionary with the attributes and ranges in to Core Data aswell as the attributed string stripped of it's attributes.

Then when the string is loaded again you could apply the attributes that are in the dictionary to the attributed string using initWithString:attributes:.

Joshua
  • 15,200
  • 21
  • 100
  • 172
0

It's the font that's giving you grief - a CTDictionary is toll-free bridged to NSDictionary which implements NSCoding so should encode fine.

You might have to deal with the font yourself :( - here's a sucky way of doing it.

1) Instead of storing the NSAttributedString, break it down and put each of it's components into an array.

2) Go through the array - if you see font ref you must store just the information required to re-create this font - have a look at the CTFontCopyFontDescriptor function and the CTFontDescriptorCopyAttribute function should let you get font attributes as a string. Put all these into a NSDictionary which should store in core data fine.

3) Store this array in core data - hopefully all the items in the array will be NSCoding compliant so you should be fine.

...

To recreate your string, when you load from coredata, if you see an NSDctionary representing font attributes you should be able to re-create the fCTFontDescriptor and from that the font.

Then, put your string back together.

deanWombourne
  • 38,189
  • 13
  • 98
  • 110
  • May I ask why NSFont is causing grief, when it implements NSCoding? Unfortunately it also causes problems with NSParagraphStyle, so it doesn't appear to only be the font that is causing the grief. – jowie Nov 24 '10 at 15:43
  • The main reason we wanted to store the attributed string to Core Data was so we did not have to re-parse anything back when pulling it out of the database. We thought about this option earlier as there are only 8 unique styled paragraph as we though about having dictionaries of styles. If have to re=parse on output then there is no point and we may aswell just parse each time... :o( – jodm Nov 24 '10 at 15:48
  • Yeah sorry forgot to say and to add to @joe's point... The first thing it finds is a paragraph and it crashes on that too :O( – jodm Nov 24 '10 at 15:49
  • blast - I'd just assumed that NSFont didn't do NSCoding and that was why it was failing. Oh well, I should learn never to assume :) Given there's a client deadline, I'd just break it down and do it by hand :( – deanWombourne Nov 24 '10 at 16:11
-1

OK... Some kind of break through although not a good one...

If we NSLog the attributed string then we can see in there it has NSFont and NSParagraphStyle in. Although these are NOT NSFont and NSParagraphStyle in the code these are CTFontRef and CT Dictionaries of paragraph styles... These are not NS objects although in NSLog they output as these and therefore guess that is why we can not perform the "encodeWithCoder" selector on the object.

IF in the code we just do "NSFont;" the compiler says "NSFont undeclared" so what can we do as we only have the CT functions?

As my colleague said in the comments above if we set the ".text" property to just "NSAttrinutedString *string = [NSAttributedString alloc] initWithString:@"test"] it saves fine and if we remove all the styling from the one we WANT to save it also works!

THERE MUST BE A WAY OF STORING NSATTRIBUTED STRING WITH STYLING INTO CORE DATA... NO?

Any ideas greatly appreciated.

-JM

jodm
  • 2,607
  • 3
  • 25
  • 40
-1

I found a way to save attributed text in Swift 4 that does not use a header file. My Core Data store consists of an entity called "AttrNote" with attributes of "title" and "notes". "title" is of type "String" but "notes" is of type "transformable". Then within the view controller where note entry/editing is done I have the following for the save button:

@IBAction func saveBtn(_ sender: Any) {

var note: AttrNote!

//other code

if let title = titleTextField.text {
note.title = title
}
if let noteText = notesTextView.attributedText {
note.notes = noteText
}

And the function that is called to load the data has the following:

func loadNoteData() {
if let note = noteToEdit {
titleTextField.text = note.title
notesTextView.attributedText = note.notes as! NSAttributedString
}
}

And I have the following in ViewDidLoad that enables the B/I/U formatting options to appear with the selection of text:

notesTextView.allowsEditingTextAttributes=true

I am able to save attributed text and then view/edit it later.

Steve Robertson
  • 209
  • 5
  • 12