Core Data

Redefining Symbol Error and Core Data Entities

I have had modules switched off in Xcode for Trail Wallet since its introduction. I have a Core Data entity called “Category” and, when I tried to enable the modules feature (introduced in Xcode 5), I was hit with Redefinition of 'Category' as a different kind of symbol error.

Recently, however, things came to a head when I tried to import a mixed-source framework into Trail Wallet and it refused to see the Swift files.

The solution to getting Swift files in external frameworks to work in Objective-C projects, according to Apple’s Using Swift with Cocoa and Objective-C, is to import it as a module within Objective-C (e.g. @import ExternalFramework;).

So I assumed I was finally going to have to tackle entity renaming, which is handled by Core Data’s lightweight migration but only if it is set up correctly.

I generally dislike database migrations and try to avoid doing them if I can. I’ve never had an issue with a production version and migration (probably because I’m so paranoid about it), but it’s always nerve-wracking when a new version with a migration goes out into the world.

Previous searches had yielded no good news but I thought I should search one last time, which revealed this answer at Stack Overflow—I could avoid a migration after all.

The SO answer is a little light on details, so here are the exact steps I took:

1. Select the .xcdatamodeld file, then select the entity that’s causing the conflict (Category, in my case)

2. Select the Data Model inspector in the Utilities pane.

3. Rename the Class property:

A screenshot showing where to set the class name for a Core Data entity.

4. Re-generate the NSManagedObject subclasses by going to Editor -> Create NSManagedObject Subclass....

Xcode 7’s NSManagedObject subclasses work a little differently than they used to—it generates its own category that it is then free to overwrite should you change the entity in future versions, and leaves the core class for you to add additional methods and properties to.

5. If you have added additional functionality to the NSManagedObject subclasses, you’ll need to copy it across.

I had a category on my Category entity (of course I did) called Category+GetCategories.[h/m], so I had to copy out all of the API and implementation from these files and put it all in the newly generated CDCategory.[h/m] files.

6. Delete the old classes.

7. Update all references to the old class and replace them with the new class, in my case I had to replace Category and with CDCategory throughout the project.

It all worked just fine, no database migration or entity renaming required.

The only other gotcha was that, after attempting to run with the new framework, it was crashing out with a dyld: Library not loaded: @rpath/libswiftCore.dylib error.

This is caused by the build setting Embedded Content Contains Swift Code being set to No. Switching this to Yes allowed the project to run and use the Swift code in the framework.

Core Data and NSAttributedString

I’m currently creating an app that uses some weird formatting and inline images that can be handled nicely by NSAttributedString.

The app is bundled with a pre-populated database and I was considering populating the database with the already formatted strings and images.

Core Data supports NSAttributedStrings—just set the attribute to be Transformable and you can create and store attributed strings along with most attributes.

The interesting thing is that it can also store images within the strings if you add it using NSTextAttachment:

let attributedString = NSMutableAttributedString(string: "Hello images!")
var attributes: [String : AnyObject]?

if let font = UIFont(name: "AvenirNextCondensed-Medium", size: 30) {
    attributes = [ NSFontAttributeName : font ]
}

let range = NSRange(location: 0, length: attributedString.length)
attributedString.setAttributes(attributes, range:range )

// You can safely pass nil to both parameters if using an image
let textAttachment = NSTextAttachment(data: nil, ofType: nil) 
textAttachment.image = UIImage(named: "TestImage")        
let imageString = NSAttributedString(attachment: textAttachment)
attributedString.appendAttributedString(imageString)

coreDataEntity.transformableAttribute = attributedString

There’s a few issues with this:

  1. Your database will get big fast. Unlike the Binary attribute, there’s no way to tell Core Data to use external storage.
  2. You’re passing in the scale of image of the device that created the image. For example, if you’re running the app on an iPhone 6, then UIImage(named:) will return an @2x image and this and only this scale is what will be stored in the database

This will be fine if it’s going to be accessed on the same device that it’s created on, but if you use any sort of syncing or you want to deliver a pre-populated database in your app bundle, then you’re going to be passing around an image that might be the wrong scale for the device.

Also, storing attributed strings this way breaks any connection with dynamic text styles, so there’s no resizing of the string after it comes out of the database if the user changes the text size in Accessibility. This makes sense as the font is hardcoded into the attributed string so there’s no way of the system knowing that it should resize it, but it does make storing attributed strings in the database less useful if you want to provide dynamic text resizing (which you should).

Because of these limitations, I think I’m going to stick with formatting the strings and adding the image on the fly for the moment, but it’s good to know what’s possible with Core Data.