When making an iOS application of any complexity, Core Data is nearly always the right choice for your data layer. It is a very mature framework, having been around since 2005, in OS X Tiger.
The fact that it is a separate framework, not part of AppKit (OS X) or UIKit (iOS), means something very useful: an iOS app and a Mac app can both use the same data model and object classes. Furthermore, using an OS X technology called bindings, you can make a Mac app to edit and create the content for your iOS app almost entirely in interface builder!
This approach is useful in the following scenarios:
- Unit testing: free yourself from the tedium of writing code to populate temporary model objects to support your test cases
- Independent development: web services not ready yet? Get on with the rest of the app by populating your model layer in advance
- Prototyping: easily create sample data sets, and give non-developers the ability to edit or set these up
- Static content: if your app contains static data as part of the bundle, you now have an easy way to build this data. Don’t waste startup time parsing JSON or XML into your database on first launch.
- CMS “replacement” – with a little more polishing than will be evident in this article, the Mac app can be given to your clients or other stakeholders so that they can directly control the content.
To your existing Xcode iOS project, add a new target. As you navigate through the options, select an OS X, Cocoa application, using a document-based core data template. We want this so that it gives us the flexibility to edit multiple database files if we like. Think TextEdit rather than iTunes.
You get a lot for “free” when developing for the Mac – window and file management and undo support, and, quite significantly for iOS developers, you can edit any text field without having to move the keyboard out of the way! As part of the basic app template you’ve got what doesn’t look like much – an NSPersistentDocument subclass, a .xib file for the main menu (essentially the contents of the menu bar) and another .xib for your document window. You’ve also got a managed object model – we can delete that, since we already have a model from the iOS app.
Select the model and any relevant NSManagedObject subclasses from the iOS app and, using the file inspector, make sure they are added to the new target as well as your existing one. NSPersistentDocument will automatically pick up the model file and use it.
First, a sad note. In previous versions of Xcode you could literally drag objects from your data model into interface builder and it would create a whole UI for them. Unfortunately this ability disappeared in version 4, and shows no sign of returning. So the following steps are slightly more complicated, but have the advantage that you at least get to learn something whilst getting the job done.
If you’re fed up of writing the same code to link a fetched results controller to a table view, a) shame on you for not creating reusable code and b) hold on to your hat. I’m going to assume you have an entity in your model called, for want of a better cliché, Person. I’m going to further assume you want to display a list of the Persons in the database in a table.
Open the document xib file. Drag in a table view. Size it how you want. Now, find an object called NSArrayController. Drag one of those into the document navigator on the left of the window
Open the properties inspector and set the type to Entity, enter Person, and check “Prepares Content Automatically”. Open the bindings inspector and find the Managed Object Context binding, select the File’s Owner, and enter managedObjectContext in the key path
You’ve just created a binding. There are plenty of tutorials
out there to delve further into them, but basically it’s a wrapper around key-value observing. Your array controller will now get its managed object context from the File’s owner, and because you’ve told it the entity name you want it to use, it will also allow you access to all of the Person objects in the database and allow you to link these to the UI in your mac app.
You can populate a table via bindings:
- Select the table view (it will take a few clicks, the view hierarchy in Mac apps is pretty deep).
- Open the bindings inspector and choose the Content binding. Set this to your array controller’s arrangedObjects path.
- Bind the selectionIndexes property to the array controller’s selectionIndexes.
That’s taken care of all your table view data source methods! The array controller can tell the table view how many rows it needs, update it when items are added or removed, and manage the selection, without a single line of code.
Now, you can populate the columns in the table. Choose a column and bind it to the array controller’s arrangedObjects.name path (assuming the Person entity has a name property). Repeat for any other columns you wish to have in the table. That will give you meaningful column data. Also for free, you will be able to sort the table on any of these columns, and double-click to edit the value in each field.
Drag in a couple of buttons, label them Add and Remove. Control-drag to the array controller, and link to the add: and remove: actions. That’s all you need to do to allow creation and deletion of objects through the UI.
Build and run the app – you should be able to add and remove entities, and double-click to edit the properties. Save the document as a SQLite file – this can be added to the bundle for your iOS app and either copied to the documents directory (for editable data) or read straight out (for read-only reference data).
The table view works as a nice Master view for a master-detail structure. The detail view is also trivial to create – drag in text fields, check boxes, date pickers and the like and bind their values to the array controller’s selection, instead of arrangedObjects.
If your model has to-many relationships, you can add additional array controllers for the related entities, and bind the contentSet of the new controller to the selection.relationship key path of the original controller.
Note that not a single line of code has been written for the Mac app. There are a couple of instances where you may need to write some:
- To support migration between model versions – the default NSPersistentDocument doesn’t use any options when creating the persistent store coordinator. You can override configurePersistentStoreCoordinatorForURL… and pass in the same options you would in iOS.
- Overriding description methods can be useful if you are populating a pop-up button or want to show a little more detail in a label than binding to a single variable will give you
- Storing images won’t work out of the box, since the Mac app will archive them as NSImage objects and the iOS app won’t be able to decode them into UIImages. For that, you need to write a simple NSValueTransformer subclass to convert the image to and from NSData, using the TIFF representation. UIImages on the iOS side will be able to read that.
A very simple example project can be found on GitHub
. Will something as useful as bindings ever appear on iOS? Who knows. In my experience, they are great for simple use cases as described here, but can very quickly turn nasty if you try to do anything involved with them, and are very difficult to debug. But for the moment, they are a fantastic tool for making your development life easier without having to write a lot of repetetive code.