Data Persistence — CloudKit
Using CloudKit to save data to iCloud and retrieve it on other devices
Introduction
My last piece was about using NSUbiquitousKeyValueStore
to save key-value pairs to iCloud. Well, now it’s time to break out the big guns! Today, we’ll be using CloudKit to create, retrieve, update and delete (C.R.U.D.) data from iCloud.
I would love to do a multi-part series on using everything that CloudKit has to offer, such as C.R.U.D., subscriptions, notifications, and other things along those lines, so I have started here with the easiest (in my opinion) and that is C.R.U.D.
Using CloudKit, even apart from the NSUbiquitousKeyValueStore
, is a little more intense than just saving a key-value pair.
So let’s set out a little roadmap of what I want to cover in this piece:
- The Interface
- CloudKit Dashboard
- Saving Records
- Retrieving Records
- Updating Records
- Deleting Records
- General Notes
- Demo
What Is CloudKit and How Is It Useful?
CloudKit, at least in my opinion, is a data persistence layer that can really put your app over-the-top. It can give the user a sense of being so connected that they will be poised to continue using your app for the convenience alone. It’s a great feeling when a user is able to start something on their iPhone and then continue it on their iPad or Mac.
Most importantly, it provides the user with the ability to save more complex data than just key-value pairs, which comes in handy when a developer wants to make an even more complex app.
Getting started with CloudKit can be a bit tricky but I’ll be with you every step of the way. If you find the way I describe it difficult to understand, then I suggest heading over to the Apple Developer Documentation on CloudKit.
Requirements
- Xcode 10
- Swift 4 (and up)
- Apple Developer Account ($99 / year)
I am going to assume the reader is new to CloudKit because it was such a confusing thing for me to understand at first. However, once you work with it long enough and start to piece things together then it will start to click.
Getting Started
The first thing to do, as always, is to create a new Xcode project. We will select a Single View App
for this tutorial (the completed project can be found at the end of this article).
Next, name it whatever you want, for this project I will be naming it CloudKitDemo
.
Click next and save it somewhere where you’ll remember. Now let’s set up some simple capabilities in your app so we can get the CloudKit functionality within it.
Click your app name at the top of the Navigator
and choose the Capabilities
tab at the top. After that, find the iCloud option in the list of capabilities and turn the switch on. Make sure CloudKit
and Use default container
are selected. Once those three things are done your app is ready to use CloudKit!
The last thing we’ll need to do, it’s something I like to do to stay organized, is to make a Variables.swift
file so that we have all the variables we’ll need in one place.
To do this, press Command-N
, choose Swift File
, name it Variables
and you should be good to go!
This is what my final Variables.swift
file looks like:
- Line 2. Importing CloudKit will let me use some of its framework for a variable we’ll define later
- Line 4. This is an array of
Strings
that when we retrieve all of our records, all of the titles will go into this variable as multiple strings - Line 5. This is an array of
CKRecord IDs
, for every record you save, iCloud will assign it an ID. When retrieving the records we will put all the IDs into this array
The Interface
As with all my articles, I want the UI to be as simple as can be so you can see and understand the relationship between the code and the interface. By making it as simple as possible, it opens you up to understanding that you can connect the code we write here to any type of IBOutlet, IBAction or even a timer, which will help you when creating an app to give you a bit more freedom than you may think when reading a normal tutorial.
In UI I have set up, I added a text field and 4 buttons
. Each button will have its own action for C.R.U.D.
Next, we need to connect the interface to the code. In order to do this, go into the assistant editor by holding down Command-Alt-Enter
, control-click the text field, drag it over to ViewController.swift
, and release. You have made a IBOutlet
— name it whatever you want and do the same for the buttons.
Then control-click
the button and drag it over to ViewController.swift
, releasing when your cursor is under the ViewDidLoad
method. Mine ended up looking like this:
If you need more help making IBActions or IBOutlets, check out this YouTube video for more information.
CloudKit Dashboard
In order for you to save data to iCloud, some things have to be set up first. Some of the things I am going to show you are automatically set up when you go to save a record but I would much rather set up everything in the iCloud Dashboard
beforehand so we know that everything works.
First thing you’ll want to do is go to the developer dashboard where your iCloud Dashboard
lives. On the left side, you’ll see a list of identifiers which are all of the apps that you have flipped the On
switch in Xcode for iCloud. Find the app that you created and click on it.
Next, we will set up some things that will help you retrieve data. To retrieve the data you save to iCloud, you need to either query
or fetch
the data. To make doing either of those possible we have to set up some things in the Schema
section of the dashboard. Now just click New Type
then just type whatever type of “thing” you want to save.
This part was kind of hard for me to understand at first. The way I began to think of it was as a kind of category of what your app will be. I don’t think that was the best way to explain it, so here is an example: Say you’re making a note-taking app, a good name for this custom type is “Note”
. Or if you are making a scorekeeping app, name it “Score”
.
This will act somewhat as a “container”
for the data you’ll be saving in your app. So going back to the note-taking app example, inside of the custom type “Note”
you’ll be saving a title and content for each note. So titles and contents of every note you’ll save in the app will be saved under the custom type of “Note”
.
After you are done entering in a name, click Add Field
and enter a “title”
without quotes. Make the Field Type
a String
.
Now under the Custom Field
of the title you just created there is an Edit Indexes
button in blue. Click that and it will bring you to a screen that looks like the following:
Click Add Index
at the top of the screen, it will give you a row that has two dropdowns, one for the Field Name
and one for the Index Type
. I prefer to retrieve the records by the latest one that was modified, so I set it like the following:
You want to make sure to have at least the modifiedAt be QUERYABLE and SEARCHABLE
, and at least recordName be QUERYABLE
. Click Save Changes
in the lower left-hand corner and you’ll return to this screen:
The Code
Now with all the boring set up out of the way, it’s time to begin the exciting portion of the tutorial!
Here is all the code for ViewController.swift
. This is a lot longer than any of my other tutorials, so instead of going line by line in this whole thing, I’d prefer to go line-by-line and cover each action that will be performed. The first is saveBtn
, then retrieveBtn
, then updateBtn
, and finally deleteBtn
.
First off, however:
- Line 2.
import CloudKit
just makes it possible to be able to do all the things we need to with CloudKit - Line 8.
let privateDatabase = CKContainer.default().privateCloudDatabase
allows us to shorten our code later in the IBActions. I use a private database instead of public or shared because for this project we don’t need to be sharing any data with anyone else.
Also please don’t be intimidated by the long code! When it is broken up it is a lot easier to understand and follow, trust me!
Saving records
Saving a record is really what starts this whole process, without saving a record first, you cant retrieve, update, or delete anything. Here’s the code for saveBtn
:
- Line 3. This is setting the variable
title
to the value oftextField
in the interface - Line 5. This is the record of type
“Note”
. If you entered a differentCustom Type
from above, enter that in between the quotes for a string instead of Note. - Line 7. This is kind of like saving in
UserDefaults
, you set a value for a key off of the variablerecord
we created above. In theiCloud Dashboard
where we entered“title”
that’s the key for after theforKey:
in thesetValue
method. - Line 9. Now all we do is use the
privateDatabase
variable we set in the beginning ofViewController.swift
and use a.save method
. When it asks what record to save, just use therecord
variable we created above. - Line 11–17. This is just error handling at its minimum, if all goes well we should see
“Record Saved”
.
And that is it for the saving portion of the tutorial, not too bad right? Next, we will focus on retrieving the records back that we have previously saved.
Retrieving records
There are a couple of ways of retrieving records from iCloud. One way is to fetch a record, this only returns one record from the records’ ID you pass to it. The other way is to query all the records. Querying all of the records will retrieve all the records with the sort method you specify.
We are going to query for the records and return them in the order they were modified, which is how we set up the iCloud Dashboard earlier.
- Line 3. This will just tell our query that we would like the records returned to us sorted in some way, we will tell it in which way we want it in a few lines
- Line 5. This is the
query
that will do our retrieving of records, we pass into thepredicate
we set earlier - Line 6. Here we set how we want the
query
to be sorted, I have enteredmodificationDate
because that’s how we set it up in the iCloud Dashboard - Line 8. This is us creating an
operation
, so when we pass in thequery
variable, whenever the operation is run, it knows to perform a query - Line 10–11. This is just clearing the variables, we do this because if we didn’t and we ran a query several times in one app session, then the records would essentially be duplicated in the variables so we just clear them out so we don’t have that problem
- Line 13. This is where we actually get the records from iCloud, it’s important here to just append each record into an array and NOT to update any UI in here
- Line 15–16. Here we are just appending the correct CloudKit types to their respective array variables
- Line 20–29. This is where you’d update the UI, but be sure to do it in a
DispatchQueue.main.async
that way you’re not updating UI before the UI is ready to be updated. That part was kind of confusing but just know to update the UI in thequeryCompletionBlock
and inside of aDispatchQueue.main.async
method and you should be OK. - Line 31. This just adds the operation to the line of things to happen, without this, the query won’t run, so make sure to pass in your
operation
variable to this method.
Retrieving data is probably the hardest thing in CloudKit to wrap your mind around. As with anything in this tutorial, if you don’t understand something, please don’t hesitate to contact me or leave a comment down below.
On to updating records!
Updating records
As I stated above, we will be using fetch to update records. This is because we can fetch a record given its record ID. Ideally, we would have all of the data displayed in a UITableView, that way we could get the index of the item you wanted to edit and get the record ID element from the recordIDs
variable at that specific index (I’m going to make another tutorial covering that, but we won’t worry about it now).
- Line 3. Here I am hard setting a string that we will update one of our records’
“title”
with thisnewTitle
variable we set - Line 5. I am getting a
record ID
from the array that holds all of them. I am just getting the first one in the array because this is just a demo project, you can customize yours if you want. Also a side note, you have tosave and retrieve your records before calling update
, so that there are records that you can actually update - Line 7. This is where we
fetch
the record we want to update, we pass in therecordID
of the record we want to update and the method returns a variable for us to be able to edit the“title” key
but that comes later. - Line 9. This is just basic error handling, nothing crazy
- Line 11. This is where we set the records’
title
key to thenewTitle
variable, it is just like when we were initially saving the record - Line 13–23. This is exactly the same as the
saveBtn
code that we wrote earlier, passing in therecord
you want to save and do some basic error handling…because it is somewhat important. - Line 29. This is the rest of the error handling from when we were
fetching
the record to begin with
Updating records can be hard to grasp but if you think of it as two different parts, one that fetches a record by its recordID
and another that is saving
it the same way you did for the saveBtn
, then it should be easier to understand.
Deleting records
Deleting records is just about as easy as saving a record. Let’s take a look:
- Line 3. Just like when updating a variable we need to access a record by its
recordID
. - Line 5–15. Then after getting a
recordID
for the record we want to delete, we just…delete it! Call delete on theprivateDatabase
and do some basic error handling…really nothing hard at all!
General notes
And that is it for all of the code! It is really not all that bad. Sadly there is still a lot of information to take in with this article.
My opinion would be to take this article a piece at a time. Start with the top and work your way down. Bookmark this and come back to it later. Don’t get intimidated by the amount of information given here.
CloudKit is not something you want to let defeat you mentally, because once you’ve been looking through so many different tutorials, it can get extremely confusing. Hopefully, this one could be the only one you’ll ever need (at least as far as C.R.U.D. goes).
When working with a real app, make sure to do a check every time the app launches to make sure the user of the app is signed into CloudKit. If they’re not, either ask them to sign in or provide a second data persistent layer to make sure none of their data gets lost.
Demo
All you have to do now is run the scheme that deploys the app to the iPhone simulator (it may take a few minutes if it’s the first time running the app to the simulator).
Type a message into the text field and click the save button.
- Type a
“title”
into thetextField
and clickSave
— you should seeRecord Saved
in the output screen - Click
Retrieve
and you should see thetitles
array output with our onerecord
that we have saved and therecordIDs
array output with the ID of the onerecord
we’ve saved - Click
Update
and it will fetch thatID
we have for our one record and update it with the new title we have coded into theupdate
function. ClickRetrieve
again and you should see the new title in the output screen - Click
Delete
and it will access thatID
we have for our one record and it will delete it - Click
Retrieve
again and you should see two empty brackets in the output screen looking like this: []
Conclusion
I say “that’s it for CloudKit — C.R.U.D” but there is a lot to it.
Using iCloud in your app though will no doubt be really good for user retention and making sure every user can feel as connected as possible using your app. Including CloudKit in your app is definitely worth the time investment if you can make it through all the thick information.
I am continuing on in my quest of attempting to make easy to follow tutorials covering harder topics in the Swift ecosystem.
If anyone at all has any issues with my tutorial or have any questions please don’t hesitate to send me a message on here or leave a reply on this and I will respond as quickly as possible.
Thanks all for reading!
Project Files
Here’s a link to the GitHub project for this tutorial.