Question

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

Was it helpful?

Solution

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.

OTHER TIPS

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.

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

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:.

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.

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.

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

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top