Offline persistence with the Swift client library
This guide will show you how to use contentful-persistence.swift to synchronize Contentful data to your local datastore.
Contentful's Content Delivery API (CDA) is a read-only API for retrieving content from Contentful. All content, both JSON and binary, is fetched from the server closest to a user's location by using our global CDN.
We publish client libraries for various languages to make developing applications easier.
Requirements
This tutorial assumes that you understand the Contentful data model.
Authentication
For every request, clients need to provide an API key, which is created per space and used to delimit applications and content classes.
You can create an access token using the Contentful web app or the Content Management API.
Setup
Persisting data to disk for use when offline is important for providing a pleasant user experience. The Contentful persistence library provides a SynchronizationManager
type which interfaces with the /sync
endpoint of Contentful's Content Delivery API to synchronize all data within a Contentful space to a local data store. It relyies on property mappings provided by you to return types of your own definition.
In order to create a SynchronizationManager
instance, you will first need to define your data model by passing in your NSManagedObject
subclass types, which must conform to the Persistable
protocols, into your PersistenceModel
.
let model = PersistenceModel(spaceType: SyncInfo.self,
assetType: Asset.self,
entryTypes: [Image.self, Gallery.self, Author.self])
You will also need to tell the SynchronizationManager
where to store the data by passing in a storage class which conforms to the PersistenceStore
protocol. There is a default implementation of the PersistenceStore
using Core Data, but if you want to use a different local store, you can create your own.
let store = CoreDataStore(context: self.managedObjectContext)
let syncManager = ContentfulSynchronizer(client: client,
// Store separate entities for every locale in the space.
localizationScheme: .all,
persistenceStore: store,
persistenceModel: model)
In this case, it's your responsibility to provide the right NSManagedObjectContext
for your application. If you are working with Core Data in a multi-threaded environment, make sure to read Apple's Core Data Programming Guide. Depending on your setup, you might need to create different managed object contexts for writing and reading data. While you can use the CoreDataStore
class for querying, you don't have to.
The mapping between your persistence model classes and Contentful data is done by conforming to the Persistable
protocols mentioned above. In the example below, we define the mapping between a Contentful content type with the identifier "author" to our NSManagedObject
subclass Author
. Note that the system properties required by Contentful, such as id
and updatedAt
should not have a mapping defined in the fieldMapping()
method.
class Author: NSManagedObject, EntryPersistable {
static let contentTypeId = "author"
// Properties required by Contentful.
@NSManaged var id: String
@NSManaged var localeCode: String
@NSManaged var createdAt: Date?
@NSManaged var updatedAt: Date?
// The fields defined on your Author content type.
@NSManaged var biography: String?
@NSManaged var name: String
@NSManaged var twitterHandle: String?
@NSManaged var authorInverse: NSSet
@NSManaged var createdEntries: NSOrderedSet
@NSManaged var awesomeProfilePhoto: Asset?
// Map field names on your Contentful entry to property names on your class.
static func fieldMapping() -> [FieldName: String] {
return [
"name": "name",
"biography": "biography",
"twitterHandle": "twitterHandle",
"createdEntries": "createdEntries",
// We can name our class property however we like.
"profilePhoto": "awesomeProfilePhoto"
]
}
}
Keep in mind that for AssetPersistable
types, the URL of the underlying media file will be stored along with other metadata, but no caching of the actual documents will be performed.
Special notes on the data model editor in Xcode
- You will want to make sure that code generation is turned off in Xcode's "Data Model Editor". The editor can be found in the right side-bar when viewing your
.xcdatamodel
file. - You will also want to ensure that the
id
andlocaleCode
properties on each of yourEntryPersistable
andAssetPersistable
types is set to non-optional. This can also be acheived through the data model editor. - Lastly, in the "Configuration" section in the
.xcdatamodel
file, ensure that the module name is prefixed before your class name if you are using Swift classes. In the app (and Xcode target) we are working on is called "AwesomeApp", the class name for ourAuthor.swift
file would be set toAwesomeApp.Author
.
Syncing the data
If you've made it this far, you've done all the setup necessary to sync data to your CoreData store, congrats! All that's left to do is to make the appropriate calls with via the SynchronizationManager
, the update your UI using using the fetch results from your local store. Note that if you don't have a localeCode
predicate specified, you will get data for all the locales you have synced with. The LocalizationScheme
used by the libary is specified during initialization of the SynchronizationManager
.
syncManager.sync { result in
do {
// In this case, we'll only fetch the entries that are localized to Mexican Spanish as our app is currently being used by somebody in Mexico.
let authors: [Author] = try self.store.fetchAll(type: Author.self, predicate: NSPredicate(format: "localeCode == 'es-MX'"))
} catch {
// Handle failure to fetch from CoreData.
}
}
Next steps
Not what you’re looking for? Try our FAQ.