IntroductionWelcome| 00:00 | (music playing)
| | 00:04 | Hi, I'm Todd Perkins.
| | 00:06 | Welcome to Developing for
the Apple iCloud API with iOS.
| | 00:11 | This course is designed to demonstrate the key
features provided by Apple in the iCloud API.
| | 00:17 | iCloud enables data syncing between
multiple devices using the same app, as well
| | 00:22 | as multiple devices using different
apps created by the same developer.
| | 00:27 | The topics we'll discuss include
building a note-taking app, setting the app
| | 00:31 | up for iCloud integration, and multiple ways to
integrate iCloud functionality into your apps.
| | 00:37 | I'll also show how to manage conflicts that
can arise when devices sync through iCloud,
| | 00:43 | so that changes made on one device will
appear on another device in a way that
| | 00:47 | a user would expect.
| | 00:49 | This course covers a topic
that is exciting to me personally.
| | 00:52 | So I hope you can share in that
excitement as we go through Developing for the
| | 00:56 | Apple iCloud API with iOS.
| | Collapse this transcript |
| What you should know before taking this course| 00:01 | While you will need some Basic iOS and
Objective-C experience coming to this
| | 00:06 | course, you won't have to be too advanced.
| | 00:09 | So if you've already watched an iOS
Essential Training Course, you should be
| | 00:13 | good to go for this course.
| | 00:15 | But definitely having any
extra programming background helps.
| | 00:19 | So the more experience you
have with Objective-C, the better.
| | 00:23 | But again, coming into this course, I'm
really expecting you to just have some
| | 00:27 | basic familiarity with
developing an iOS application.
| | 00:32 | So with that knowledge, to follow along
testing all of the iCloud functionality
| | 00:37 | in the course, you also need an Apple
developer account and a device to test
| | 00:41 | your app on, because iCloud
does not fully work in a simulator.
| | 00:46 | Ideally you would have two devices. So
again, knowledge of the programming, an
| | 00:52 | Apple developer account, and one
or more devices to test iCloud on.
| | Collapse this transcript |
| Using the exercise files| 00:01 | If you are a premium subscriber to lynda.com
then you have access to the Exercise
| | 00:05 | Files for this course.
| | 00:07 | Exercise Files are organized by Chapters.
| | 00:10 | You'll also see a folder called assets.
The assets folder has the graphics that
| | 00:15 | are used, which are simply app icon images.
| | 00:19 | Each chapter contains the
Exercise Files used for that chapter.
| | 00:22 | The individual exercise files for each
movie are organized into folders, with
| | 00:27 | the final and start states
being saved in separate folders.
| | 00:31 | That way you could follow along and if
you get stuck somewhere, you can open up
| | 00:36 | the final version of the
files and see what's different.
| | 00:39 | If you don't have access to the
Exercise Files, there is no need to worry.
| | 00:42 | Throughout this course I'll show
you how you can build the exact same
| | 00:46 | application without having the
Exercise Files to start with.
| | Collapse this transcript |
| Demoing the finished app| 00:00 | Here is the finished
version of this application.
| | 00:03 | As you can tell, it's pretty basic, you
tap the plus button to create a note, and
| | 00:09 | I'll call this note Example note.
| | 00:12 | Then I can save the note by
hitting the Notes button to go back.
| | 00:17 | Of course I can edit the note if I want
to by tapping the note again, call this
| | 00:21 | Example note changed, and
then go back to save it.
| | 00:26 | Now the great thing about this
is with the iCloud integration, I
| | 00:29 | automatically see this update appear
on any devices that are logged into the
| | 00:34 | same iCloud account.
| | 00:35 | Typically this update is going
to happen within 10 to 15 seconds.
| | 00:40 | And right now you can see the
update happened on the other device.
| | 00:44 | So I have my second device here
with that exact note updated on it.
| | 00:49 | Let's say I wanted to delete
this note from my second device.
| | 00:52 | When I delete that note, 10 to 15
seconds later, depending on your connection
| | 00:57 | speed, it might take longer, or shorter,
you're going to see that note disappear
| | 01:02 | from my original device.
| | 01:04 | That's really the gist of iCloud, is
that we have this automatic syncing
| | 01:07 | that happens semi-magically in
the background, and we create this as
| | 01:12 | developers so that users have the
easiest experience integrating all their
| | 01:17 | data between multiple devices.
| | 01:20 | So that's the finished version of our
application, and throughout this course
| | 01:23 | we're going to look at how to build
all the different parts of it and how to
| | 01:26 | make sure that they sync
together in the appropriate way.
| | 01:29 | So let's move forward in building our
application to integrate with iCloud.
| | Collapse this transcript |
|
|
1. Building the Note-Taking AppUnderstanding class structure for this app| 00:00 | Before we start running the code for our app,
| | 00:03 | I wanted to go over the basic classes and
files that we're going to be working with,
| | 00:08 | so you have an idea about how the
app is organized before you create it.
| | 00:12 | This is actually the finished version
of the app, and for those of you who have access
| | 00:16 | to the Exercise Files, I didn't provide
files for this movie, because I just want
| | 00:21 | you to watch and just look on the screen here.
| | 00:24 | And I'm at MainStoryboard.storyboard,
and this application is a storyboard
| | 00:29 | application that's based on
the master detail template.
| | 00:33 | And so it's going to be a note-taking
app, and in a storyboard you will see that
| | 00:39 | there is a Master view with a table
view in it called Notes, and then the
| | 00:44 | detail view has the text in it.
| | 00:46 | So you create a note by clicking the
plus button, the View with the Table view,
| | 00:52 | and then you click on the note, and you
see the note in a single Note view page.
| | 00:59 | All the data is managed in the data
class, and there are constant values in
| | 01:04 | Constants.h. The data class has a number
of methods that allow you to manipulate
| | 01:11 | the data that's stored
inside of an NSMutableDictionary.
| | 01:15 | Again the MasterViewController
controls the Main View, which has the table
| | 01:21 | view, and the DetailViewController controls
the view when you're looking at a single Note.
| | 01:28 | The finished application will allow you to
create notes by clicking the plus button.
| | 01:34 | After you create a note you can go
back and see that the note is saved.
| | 01:40 | You can click on the note to see and edit it.
| | 01:47 | Notes are then saved to user defaults,
which means they are saved on your device
| | 01:52 | until you delete the application.
| | 01:53 | So if I click the Home button and the
Stop button and run the application again,
| | 01:58 | we'll see that that same note that I
added is there along with the other notes
| | 02:03 | that were there from before.
| | 02:04 | So that's an overview of the classes
we'll be working with in this chapter.
| | 02:11 | This organization is called the
ModelViewController design pattern.
| | 02:16 | The Model represents the data for your
app, which is stored in our data class.
| | 02:21 | The View is the visual part of your app,
which is our storyboard. And Controller
| | 02:26 | is the part of the app that contains the
logic for manipulating the model to update
| | 02:31 | the view. And that's split up
into the MasterViewController and
| | 02:35 | DetailViewController classes.
| | 02:36 | So now that you've seen an
overview of how this app is set up, let's
| | 02:41 | start building it.
| | Collapse this transcript |
| Creating the project| 00:00 | Now let's create the project for the app.
| | 00:03 | In Xcode, click Create a new project or
go to File > new project, and choose under
| | 00:09 | iOS > Application > Master-
Detail Application template.
| | 00:14 | This template isn't far away from what
we want the end result of our notes app
| | 00:18 | to be, so it's going to help us a lot.
| | 00:21 | I'll click Next, for Product Name I'll
type Plain Ol' Notes, for Organization
| | 00:26 | Name I am going to leave that blank, for
Company Identifier I am going to put my
| | 00:30 | reverse domain structure
which is com.toddperkins.
| | 00:33 | Of course you could use
your own here if you want.
| | 00:36 | For Devices choose iPhone and then
check Use Storyboards and Use Automatic
| | 00:39 | Reference Counting. And click Next.
| | 00:42 | I am going to save this in the
Chapter_01 folder in the creating_project
| | 00:47 | folder and click Create.
| | 00:50 | In the project I am going to go to
MainStoryboard.storyboard, and I am going to
| | 00:56 | scroll over to find the
MasterViewController and change Masters to Notes, and
| | 01:03 | then I'll go to the
DetailViewController and change Detail to Note.
| | 01:07 | This is going to the single note view.
| | 01:09 | Then I am going to replace this UI
label with a UI Text view, so I'm going to
| | 01:15 | scroll down in the Object area until I
find text view, and just drag and drop
| | 01:21 | that into place, making sure it's
aligned properly, and now I'm going to go to the
| | 01:27 | DetailViewController.h and
make a small tweak to the code.
| | 01:30 | I'm just going to change UILabel
to UITextView, and I'm going to change
| | 01:39 | detailDescriptionLabel to tView,
and I'll save that file and go to
| | 01:45 | DetailViewControl.m and make sure I
change where the detailDescriptionLabel
| | 01:50 | is mentioned to tView. And then we'll
need to go back to that storyboard and
| | 01:56 | make the connections.
| | 01:57 | So I'll save this file and go to
MainStoryboard.storyboard, click the
| | 02:03 | Connection inspector button, click
Detail View Controller on the left side of
| | 02:08 | the screen, and then just click and
drag from tView onto that text view so
| | 02:14 | the connection is made.
| | 02:15 | So now you should be able to save
and test the app in the simulator. And
| | 02:20 | simulator you see Notes, I can click
plus to add a new item, I can add as many
| | 02:30 | as I want, and it shows the timecode of
the item, and if I click it, and I see the value
| | 02:35 | in that UITextView.
| | 02:37 | So now I've successfully created our
project and set up the storyboards.
| | Collapse this transcript |
| Building the data model and constants| 00:00 | Now, as we have discussed earlier,
this app is going to use the
| | 00:05 | ModelViewController design pattern.
| | 00:07 | So as part of the model, we're going to
create a class to hold all the data and
| | 00:12 | we'll access the data through static methods.
| | 00:14 | We'll also create a file
to hold our constant values.
| | 00:18 | So I am going to press Command+N to
create a new file, and I'll choose
| | 00:23 | Other, Empty, and click Next, we'll call this
file Constants.h, and then I'll click Create.
| | 00:31 | In Constants.h I'm going to define three values.
| | 00:35 | So, we'll define one called kDefaultText,
we'll set it equal to New Note, this is
| | 00:44 | going to be the default text when
you create a New Note, and then we'll define
| | 00:48 | another value called kAllNotes, this is
going to represent the key that holds
| | 00:54 | all the notes in user defaults.
Call this notes. And then finally, we'll
| | 01:01 | define kDetailView, that will represent the
DetailViewController, and we'll call this showDetail.
| | 01:06 | Save the file, and press
Command+N to create a new file.
| | 01:12 | Under Cocoa Touch, choose Objective-C
class and click Next. The Class name is
| | 01:16 | going to be Data, which it will be a
subclass of NSObject, and I'll click Next. And
| | 01:23 | then, press return to create the file.
| | 01:25 | So what we'll do here is just define
NSMutableArray that's going to store all of our data.
| | 01:32 | So in the header file, let's
declare a static method that returns an
| | 01:36 | NSMutableDictionary, and
we'll call this getAllNotes.
| | 01:43 | And then, I am going to copy this line
of code and then paste it in Data.m. Now
| | 01:59 | in Data.m, above that method I just
pasted in, I am going to declare a static
| | 02:05 | NSMutableDictionary called
allNotes, and I am going to return that at the
| | 02:17 | bottom of getAllNotes.
| | 02:22 | Above the return value, I'm going
to instantiate all notes if it's not
| | 02:27 | already instantiated.
| | 02:29 | So, we'll check to see if allNotes is equal to
nil. If that's the case, we'll set the
| | 02:36 | value of allNotes by creating an
NSMutableDictionary. So allocate it, and we'll
| | 02:48 | call initWithDictionary.
| | 02:49 | And then in double brackets I
am going to call NSUserDefaults,
| | 02:58 | standardUserDefaults, and in the outer
brackets, dictionaryForKey, and the key is
| | 03:06 | going to be kAllNote.
| | 03:09 | And since that's going to come from our
constants file we'll need to import that
| | 03:13 | constants file at the top of our code.
| | 03:15 | So, import Constants.h, and for
that string type in kAllNotes.
| | 03:22 | So you can save the files now.
| | 03:26 | Now we've created the classes
that we need for our data model.
| | Collapse this transcript |
| Creating helper methods for manipulating data| 00:01 | Let's now create some static helper
methods so that we can easily manipulate the
| | 00:05 | data in our data class from
anywhere in our application.
| | 00:11 | Under getAllNotes I am
going to declare a few methods.
| | 00:13 | Again, they are all going to be
static and they're going to revolve around
| | 00:17 | manipulating our
NSMutableDictionary that holds all the notes.
| | 00:22 | So the first one is going to return
void, and we will call it setCurrentKey. I'm
| | 00:27 | going to receive an NSString called key.
| | 00:35 | The next one, we'll go with that
current key method, and it will return an
| | 00:39 | NSString, we will call it getCurrentKey.
| | 00:42 | The current key is going to correspond
to the note that's being edited currently.
| | 00:48 | So this will represent the key for the
currently edited note and the value will
| | 00:54 | be the text inside of the note.
| | 00:55 | The keys will actually
just be timestamps as well.
| | 01:00 | So on the next line, another
static method, returning void, called
| | 01:05 | setNoteForCurrentKey.
| | 01:12 | So this will receive an NSString called
note, and then on the next line, we'll call
| | 01:20 | this setNote and then a colon,
| | 01:23 | and we'll receive an NSString right
there called note forKey, and then we'll
| | 01:31 | pass in the key, that's an
NSString as well, we'll call that key.
| | 01:37 | On the next line we are going to declare a
method called saveNotes, it will return void.
| | 01:42 | On the next line, removeObjectForKey, and
we'll pass in the key so, that's going
| | 01:55 | to be an NSString. Again, we'll call it key.
| | 02:01 | And then what I am going to do is
actually just copy and paste all of this code
| | 02:06 | that we just wrote, and save the file,
and go into Data.m, and I am going to
| | 02:11 | paste the code right under getAllNotes.
| | 02:14 | Now for each of these methods I am
going to delete the semicolon, go to
| | 02:19 | next line, and add some curly braces.
| | 02:24 | And once that's done I am going to
scroll to the top of my code and declare a
| | 02:30 | static NSString called CurrentKey.
| | 02:33 | So I will do that right under
the NSMutableDictionary allNotes.
| | 02:38 | Again that's an NSString called currentKey.
| | 02:43 | So in setCurrentKey method, I'll set
it to currentKey = key, and in the get
| | 02:50 | method I'll return it.
| | 02:51 | So return currentKey, and then setNote for
current key, I'm going to call the method self.
| | 03:01 | Since this is a static method, self means
the class. setNote, passing in the note
| | 03:10 | received forKey, currentKey.
| | 03:14 | And then in setNote for key, I'm going
to manipulate the NSMutableDictionary.
| | 03:20 | So allNotes setObject:note forKey:key.
In saveNotes are going to save the
| | 03:29 | values to NSUserDefaults, so
double brackets, and the inner brackets,
| | 03:35 | NSUserDefaults standardUserDefaults,
setObject, and object is going to be
| | 03:41 | AllNotes, and the key is going to
KAallNotes, remember we declared that in
| | 03:47 | the Constants file.
| | 03:49 | So I'll go to removeObjectForKey, and
again we are going to manipulate our
| | 03:52 | NSMutableDictionary AllNotes by calling
removeObjectForKey, passing in the key received.
| | 03:59 | So a very simple, straightforward
manipulation of our NSMutableDdictionary,
| | 04:03 | but we can easily access all of that
information straight through our data
| | 04:08 | class static methods.
| | Collapse this transcript |
| Adding notes| 00:00 | Now we are going to look at
adding the notes to our app.
| | 00:04 | This is where you're going to see the
Master Detail template app really help a
| | 00:10 | lot to creating notes taking app.
| | 00:12 | So you go to MasterViewController.m, and
in the Import statement area we are going to
| | 00:19 | import both the Constants
file and the Data Class.
| | 00:26 | So Constants.h and Data.h. If you
scroll down a little bit you will see an
| | 00:31 | NSMutableArray declared called objects,
and objects is going to hold all of the
| | 00:36 | data that's going to appear in the UITableView.
| | 00:40 | So by manipulating that objects array we can
control what appears in the table in the app.
| | 00:47 | So let's do that by scrolling down
below insertNewObject. We're going to declare
| | 00:53 | our own method called makeObjects. And
makeObjects is simply going to instantiate
| | 01:02 | objects to an NSMutableArray, so we will
call it NSMutabelArray, arrayWithArray, and
| | 01:09 | the array is going to come
from the keys in our data class.
| | 01:13 | So first we will need to access that dictionary.
| | 01:17 | So Data, getAllNotes, and then allKeys.
| | 01:23 | So that's going to return an NSArray,
which we're wrapping within an NSMutableArray.
| | 01:30 | That way we can manipulate it.
| | 01:31 | So let's scroll up to viewDidLoad, and
right below that comment, it says Do any
| | 01:39 | additional setup after loading the view,
| | 01:41 | we're going to call self makeObjects.
| | 01:45 | Now if you look at this ad button
declaration, again this is part of the
| | 01:48 | template that already exists for us.
| | 01:51 | The add button calls
insertNewObject when clicked.
| | 01:55 | So let's scroll down to
insertNewObject and we'll delete the code that
| | 02:00 | instantiates the objects array, so
that's that if statement there,
| | 02:05 | and we'll replace that by creating a
string key based on the NSDate, which is
| | 02:11 | basically just the timestamp.
| | 02:13 | So NSString, call it key, set it
equal to in [[NSDate date]] so that's the
| | 02:27 | current date, down to the
second, and then description.
| | 02:31 | So its going to give us a string for the date.
| | 02:35 | So on the next line we are going to
set the current note, so Data setNote, and
| | 02:42 | the note is going to be the default
text, so that's just kDefaultText coming
| | 02:46 | from Constants.h, and for key its going to be
Key, on the next line Data setCurrentKey key.
| | 02:58 | And now it says _objects insertObject
we're going to replace NSDate date just with key.
| | 03:03 | And at the bottom of this method we are
going to segue to the detail view controller.
| | 03:12 | Now we want to do that because whenever
you add a new note, we don't want to see
| | 03:17 | that there's a blank new note added to
the table view. We want to create that new
| | 03:22 | note in the table view and then
transition right into that detail view so the
| | 03:25 | user can just start writing a note right away.
| | 03:28 | So to do that, call self
performSegueWithIdentifier, the identifier is going to
| | 03:36 | come from Constants.h, and that is
kDetailView, and the sender will be self.
| | 03:45 | So at this point we should be able to
save and test and just click on the plus
| | 03:50 | button and see the
transition to the detail view.
| | 03:53 | So now we still see that timestamp in
there, so let's make sure that when you
| | 03:57 | create a new note, that there's
actually nothing written in that note area.
| | 04:01 | So let's stop the app and go into
DetailViewController.m, scroll down, and notice
| | 04:11 | that in viewDidLoad we are
calling self configureView.
| | 04:15 | So again, that comes from the template.
| | 04:16 | And so what I'm going to do is I am
going to set the text field to blank.
| | 04:23 | So I am going to delete everything in
configure view except for that comment, and
| | 04:27 | just type self.tView.text
equals a blank NSString.
| | 04:34 | So let's save and test that again.
| | 04:36 | And we should see that when you click
the plus button, we transition to the
| | 04:40 | detail view and its blank, and then
if we want to we can click in there
| | 04:45 | and start typing text.
| | 04:46 | Now of course that would be better
if the keyboard showed automatically.
| | 04:50 | To do that, all we have to do is make
that text view become first responder.
| | 04:55 | So in brackets, self.tView becomeFirstResponder.
| | 05:00 | So with that line of code, we can test,
we will see the transition to the
| | 05:04 | detail view, that the text field is
blank, and that the keyboard automatically
| | 05:11 | comes up. And there we go.
| | 05:14 | So we have some test text in there.
| | 05:18 | So now we have successfully created a
note in the table view and transitioned
| | 05:24 | over to the detail view with the
keyboard ready to type out a note.
| | Collapse this transcript |
| Displaying and saving notes| 00:00 | Now that we have a system for adding
notes, let's add the functionality to save
| | 00:06 | the notes that we write and
display notes that we've saved.
| | 00:10 | Go to MasterViewController.m and
scroll down to the bottom and find the
| | 00:14 | prepareForSegue method.
| | 00:15 | In this method, I am going to change
NSDate to NSString. Let's go to the next
| | 00:23 | line, and we are going to
set the current key for data.
| | 00:31 | So Data setCurrentKey, and just pass an object.
| | 00:35 | So now save this file and
go to DetailViewController.m.
| | 00:38 | Now let's scroll down to configureView.
| | 00:42 | Now the first thing we want to
do here is import the data class.
| | 00:45 | So scroll to the top and import Data.h
and Constants.h. And then, scroll down
| | 00:55 | into configure view, and what we are
going to do is check to see if the
| | 01:00 | current note is blank.
| | 01:02 | So let's delete self.tView.text equals
the blank string, and what we are going to
| | 01:08 | do is create a string, let's create an
NSString called currentNote, and we will
| | 01:17 | set it equal to Data getAllNotes,
objectForKey, and then some brackets,
| | 01:26 | Data getCurrentkey.
| | 01:29 | Now you may have noticed that the
detail item was set in MasterViewController.m
| | 01:35 | before the Segue, and it actually has
a value the same as the current key.
| | 01:39 | So we could put detail item
right here in objectForKey.
| | 01:43 | But since we want to manage all of our data
in the data class I decided not to do that.
| | 01:47 | So now let's check if currentNote is
EqualToString:kDefaultText, remember that's
| | 01:58 | the default text for a new note as set
in Constants.h, and it just says new note.
| | 02:04 | So if it's the default text and we
haven't changed or saved the note, then we're
| | 02:09 | going to set self.TView.txt
equal to a blank NSString.
| | 02:14 | Because we don't want a user
clicking the button to create a new note and
| | 02:18 | having to delete the words new
note every single time that happens.
| | 02:22 | So to save us from that we are just
going to set the string to blank, and then we
| | 02:27 | will create an else statement below the
if statement, and here we will just set
| | 02:32 | the TView's text = currentNote.
| | 02:36 | So it's whatever note is saved.
| | 02:37 | Now let's look at saving the notes.
| | 02:40 | Scroll down right below viewDidLoad.
| | 02:43 | And we are going to implement
viewWilldisappear, so returns void,
| | 02:49 | viewWillDisappear, and here we want to
make sure that the note's not blank
| | 02:57 | before anything else happens.
| | 02:58 | So create an if statement and check to
see if not self.TView.txt is equal to
| | 03:06 | string and then just a blank NSString, so if
it's not blank we're going to set the
| | 03:14 | note for current key.
| | 03:15 | So Data, setNoteForCurrentKey, and the
note is going to be self.TView.txt.
| | 03:23 | But if the note is blank, in other
words, else, we are going to remove the object
| | 03:32 | for the current key.
| | 03:33 | So Data, removeObjectForKey,
and then Data, getCurrentKey.
| | 03:39 | Now after we take care of this we
want to make sure that we save the note.
| | 03:43 | So below the else statement, Data saveNotes.
| | 03:47 | Now what we want to do is when we
return back to the MainViewController, we want
| | 03:52 | to reset all of the data to make sure
that all of the table view items are
| | 03:58 | reloaded from our data model.
| | 04:00 | So save this file, and go to
MasterViewController.m. In here, scroll up right
| | 04:09 | below viewDidLoad, and then we're
going to implement viewWillAppear.
| | 04:14 | Then we are going to override
viewWillAppear. So just start typing it out, you
| | 04:22 | should see that code hinting, and
of course we're going to call super
| | 04:26 | viewWillAppear, animated.
| | 04:31 | And on the next line we are just going
to call makeObjects, so self makeObjects,
| | 04:35 | and the next line after that we are
going to reload the table view, so
| | 04:39 | self.tableView, reloadData.
| | 04:43 | So that will re-populate the data
in table view, making sure everything is
| | 04:48 | re-created based on the new data.
| | 04:51 | So we will save and test the app in the
simulator, and we can create a new note,
| | 04:59 | and just type Example note, click
Notes to return, I can click on that note
| | 05:06 | and I see Example note.
| | 05:07 | And now you'll notice that we still
have that timestamp displaying instead of
| | 05:12 | the name of the note.
| | 05:13 | So let's fix that by stopping the
simulator, and then finding the method called
| | 05:21 | cellForRowAtIndexPath.
| | 05:25 | In here change NSDate to NSString, and
then for cell.textLabel.text we are just
| | 05:34 | going to use that object as the key.
| | 05:36 | So Data getAllNotes, and then we are going
to type objectForKey, passing in the object.
| | 05:42 | So it's going to display the actual
text of the note instead of displaying the
| | 05:47 | key, which is the timestamp.
| | 05:50 | So save and test this in the
simulator, you create the new note, you can
| | 05:55 | type out example or anything you want
for the note, and then I see the name
| | 05:59 | of note here, I can create another
note in here, return back to the master
| | 06:06 | view, and I can view my notes.
| | 06:09 | You will notice also that I can edit
them, so I can click on example, change that
| | 06:14 | to examples, and it's updated
when I return to the master view.
| | 06:18 | So now we can create, save, and edit notes.
| | Collapse this transcript |
| Sorting and deleting notes| 00:01 | The last thing we'll do for our
application is sort all of the notes so that
| | 00:05 | the newest notes appear on the top.
| | 00:08 | In addition to that, we're going to make
it so you can delete notes if you want
| | 00:12 | to when you click the Edit button.
| | 00:14 | So, first we will sort the notes. Go to
makeObjects in MasterViewController, and
| | 00:19 | right under where we instantiate
objects, call _objects sortUsingComparator.
| | 00:24 | I am going to press return so
Xcode writes out the block of code.
| | 00:31 | And in here I'm going to return
in brackets [obj2 compare:obj1].
| | 00:42 | Now I want to typecast these two
NSDates so that they are treated in that way.
| | 00:47 | So typecast both of these values to NSDate.
| | 00:52 | And those will be sorted again with
the newest values first.
| | 00:56 | Now the reason why that typecast is
going to work is because all of the keys are
| | 01:03 | actually strings of dates.
| | 01:04 | So if you go to insertNewObject,
remember we declared that key, it's a string
| | 01:10 | representation of the date.
| | 01:12 | So this is actually going to work
when we typecast it back when we are
| | 01:16 | organizing them later.
| | 01:17 | So now scroll down and find a
TableViewCommitEditingStyle.
| | 01:26 | So right above _objects
removeObjectAtIndex, we are going to call Data
| | 01:32 | removeObjectForKey, and
the object is going to be
| | 01:38 | [_objects objectAtIndex:indexPath.row].
| | 01:44 | So its going to remove that key, and then what
we want to do is save the notes, so we'll
| | 01:50 | call Data saveNotes, and remember
that's going to actually save the value to
| | 01:56 | user defaults, so the value will be saved on
the device until the application is uninstalled.
| | 02:02 | So let's save and test this in the
simulator, and see we have the notes that are
| | 02:09 | still saved in the simulator from
earlier, and if you didn't happen to see those
| | 02:14 | notes saved now, don't worry about it,
sometimes changes are not saved until you
| | 02:21 | click the Home button of your application.
| | 02:23 | When you exit by clicking the stop
button in Xcode, sometimes your application is
| | 02:28 | killed before values are saved to user defaults.
| | 02:31 | So if you are not seeing values saved,
just click the Home button first before
| | 02:35 | clicking stop in Xcode, and
the value should be saved then.
| | 02:39 | So I will create a new note, and we
should see that it's at the top, and I will
| | 02:47 | click notes again, so I have a top
note at the top, and I will click the plus
| | 02:52 | button again, and I'll call this Real top
note, and now this one will be at the top.
| | 02:58 | Now I can edit, delete any notes I
want, click Done, they are deleted.
| | 03:04 | Now I have Real top note and another
note, lets see that they're displayin
| | 03:08 | the appropriate data.
| | 03:10 | And if I hit the Home button, stop
the app, remember I hit the Home button
| | 03:16 | first to make sure the values are saved to
user defaults, and then I run the app again,
| | 03:21 | you'll see that the same notes that I had
when I closed the app before are still there.
| | 03:29 | So that completes our app.
| | 03:31 | So now we've created an app where you
can make notes, edit them, and save them.
| | 03:37 | And with this all done, we're
ready to start integrating iCloud.
| | Collapse this transcript |
|
|
2. Preparing the App to Use iCloudUnderstanding what iCloud does| 00:01 | Before we add iCloud integration in our
app, let's take a minute to discuss what
| | 00:07 | iCloud actually does.
| | 00:08 | I am going to get my information from
Apple's iCloud Design Guide, so you can
| | 00:14 | either find it on Apple's website
or just follow along on my screen.
| | 00:19 | iCloud was made to sync data between
multiple devices with minimal user interaction.
| | 00:28 | So, as this picture shows, you have one
document in the Cloud, and you'd maybe
| | 00:33 | edit it on your iPhone, and the
data would be beamed up to the Cloud, and
| | 00:39 | the file would be updated, and
automatically your iPad and your Mac book would
| | 00:45 | be updated as well.
| | 00:47 | I'm going to click Next on this page to
go into Apple's Design Guide, and if you
| | 00:53 | scroll down we can see how
the data is actually stored.
| | 00:58 | Your Application has a Sandbox
Container. That's where you store the documents
| | 01:04 | that are used in your app.
| | 01:05 | There is also something called a
Ubiquity Container that's outside of your
| | 01:09 | application Sandbox, and that's
where you store the iCloud data.
| | 01:14 | Your Ubiquity Container is a local
representation of your Cloud documents.
| | 01:21 | I'll scroll down just a little bit more,
| | 01:24 | and we'll look at a Ubiquity Container.
| | 01:25 | There is a folder that has a
subdirectory called Documents, and you can create
| | 01:32 | your own subdirectories in the
Documents folder if you want, or you can create
| | 01:36 | them outside the Documents folder.
| | 01:38 | Files and folders created within the
Ubiquity Container's Documents folder are
| | 01:44 | publicly viewable by users.
| | 01:46 | Files outside the Documents folder are private.
| | 01:51 | The Design Guide explains more
important things if you'd like to look over it.
| | 01:56 | This section explains how a
user's iCloud storage is limited.
| | 02:02 | So you don't want to store massive
amounts of files in there that you can easily
| | 02:06 | replace within your app.
| | 02:08 | You'll also see that there are two
different types of iCloud storage.
| | 02:12 | There is key-value storage, for
preferences and small data values like you would
| | 02:20 | store in user defaults, and there's
document storage for iCloud for larger
| | 02:24 | document files that could be
publicly available and viewable by our user.
| | 02:30 | So, if you do have more questions
about how iCloud works, there is the Design
| | 02:34 | Guide available to you, so you
can feel free to read that over.
| | 02:37 | But for the purposes of this course,
just understand that iCloud is made to sync
| | 02:42 | files between devices in a way that
doesn't require a lot of user interaction,
| | 02:48 | that's kind of magically done behind the scenes.
| | 02:52 | So, throughout this chapter we'll
look at how to set up our application
| | 02:55 | to support iCloud.
| | Collapse this transcript |
| Making your app compatible with iCloud in the developer portal| 00:01 | To make your app compatible with
iCloud, you'll need to go to the Provisioning
| | 00:05 | Profile and click on App IDs, so scroll
down and I'm going to find my App, which
| | 00:11 | is called Notes, and if you don't have an
app that's here already, then you're going
| | 00:15 | to need to create the app and the
Provisioning Profile, and all of the standard
| | 00:20 | things you do when you create an app,
liked you learn in iOS Essential Training.
| | 00:24 | And here I'm going to click the
Configure button, and open up the Configure App
| | 00:29 | ID menu, and you can check Enable for
iCloud there, click Done, and when you
| | 00:36 | return back to App ID's page you
should see that your app has iCloud set to
| | 00:41 | be Enabled, and then make sure to re-download
your Provisioning Profile and install it.
| | 00:48 | Also note that iCloud is not
going to work on the simulator.
| | 00:53 | You are going to need to actually
use your device for testing iCloud.
| | 00:57 | So, once you've done that then you're
ready to go back into Xcode and start
| | 01:02 | enabling iCloud there.
| | Collapse this transcript |
| Modifying your project's properties for iCloud connectivity| 00:00 | Now let's set up or Xcode
project to support iCloud.
| | 00:04 | I am on the Summary tab with my target
selected, and I've changed my Bundle
| | 00:09 | Identifier to com.toddperkins.notes,
and that corresponds to my Development
| | 00:14 | Provisioning Profile that we
setup in the previous movie.
| | 00:18 | As I scroll down you'll also notice my
app icons are set, and these each come from
| | 00:23 | the assets folder so you can set
these if you'd like, by right-clicking the
| | 00:28 | images and choosing Select File.
| | 00:31 | And I am going to scroll down to the
Entitlement section and I am going to check
| | 00:35 | to use and entitlements file.
| | 00:36 | When I check that box, on the left side of the screen
you'll notice that Plain Ol' Notes.entitlements is created.
| | 00:42 | So this is going to hold our
entitlements, and then I checked to Enable iCloud,
| | 00:47 | and then I check the box use Key-Value Store
with identifier, and then I am
| | 00:52 | going click the plus button
to create a Ubiquity Container.
| | 00:55 | Now this Ubiquity Container is for
when we use the Document model for storing
| | 01:00 | iCloud data, and the Key-Value Store is of
course for Key-Value pairs when you're
| | 01:05 | storing iCloud data.
| | 01:06 | So, we're going to look at using
both in the rest of this course.
| | 01:10 | So once you have these boxes
checked, your app is then iCloud-enabled.
| | Collapse this transcript |
|
|
3. Working with iCloud Key-Value PairsUnderstanding key-value pairs in iCloud| 00:01 | Perhaps the easiest way to add iCloud
integration to your app is to store key value pairs.
| | 00:08 | Note that with key value pairs the max
amount of total data that you're allowed
| | 00:12 | to store in iCloud is 1 MB.
| | 00:16 | If you're making a more robust app than
this, then maybe you don't want to save all
| | 00:21 | of your data into the key value pairs.
| | 00:23 | We will look at ways in another
chapter for how to store more data to the cloud.
| | 00:29 | But for the purposes of teaching you how
to save key value pairs, we're going to
| | 00:33 | save all of our note data to iCloud.
| | 00:35 | In Data.m scroll down to where we
save the notes. So here we save to
| | 00:41 | NSUserDefaults. Saving to iCloud is
almost exactly the same. In fact I can
| | 00:47 | actually copy and paste this line
of code to the next line and simply
| | 00:51 | change NSUserDefault standardUserDefaults to
and NSUbiquitousKeyValueStore defaultStore.
| | 01:00 | And that value is then saved to iCloud.
| | 01:03 | The NSUbiquitousKeyValueStore works
almost exactly like NSUserDefaults, and it
| | 01:09 | has the same methods like set object
for keys, set dictionary for key, and in
| | 01:14 | almost every situation it
works just like NSUserDefault.
| | 01:18 | Apple, however, does not
recommend replacing NSUserDefaults with
| | 01:23 | NSUbiquitousKeyValueStore. It
recommends using them together so that you always
| | 01:28 | have a backup of your data locally
| | 01:31 | So now with our data saved to the
cloud every time it's saved normally,
| | 01:36 | let's create a new method that
returns void, and this is going to handle
| | 01:42 | change events in iCloud.
| | 01:44 | Whenever your app notices data from
the cloud that's been updated, this
| | 01:49 | notification will be sent to your app.
| | 01:51 | So we will call this dataUpdatedFromCloud,
and it's going to receive an
| | 01:58 | NSNotification, we'll call it notification.
| | 02:06 | For now we are just going to copy and
paste this into data.h and leave it blank,
| | 02:15 | and we'll go to AppDelegate.m,
ImportData.h, and then we are going to listen
| | 02:22 | for the event that occurs
when data comes from iCloud.
| | 02:26 | So type double brackets, NSNotificationCenter
defaultCenter, addobserver, selector,
| | 02:35 | name, object. So make sure you
grab the right method their.
| | 02:39 | The observer is going to be Data class,
because it's a class method, selector is
| | 02:45 | going to be dataUpdatedFromCloud.
| | 02:48 | The name is going to be
| | 02:49 |
NSUbiquitousKeyValueStoreDidChangeExternallyNotification.
| | 02:54 | Notice the other notifications here.
If you want to learn more about that I
| | 02:58 | recommend looking that up in the documentation.
| | 03:01 | But just note now that you can
listen for other events if you want to.
| | 03:07 | And the object is going to be
NSUbiquitousKeyValueStore defaultStore.
| | 03:14 | And then after that in application
didFinishLaunching, we're going to call
| | 03:20 | NSUbiquitousKeyValueStore
defaultStore synchronize.
| | 03:25 | Now what this is going to do it is its
going to search for data from the cloud.
| | 03:29 | So we will save the file, and now our
application is set up to save our notes to
| | 03:35 | iCloud using key value pairs, and to listen
for changes on iCloud, so we can update our
| | 03:42 | app when that happens.
| | 03:43 | We'll look at how to do that
throughout the rest of this chapter.
| | Collapse this transcript |
| Using iCloud as your main data source| 00:00 | So let's say you wanted iCloud to be
your main source of data for your app, so
| | 00:04 | you back everything up to iCloud and as
soon as you get an update from iCloud you
| | 00:08 | just overwrite all the local
of data with your cloud data.
| | 00:11 | Now you may be thinking off the bat
that their might be some problems with that,
| | 00:15 | but I want to show you how to do it
anyway, in case there are those of you out
| | 00:20 | there who would like to see how to do
it. And we are going to spend the rest of
| | 00:25 | this chapter after this
| | 00:26 | improving the app step by step to
give it better support with cloud data, and
| | 00:29 | better integrate that with the local data.
| | 00:31 | So we always have the most accurate, up-to-date
information in our app when we are running it.
| | 00:36 | So what we are going to do is scroll down to
data updated from cloud, and I'm in the data class.
| | 00:42 | And in here, I am going to create an
NSDictionary called dictionary called cloudData.
| | 00:49 | Now remember we can grab a dictionary
as if we're using NSUserDefaults, except
| | 00:53 | for use NSUbiquitousKeyValueStore
defaultStore, and then we just call
| | 00:59 | dictionaryForKey and we pass in kAllNotes.
| | 01:02 | So that gives us a
dictionary of all the cloud notes.
| | 01:07 | So right here what we could do is say
allNotes equals, then we just create a new
| | 01:13 | NSMutableDictionary.
| | 01:15 | So NSMutableDictionary alloc
initWithDictionary, and we pass in cloudData.
| | 01:21 | Once we've done that we are going to
want to update the table view on the screen.
| | 01:26 | Now to do that we will need to be able
to access the table view from here, and a
| | 01:31 | simple way to do that is to go to
MasterViewController.h and add a few methods.
| | 01:35 | First, a static method that returns a
MasterViewController that we will call a masterView.
| | 01:41 | And now we will define a method called
reload, and what reload is going to do is
| | 01:48 | it's going to reload our table view.
| | 01:50 | So we will go MasterViewController.m,
and the first thing we will do is define
| | 01:54 | the master view method.
| | 01:56 | So right under Implementation, I'm
going to declare a static property that is
| | 02:01 | of type MasterViewController, and we
will call this mView, and we'll just
| | 02:08 | define masterView to return mView,
and in viewDidLoad we are going to
| | 02:17 | instantiate mView as self. So mView = self.
| | 02:21 | So that way we have an anywhere
accessible through this class way to grab
| | 02:27 | the current table view.
| | 02:29 | And then I'm going to scroll down,
and I'm going to define reload right
| | 02:34 | below view will appear.
| | 02:35 | And all I am going to do is just
cut and paste self makeObject and
| | 02:41 | self.tableView reloadData.
| | 02:42 | And then I am going to call
reload and view will appear.
| | 02:50 | So whenever we want to reload the
table view we just call this reload, and we can
| | 02:55 | access the MasterViewController through
its static method. So lets save this and
| | 03:00 | head on back to Data.m, scroll
to the bottom, we want to call
| | 03:05 | MasterViewController to
reload the data to the table view.
| | 03:08 | Of course first, we are going to need
to import MasterViewController.h. So we'll
| | 03:14 | do that and scroll back down, and
then in double brackets we will call
| | 03:19 | MasterViewController masterView reload.
| | 03:23 | So you reload that table
view with the cloud data.
| | 03:26 | So I will save the file, and now what
I'm going to do is plug in my phone and
| | 03:31 | test this on my phone instead of in the
simulator, that's because these iCloud
| | 03:36 | features do not work in the simulator.
| | 03:39 | So with my phone plugged in,
I'm going to run the app.
| | 03:48 | So in the Notes app on my phone, I can tap
the plus button to create a new note. I am
| | 03:53 | going to going to call this Example note.
| | 03:57 | Remember that when we call saveNotes
after I go back to the Main view that the
| | 04:02 | data is then sent to
iCloud in the saveNotes method.
| | 04:07 | At this point I should be able to press the
Home button on my phone and exit out of the app.
| | 04:15 | Remember you don't want to hit stop,
we want to make sure this data saves.
| | 04:18 | So I am exiting out of the app by
pressing the Home button on my phone, and
| | 04:23 | then I'm going to press stop in
Xcode, so the app is now stopped.
| | 04:29 | And back on my phone I'm
going to delete the app.
| | 04:35 | Now you may see a message that says
this app stores data on the cloud, so
| | 04:41 | deleting it will not delete that cloud data.
| | 04:44 | Now what I am going to do is go
back and reinstall it. Remember, all of the
| | 04:49 | NSUserDefaults data is deleted off my
phone when I delete the app from my phone,
| | 04:56 | but the cloud data is saved.
| | 04:58 | So if this is working right I should be
able to run the app again. It reinstalled
| | 05:03 | the app onto my phone, and see that note
that was deleted from NSUserDefaults
| | 05:08 | when I deleted my app.
| | 05:12 | And there it is on my phone.
| | 05:14 | Now of course this will work with any
version of this application that's on
| | 05:18 | a different device.
| | 05:19 | So when I update my notes on here, if
I have installed the same app, those
| | 05:24 | updates will be sent to I cloud and
then downloaded to that other device.
| | 05:28 | So in this way we are overriding all
of the data that's in the app with the
| | 05:34 | iCloud data, which in theory should be
completely fresh, but in practice I've
| | 05:40 | noticed some glitches with this and
throughout the rest of this chapter, I said
| | 05:44 | earlier, we're going to look through
handling those problems and conflicts and
| | 05:49 | some ways that I found to resolve them.
| | Collapse this transcript |
| Adding notes when cloud data is found| 00:00 | Now we've already looked at
overwriting all of your local data with cloud data.
| | 00:05 | But let's say you don't want to do
that, you just want to detect whenever
| | 00:09 | new cloud data is added and then you want
to add new notes that weren't there before.
| | 00:15 | So let's look at how to do that.
| | 00:17 | In dataUpdatedFromCloud, in Data.m, I'm going
to delete the line of code that sets allNotes.
| | 00:23 | Here I'm going to create a loop
that's a for in loop, so I'm going to loop
| | 00:29 | through all of the keys, so those are
NSStrings we're calling key in cloudData.
| | 00:34 | And what I want to do is check to see
if the key in cloudData is not already
| | 00:41 | a locally saved note.
| | 00:43 | And if not I'll add it to the local notes.
| | 00:46 | So all I need to do is check to see if
the value for that key is nil in all notes.
| | 00:52 | So in allNotes objectForKey, key is
equal to nil, and we are going to add it.
| | 01:00 | So in brackets allNotes setObject,
and the object is going to be cloudData
| | 01:05 | objectForKey, key, and the key
is going to be key as well.
| | 01:12 | So now what should happen is that when
we actually update a note on another
| | 01:17 | device, that note should then come and
be added to our current notes and not
| | 01:23 | overwrite anything that's existing.
| | 01:26 | So let's save the file, and this time I am
actually going to test it on two devices.
| | 01:33 | So I am starting it on my iPhone 5 now, and
I'll start it on my other iPhone as well.
| | 01:42 | So now what I'm going to do is I'm
going to add another note from my other
| | 01:47 | iPhone, so not my iPhone 5, I
am adding another note here.
| | 01:51 | We will call this Remote note,
and we are going to go back.
| | 01:58 | And typically this process takes, from
my experience, about 10 to 15 seconds
| | 02:04 | for the data to pass, and this is of course
on the same Wi-Fi network for these devices.
| | 02:09 | So it might take longer or shorter for you.
| | 02:13 | But if you look at my iPhone 5 now, you'll
see that I have the Remote note showing up.
| | 02:19 | And so here, if I add another note, and
I called this Bonus note, and go back to
| | 02:28 | notes on my iPhone 5, I should then, 10
to 15 seconds later, see Bonus note appear
| | 02:34 | on my other iPhone as well.
| | 02:36 | So there it is, and you can see that it's
working whenever I add a note, but when
| | 02:41 | I say, delete a note, you'll see that it
does not get updated, so if I delete one
| | 02:47 | on my other iPhone, you'll see that if
I only have Remote note on the other iPhone
| | 02:52 | that Bonus note will
not be deleted all my iPhone 5.
| | 02:57 | So we will have to create away for
handling that, if that's what you want.
| | 03:01 | So you can add every
single new added note to iCloud.
| | 03:05 | But as we move on, we'll handle
deleting and editing notes in iCloud to make
| | 03:11 | sure that all of the data, both local
and remote, is as accurate as possible.
| | Collapse this transcript |
| Handling deleted notes| 00:00 | So we have already looked at adding
notes and having them transfer to other
| | 00:04 | devices through iCloud.
| | 00:06 | But what if you wanted to transfer your
deleted notes without having the clunky
| | 00:11 | system of just using all of iCloud's data
and dumping it on all of your notes?
| | 00:17 | So let's go to Constants and look at
how to do that by adding a new constant
| | 00:22 | value called kDeletedNotes, and the
value will be the NSString deletedNotes.
| | 00:28 | Save and head over to Data.m.
| | 00:32 | And now scroll up to the top
where allNotes is instantiated.
| | 00:36 | What we're going to do here is we are
going to create another static property,
| | 00:41 | and we will call it deletedNotes
and we are going to save this to
| | 00:45 | NSUserDefaults and the cloud, so that
way when a note is deleted we can check
| | 00:49 | against the cloud's deleted notes and see what
needs to be deleted from our local data source.
| | 00:57 | So where it says static NSMutableDictionary
allNotes, separate comma and add a new
| | 01:02 | one, and we'll call this deletedNotes,
and what we'll do is copy allNotes, and
| | 01:10 | if allNotes is equal to nil, and change
allNotes to deleted notes, and change the
| | 01:16 | key value to KDeletedNotes.
| | 01:19 | Remember, we already have a method for
handling deleted notes here in our data class.
| | 01:25 | So if we scroll down and find that
method, which is removeObjectForKey,
| | 01:30 | now here below where we removed that
object, what we want to do is set the
| | 01:35 | value in deletedNotes.
| | 01:38 | So type deletedNotes setObject, and the
object is going to be the string deleted.
| | 01:43 | Now this is just something that we have to
put in there, we can't put nil as the object.
| | 01:48 | So I'm putting an NSString.
| | 01:50 | And since we're never going to recall
this value ever again, I'm not creating a
| | 01:55 | constant for it. Otherwise I would, but
this is just a random junk value that we
| | 02:00 | are really just using the key for.
| | 02:03 | So we are setting the key as key, and
then we are going to save the notes to
| | 02:08 | NSUserDefaults, so NSUserDefaults
StandardUserDefaults, set object, the object is
| | 02:16 | going to deletedNotes for key, and
the key is going to kDeletedNotes.
| | 02:22 | And on the next line we are going to
do the same thing, so I am just going to
| | 02:27 | copy and paste that last line.
| | 02:29 | And we are just going to change
NSUserDefault, StandardUserDefault to
| | 02:32 | NSUbiquitousKeyValue, defaultStore.
| | 02:37 | And now we have successfully updated
iCloud when a note is deleted, but what we
| | 02:44 | want to do is make sure that when we
receive our data that's updated from iCloud
| | 02:49 | that we make sure to delete the right notes.
| | 02:52 | To do that scroll down to
data updated from cloud.
| | 02:54 | Here I am just going to copy and paste
the line of code that grabs cloud data.
| | 02:58 | I am going to change this to cloudDeletedNotes.
| | 03:04 | And of course the key is
going to be KDeletedNotes.
| | 03:08 | And I am going to make this easy by
copying and pasting this for in loop right
| | 03:13 | here, and we are going to loop
through the keys in cloudDeletedNotes.
| | 03:17 | And we want to see if the key
is not equal to nil in all notes.
| | 03:23 | So if it's not equal the nil, we
are going to remove the object.
| | 03:27 | So I am going to delete the code
after all notes in these brackets, and type
| | 03:31 | removeObjectForKey, and
the key is going to be key.
| | 03:36 | So if it's inside of all notes and it
needs to be deleted because the cloud
| | 03:40 | says so, we are going to remove that from all
notes, then we are going to reload our table view.
| | 03:45 | So let's save this file, and we are
going to test it, and again I am going to
| | 03:49 | test on two devices.
| | 04:00 | So now I have my notes loaded up
from the cloud on both devices.
| | 04:05 | Now let's say on my other iPhone that
I delete this Remote note that I saved.
| | 04:10 | So I am going to choose to
delete it, and I'm going to click done.
| | 04:14 | We should be able to wait 10 to 15
seconds and see that on the iPhone 5 the
| | 04:19 | Remote note is then deleted.
| | 04:22 | And I can confirm that now.
| | 04:24 | So by using this method we can
control both added notes and deleted notes
| | 04:29 | on multiple devices.
| | Collapse this transcript |
| Handling updated notes| 00:00 | Now that we've looked at adding
and deleting notes and syncing them
| | 00:04 | through iCloud, let's look at how to update
notes and make sure that that data is accurate.
| | 00:09 | Go to Constants.h and define one new
constant that we'll call kUpdatedNotes.
| | 00:17 | And we'll call this updatedNotes, save
the file, and head over to Data.m, and in
| | 00:25 | here scroll up to the top, and we're going to do
the same thing that we did for the deleted notes.
| | 00:30 | So we'll add a new static value called
updatedNotes, and then we are going to
| | 00:37 | copy or re-instantiate deletedNotes in
the getAllNotes method, change the name
| | 00:42 | to updatedNotes, and change
the key to kUpdatedNotes.
| | 00:46 | Now what updatedNotes is actually going
to do is it's going to take timestamps
| | 00:52 | and save them as the objects for
the keys that correspond to the notes.
| | 00:57 | So the keys for updatedNotes and
allNotes will be the same, but updatedNotes
| | 01:02 | will store, instead of the note value as
the value, it will store the timestamp of
| | 01:08 | when the note was last updated.
| | 01:10 | So we actually already have a method
that controls when notes were updated and
| | 01:14 | that's setNoteForKey.
| | 01:16 | So below the existing code in that
method, what I am going to do is call
| | 01:21 | updatedNotes, setObject, and the
object is going to be the current date.
| | 01:28 | Remember you can get that with NSDate date,
and the key is going to be the same
| | 01:35 | as the allNotes key.
| | 01:37 | So we're saving the note each time it's updated.
| | 01:40 | And then just like before with the
deleted notes, we're going to call
| | 01:45 | NSUserDefaults standardUserDefaults,
setObject, and the object is going to be
| | 01:51 | updatedNotes, forKey, kUpdatedNotes, and
then we'll just copy and paste this line
| | 01:57 | of code to the next line, and change
NSUserDefaults standardUserDefaults to
| | 02:03 | NSUbiquitousKeyValueStore
defaultStore, and that way we'll save it to iCloud.
| | 02:09 | Now we'll scroll down to
dataUpdatedFromCloud and copy that NSDictionary, and
| | 02:16 | paste it on the next line, and
we'll change cloudDeletedNotes to
| | 02:19 | cloudUpdatedNotes. Make sure
you change the key value as well.
| | 02:24 | So make the key kUpdatedNotes. And
right below where we do the for N loop
| | 02:30 | for cloudData, I am going to copy
and paste that, and we're going to loop
| | 02:35 | through cloudUpdatedNotes.
| | 02:37 | And what we are looking for this time
in the if statement is we're checking to
| | 02:42 | see if the notes are different.
| | 02:47 | So we have allNotes objectForKey, I am
going to delete is equal to nil, and I'm
| | 02:51 | going to write an extra close bracket
which will have Xcode automatically write
| | 02:56 | the opening bracket, and then
isEqualToString, and the string is going to be
| | 03:03 | cloudData objectForKey:key.
| | 03:08 | Now member we're not referencing
cloudUpdatedNotes, because that doesn't have
| | 03:12 | the notes, that just has the keys
and the date that they were updated.
| | 03:16 | And then, I want to put an exclamation point
| | 03:19 | right at the beginning of this if statement.
| | 03:20 | So what we want to do is check to
see if the note that's saved locally is
| | 03:25 | different from the note that's saved remotely.
| | 03:28 | If so, then we want to check
to see which note is newer.
| | 03:32 | Now to do that, we're going
to have to create two NSDates.
| | 03:36 | So one NSDate that will represent the
date stored in the Cloud, and that will be
| | 03:41 | dCloud, and that's going to be
cloudUpdatedNotes objectForKey:key.
| | 03:47 | Remember that's the NSDate that we
saved earlier when the note was updated.
| | 03:52 | So that keys are the timestamps, and the
object for the keys are the timestamps
| | 03:57 | of when that note was last updated.
| | 04:00 | So now we'll go to the next line, and
now we need to create another NSDate that
| | 04:04 | represents the date of the local note.
| | 04:08 | So dLocal is what we'll call it,
and we'll set it equal to
| | 04:11 | updatedNotes objectForKey:key.
| | 04:16 | So this is when the note was last
updated locally, and this is when note was
| | 04:21 | last updated in iCloud.
| | 04:23 | So when that happens we want to compare
the two dates and see which one is more
| | 04:27 | recent, and then update the note
based on which is more recent.
| | 04:32 | So go to the next line, and we want to
make sure that neither of these are nil.
| | 04:37 | So if dCloud == nil or dLocal == nil,
we're just going to call a continue and
| | 04:47 | go to the next key.
| | 04:48 | Now on the next line, we're going to do
an if statement that checks to see which
| | 04:55 | date is more recent.
| | 04:57 | So create an if statement, and
inside of the if statement we're going to
| | 05:01 | call dLocal compare:dCloud, and we
want check to see if that's equal to
| | 05:07 | NSOrderedAscending.
| | 05:10 | And then we want to do one more check,
we want to make sure that the cloudData's
| | 05:15 | note is not equal to nil.
| | 05:22 | So && cloudData,
objectForKey, key is not equal to nil.
| | 05:25 | So if that's the case and we have
more recent data in the cloud, then we're
| | 05:30 | going to overwrite the local
note with the cloud note's data.
| | 05:34 | So in here some brackets, allNotes
setObject, and the object is going to be
| | 05:40 | cloudData objectForKey:key,
and the key is going to be key.
| | 05:48 | So again, all this code does is check to
see if, first, the localNote is different
| | 05:54 | from the cloudNote, then we grab
the dates that they were last updated,
| | 05:59 | then we check to see which one is
newer; if the cloud one's newer,
| | 06:03 | then we update the local
notes based on the cloud notes.
| | 06:08 | So now I am going to save
and test this on my devices.
| | 06:12 | Now it's important to note that the
previously stored notes do not have this
| | 06:18 | update data associated with them.
| | 06:20 | So it's a good idea to delete
those notes before adding any new ones.
| | 06:25 | So I am going to run this on both of my
devices now, and I'm going to delete all
| | 06:35 | the notes on both devices even though
you should only have to delete them on one
| | 06:40 | and then wait a few seconds, but
it's faster if I delete them on both.
| | 06:44 | And now what I'm going to do is create
a new note on my iPhone 5. Call this New
| | 06:50 | note, and then when that syncs to my
other device, I am going to edit the note on
| | 06:58 | the other device and watch the
update appear on my iPhone 5.
| | 07:04 | Now again, that should take 10 to 15
seconds for the update to happen. Of course
| | 07:09 | it could take longer, depending on how
the iCloud server is running, and your
| | 07:13 | connection, and
everything else. And there it is.
| | 07:15 | So now we've successfully created iCloud
interactivity to sync notes using key value pairs.
| | 07:23 | We've looked at using all of iCloud's
data as the latest data and overwriting
| | 07:27 | all local data with that.
| | 07:29 | We've looked at how to add notes and
save them to iCloud and bring them down
| | 07:33 | from iCloud, how to
delete, and how to edit notes.
| | 07:37 | So using these techniques you can use
key value pairs to store data for your
| | 07:41 | apps and manipulate it in
whatever ways work best for your app.
| | Collapse this transcript |
|
|
4. Working with iCloud DocumentsUnderstanding the UIDocument class| 00:00 | We've already looked at how to
integrate with iCloud using key value pairs.
| | 00:05 | Another way to integrate with
iCloud is by using individual documents.
| | 00:09 | You can use those documents
through the UI document class.
| | 00:13 | The app I'm working in here is the same
as the basic note taking app, except for
| | 00:20 | I have the MasterView method and
MasterViewController that returns the instance.
| | 00:25 | And again, that instance is set in viewDidLoad.
| | 00:29 | We did write this code in previous a
chapter, so you may already have it there.
| | 00:33 | If you don't have access to the Exercise
Files, just back up what you did before
| | 00:37 | working with the key value pairs, and
just delete in your copy the key value pair
| | 00:44 | code from the data class and from
AppDelegate.m. In order to work with the
| | 00:49 | documents in the cloud, you're
going to have to create a subclass of UI
| | 00:54 | document, because the UI
document class is an abstract class.
| | 00:58 | So let's create that subclass now by
pressing Command+N on the keyboard.
| | 01:02 | I'll choose to create a new Objective-C
class that I'll call CloudDocument.
| | 01:06 | It's going to be a subclass of UI
Document so I click Next, and Create.
| | 01:11 | In CloudDocument we have to override
two methods, one for saving the file and
| | 01:19 | one for loading the file.
| | 01:22 | So first we'll override the one for
saving the file, it returns an ID and it's
| | 01:27 | called contentsForType.
| | 01:31 | The value returned is the
value that's saved to the file.
| | 01:35 | Each Cloud document is associated
with one file or file package, which is a
| | 01:42 | group of files similar
to an application on OS X.
| | 01:47 | Here we're going to return
NSKeyedArchiver archivedDataWithRootObject, and then
| | 01:53 | what we're going to do is get all of
the notes from the data class, so this is
| | 01:58 | going to convert our notes to an NSData.
| | 02:01 | So I'll import the data class and
then pass in here Data getAllNotes.
| | 02:08 | And again this is called when
the file is going to be saved.
| | 02:13 | So you can control which
data is saved right here.
| | 02:15 | We're going to use one Cloud document
and it's going to save all of our notes.
| | 02:21 | So that's why we're
returning all of the notes there.
| | 02:23 | The other method we're going to your
overwrite returns a Boolean value, and it's
| | 02:27 | called when a file is opened.
| | 02:29 | And this is going to be called
loadFromContents. Again, we're overriding
| | 02:35 | an existing method.
| | 02:37 | Here we'll have an NSDictionary, and
what we're going to do is unarchive that
| | 02:43 | dictionary that we
archived when we saved the file.
| | 02:46 | So to do that, we'll create that
NSDictionary, and we'll call it dict and
| | 02:52 | typecast it to an NSDictionary, and
we are going to call NSKeyedUnarchiver
| | 02:56 | unarchiveObjectWithData, and the
data is going to be received in this
| | 03:01 | contents value here.
| | 03:04 | So that's an ID and we'll typecast it to NSData.
| | 03:09 | So contents just like that,
and then we can return YES.
| | 03:13 | Now what we want to do is make sure
that when the data is loaded and the file is
| | 03:19 | open, which happens when it's received
from the Cloud as well as when we open it
| | 03:25 | locally, we want to make sure
that we update our data object.
| | 03:28 | So what we're going to do is create a
method in our data class that handles
| | 03:33 | the update to the dictionary, similar to what
we did for when we worked with key value pairs.
| | 03:40 | We'll call this didReceiveCloudData.
| | 03:40 | It will be an NSDictionary, and we'll
just call it d. I'll just create the
| | 03:52 | skeleton here at the bottom of data.m.
| | 03:54 | So now we'll call this method in
CloudDocument.m once we've loaded the data.
| | 04:00 | So we have that dictionary, then
we'll call Data didReceiveCloudData:dict.
| | 04:07 | So I'll pass that in there and save
CloudDocument.m, and actually we are done
| | 04:13 | working in CloudDocument.m. So if you
want to override UI document, you just
| | 04:17 | need to implement
contentsForType and loadFromContents.
| | 04:21 | There are of course other methods you
could override to handle other events that
| | 04:25 | happen when you're working with the document.
| | 04:27 | You can look those up in
the UI Document documentation.
| | 04:31 | So let's go to Data.h and save it, and
then go to Data.m and define what happens
| | 04:37 | when we receive the Cloud data.
| | 04:38 | Now what we're going to do is
just re-instantiate all notes.
| | 04:42 | So we're going to set the value to a
new NSMutableDictionary that is based on
| | 04:49 | the data that's passed in.
| | 04:50 | So we'll allocate it and
initWithDictionary passing in d.
| | 04:55 | Of course, you could handle this just
like we handled working with key the
| | 05:00 | value pairs, and you could take this
data as the cloud data, you could save your
| | 05:04 | updates, you could save your removed notes,
then you can organize them however you'd like.
| | 05:08 | Since we already covered that in depth
in a previous chapter, I am not going to
| | 05:12 | cover that again, we're just going
to set all of our notes as one file.
| | 05:17 | But just know that you have the same
option, should you choose to go that route.
| | 05:21 | You can always work with multiple files.
| | 05:24 | And later on we're going to look at
how to detect which files are in iCloud.
| | 05:29 | So let's go to the next line, and now
what we want to do is save the notes, so
| | 05:33 | we'll call self saveNotes, and then
we're going to reload the table view.
| | 05:37 | So MasterViewController, masterView reload.
| | 05:43 | I'll save the file. And then just to
cover what we did in this movie, so we'll
| | 05:47 | save data.m, and just remember, when
you're working with a UI document, subclass
| | 05:53 | it, override the methods for loading
and saving the files, then you can do
| | 05:57 | whatever you want with the appropriate data.
| | Collapse this transcript |
| Working with cloud document URLs| 00:00 | Before we can instantiate our Cloud
document, we're going to need to grab a URL
| | 00:06 | for the appropriate file.
| | 00:08 | Let's scroll down, and we're going to
create a method in the data class at the
| | 00:12 | bottom that is again going to be a
static method and it will return an NSURL, and
| | 00:17 | we'll call it notesURL.
| | 00:18 | The reason why we're creating a method
for this is because we're going to be
| | 00:23 | using this repeatedly throughout our code.
| | 00:26 | So create an NSURL inside the method called URL.
| | 00:30 | And then we're going to set it
equal to NSFileManager defaultManager
| | 00:37 | URLForUbiquityContainerIdentifier.
| | 00:41 | Now the string here that you would
pass in is your ID that you put inside of
| | 00:47 | your project when you connected it to iCloud.
| | 00:50 | This would enable you to have multiple
apps that can communicate with each other
| | 00:55 | through iCloud data.
| | 00:56 | So you could have shared
documents for different apps.
| | 01:00 | Since we're not doing that
here I'm going to type nil.
| | 01:04 | On the next line we're going to
return url URLByAppendingPathComponent.
| | 01:11 | The path component that you append is
going to be the name of the file or folder
| | 01:16 | that you want to save.
| | 01:18 | And in this case we're going to end up using a file.
| | 01:21 | But I want to show you how you can
control the visibility of these documents.
| | 01:26 | There is a folder called Documents, and
this folder in the cloud contains all
| | 01:33 | the files that are publicly accessible and
editable by the user with that iCloud account.
| | 01:40 | So if you want it to be a public file,
public meaning visibly editable by the
| | 01:45 | user, then you can put it in the
Documents folder, or a subfolder of that folder.
| | 01:50 | But if you want it to be a private
document, then you can put it outside of
| | 01:54 | the Documents folder.
| | 01:55 | I am going to replace
that NSString with kAllNotes.
| | 01:59 | So it's actually just going to be the
URL to a file. So the UbiquityContainer
| | 02:04 | gets us our iCloud container, the
Document subfolder is going to contain
| | 02:09 | publicly visible documents.
| | 02:11 | The Document subfolder contains documents
visible to the user, and files outside of
| | 02:16 | that folder are not visible to the user,
and that's what we want to do here, so
| | 02:20 | that's why I have kAllNotes.
| | 02:23 | So just remember that when you need to
get the URL for your cloud data, you call
| | 02:28 | NSFileManager defaultManager, and its
method URLForUbiquityContainerIdentifier.
| | Collapse this transcript |
| Opening, closing, and saving documents| 00:00 | Now that we have everything set up we
are going to look at opening and saving
| | 00:05 | and closing a UI document.
| | 00:08 | So we go to Data.m and we are
going to import CloudDocument, and then
| | 00:14 | we'll create a static property that
will be of type CloudDocument, and we'll
| | 00:20 | call this cloudDoc.
| | 00:23 | Scroll down into getAllNotes, and right
below where we instantiate all notes,
| | 00:27 | we'll instantiate cloudDoc, and that's
going to be CloudDocument, we are going to
| | 00:34 | allocate it, and then we are going to
call initWithFileURL, and the URL is going
| | 00:39 | to come from self notesURL.
| | 00:40 | So remember that's the URL to
the file that we want to save.
| | 00:45 | Then, what we are going to do is try to
open that file, and if we can't open the
| | 00:50 | file because it doesn't exist,
then we are going to create the file.
| | 00:55 | So call cloudDoc openWithCompletionHandler,
press return to have Xcode write out
| | 01:01 | the block for you and then, and then in here
we want to see if success is false.
| | 01:06 | Remember success is going to return
true if the file was loaded successfully.
| | 01:10 | So if it wasn't loaded, and this is
probably going to be because the file
| | 01:14 | doesn't exist, we're going to create the file.
| | 01:18 | So in brackets, cloudDoc, saveToURL, and
we're going to use the same URL, self notesURL.
| | 01:27 | For save operation, and that's going
to be UIDocumentSaveForCreating, and the
| | 01:34 | completionHandler is going to be nil.
| | 01:38 | Of course, you can select it and press
return if you want Xcode to write the
| | 01:41 | block and you can handle the successful
saving of the file, if you'd like, but we're
| | 01:47 | not going to do anything
when the file is done saving.
| | 01:50 | So I'm just going to pass in nil here.
| | 01:53 | So that's how we open a file by
calling openWithCompletionHandler, and we save
| | 01:58 | the file by calling saveToURL.
| | 02:01 | If you want to close the
file, all you do is call
| | 02:06 | closeWithCompletionHandler. Then of course
| | 02:10 | you can write whatever you want
to do when the file is closed.
| | 02:13 | And when we look at later deleting a
Cloud document, you'll need to make sure
| | 02:17 | that you close that file before deleting it.
| | 02:20 | So I'll delete those lines of code.
| | 02:22 | So we could save the file now, but we're
actually not going to see anything
| | 02:26 | interesting until we start modifying the Cloud
document, which we're going to look at later.
| | 02:30 | But for now, I just want you to
remember that when you initialize a UI document
| | 02:35 | subclass object, you're going to
call initWithFileURL, you can open the
| | 02:40 | file using openWithCompletionHandler,
and you can save the file using saveToURL.
| | 02:46 | And finally, you can close the
file using closeWithCompletionHandler.
| | Collapse this transcript |
| Tracking changes and autosaving documents| 00:00 | UI document has autosave functionality.
All you have to do is tell the document that
| | 00:06 | a change was made, and UI document will
automatically determine a good time to
| | 00:12 | send the data to iCloud. Then other
devices are notified of the updates and they can
| | 00:17 | display the updated information.
| | 00:20 | So let's scroll down, and all we have to do is
write a few lines of code to check this update.
| | 00:25 | So find setNote for key, and under the
existing code what we want to do is check
| | 00:32 | to see if the note is not a default note.
| | 00:35 | So use the not operator, and check
if note isEqualToString kDefaultText.
| | 00:43 | So if it's not a default note, it's
either a new note or a changed note, then what
| | 00:47 | we're going to do is tell our
document that a change has occurred.
| | 00:54 | So cloudDoc updateChangeCount, and
then we pass in the ChangeKind.
| | 00:59 | As you start to type UI document
you will see the different ChangeKinds.
| | 01:03 | The ChangeKind we're
putting in here is ChangeDone.
| | 01:06 | So a change has been made and completed.
| | 01:09 | So we're letting the document know
that, and when UI document determines that
| | 01:14 | it's a good time to upload the data
to iCloud, which typically happens a few
| | 01:18 | seconds after you call this method,
| | 01:20 | the data is then uploaded
and sent to the other devices.
| | 01:25 | Copy this block of code we just
wrote, and go to removeObjectsForKey, and
| | 01:30 | make sure to paste it where we
remove the object. So above allNotes
| | 01:34 | removeObjectForKey.
| | 01:36 | Now of course, the note is not passed in
just a key, so we need to grab that key
| | 01:41 | value from all notes.
| | 01:43 | So allNotes objectForKey:key.
| | 01:46 | So we're checking to see if
the actual note is default text.
| | 01:51 | If so, then we're updating the change
kind again. And if we wanted to we could
| | 01:55 | save the file and save notes, but
you actually don't have to and in my
| | 02:00 | experience of doing this,
| | 02:02 | I've not noticed any kind of performance
increase either way with that code in there.
| | 02:09 | So of course, if you want to, you can
choose to save the file there explicitly,
| | 02:13 | but if not, it will end up
saving within a few seconds anyway.
| | 02:17 | So let's save the file, and now we should
be able to test this on both of our devices.
| | 02:27 | Now when the apps launch, create a new note
on one device, and then go back to the
| | 02:34 | master view controller, and wait for 15
to 30 seconds and see if that change ends
| | 02:41 | up going to the iCloud server and onto
the other device. And there is the example
| | 02:48 | note that I created on the other device.
| | 02:50 | Now let's delete that note on the
second device, and wait another 15 to 30
| | 02:56 | seconds and see if that note
gets deleted on the original device.
| | 03:02 | And sure enough, after a few seconds, the
note is deleted. And let's do one more thing.
| | 03:07 | We'll create another note, I'll do
that on my iPhone 5, and when the note is
| | 03:14 | added to the other device, I'm going to
make a change to it, and when the change
| | 03:19 | is made I want to confirm that the
change is updated on the original device as
| | 03:23 | well. And there's the note on the
second device, I'm going to add some text to
| | 03:28 | it, go back to the master table view to
where the change is saved, and then I
| | 03:34 | should see the updated
note on the original device.
| | 03:37 | So now we've successfully seen that we
can create, update, and delete notes and save
| | 03:44 | them through iCloud using the UI document class.
| | 03:48 | Just make sure that when you make a
change to your data that you call the UI
| | 03:52 | documents updateChangeCount method,
and that change will be sent to all the
| | 03:57 | devices with the same iCloud account.
| | Collapse this transcript |
| Handling document metadata events| 00:01 | Our app only has one cloud document.
But let's say you wanted to create an
| | 00:06 | app that had multiple documents,
and you wanted to track all of those
| | 00:11 | documents saved in iCloud and open the
ones that you want. To do that, you
| | 00:17 | can query the metadata.
| | 00:19 | Now the metadata is something that gets
updated frequently, so your app can check
| | 00:27 | the iCloud metadata that's constantly
being updated from iCloud to see what
| | 00:31 | files exist on a server
without downloading them.
| | 00:35 | Then you can download
whichever files you want as necessary.
| | 00:39 | So let's look at how to
read the metadata from iCloud.
| | 00:42 | Create a static property in the
data class, and it's going to be of type
| | 00:48 | NSMetadataQuery. We'll call this query.
| | 00:58 | Scroll down to below notesURL, and we
are going to define a method that is again
| | 01:06 | a static method that will return
void that's called loadCloudMetadata.
| | 01:14 | Now what we'll do in here is we're
going to instantiate the queries.
| | 01:17 | So query, then we'll allocate an NSMetadataQuery.
Then we'll allocate and initialize it,
| | 01:30 | standard initializer here.
| | 01:32 | And then, what we're going to
do is set the search scopes.
| | 01:35 | Now this line of code is not
necessary if you don't care where the file is.
| | 01:42 | So then I'm going to call query
setSearchScopes. What is asked for is an NSArray.
| | 01:47 | You actually don't even have to write
this line of code if you want to search all
| | 01:52 | of the areas in iCloud. That is, all
the areas that are available to this
| | 01:57 | application in iCloud.
| | 02:00 | Or you can specify that you want to
search only in the viewable documents by the
| | 02:04 | user, which is files in the documents
folder, or only files that are not viewable
| | 02:09 | by the user, which is
outside of the documents folder.
| | 02:11 | So let's create an NSArray in here, and
we'll type arrayWithObjects, and if you
| | 02:19 | want to include the documents folder
you can type NSMetadataQuery DocumentsScope or
| | 02:29 | DataScope. DocumentsScope is again
in that documents folder and in all
| | 02:34 | subfolders, and then the other one is
NSMetadataQueryUbiquitousDataScope, and
| | 02:40 | that's outside of the documents folder.
| | 02:42 | So I'm going to put both of those in there.
And again, if you don't put this line of
| | 02:47 | code, then it will automatically
search in both of those areas.
| | 02:49 | So this just allows you to
control what is searched for metadata.
| | 02:52 | Next we'll create a value of type NSPredicate.
| | 02:55 | This is going to specify our search
criteria for files that we're looking for.
| | 03:01 | So we'll call this predicate, and
we're going to set it equal to NSPredicate
| | 03:07 | predicateWithFormat. And now here, we'll
create an NSString, and what we can do is
| | 03:15 | pass in some values in search
parameters, and I'm not going to go into what all
| | 03:21 | of them are here, but you can look up
NSPredicate and find out all of your
| | 03:25 | options for specifying which
documents you want to search for.
| | 03:28 | I'm going to type a percent sign and
then a capital k, and then LIKE and '*'.
| | 03:35 | And what this is going to do is find
all files that are LIKE '*', which is a
| | 03:41 | wildcard character, so all files.
| | 03:44 | And after that closing NSString, I'm
going to type a comma, and then
| | 03:47 | NSMetadataItemFSNameKey.
| | 03:53 | So I'm going to go up. So what it's
doing is, this is a key that represents the
| | 03:59 | name in the file system, so it's
going to grab any file that's saved in the
| | 04:04 | metadata. And that %K just refers to a
string, but this is going to be a compatible
| | 04:10 | string with the NSPredicate protocol.
| | 04:13 | So again, you can look up
NSPredicate if you want to find other ways to
| | 04:17 | search for items in there.
| | 04:20 | So I'll go to the next line and we're
going to set the predicate, so query
| | 04:24 | setPredicate, we'll pass in the
predicate, and then we are going to register for
| | 04:30 | the notification when the metadata
query finishes gathering information.
| | 04:37 | So [[NSNotificationCenter
defaultCenter]] and then I'm going to call
| | 04:46 | addObserver, selector, name, object.
The observer is going to be self, the
| | 04:54 | selector I have not defined
yet so let's define it right now.
| | 04:58 | Go down below this method and define
a method called fileListReceived that
| | 05:05 | receives an NSNotification that
we'll refer to as notification.
| | 05:10 | So we're going to call that
selector when the notification is posted.
| | 05:18 | So the selector is going to be
fileListReceived, and the name is the event that
| | 05:26 | we're listening for and that is
NSMetadata QueryDidFinishGatheringNotification.
| | 05:28 | Notice that there is also
DidStartGatherNotification, DidUpdateNotification, and
| | 05:37 | GatheringProgressNotification.
| | 05:40 | So if you want to listen for when
metadata is updated you can use the
| | 05:43 | UpdateNotification and
respond accordingly there.
| | 05:47 | So now the object is going to be the
query, the last thing we are going to do in
| | 05:51 | this method is called query startQuery.
| | 05:55 | So this is going to search for the
metadata. And when the file list is received
| | 06:01 | we can respond in here. So NSArray
queryResults, and we're going to set it
| | 06:11 | equal to query, which saves all the
metadata information after we call query
| | 06:16 | startQuery, results.
| | 06:19 | So that gives us our array, and then we can
loop through it to check the value.
| | 06:26 | So I'll use a for N loop, and what
I'm looking for is an NSMetadataItem, which
| | 06:36 | we'll call result in queryResults.
| | 06:44 | You can access the information inside of
the metadata item through a list of keys.
| | 06:50 | Again, you can always look in the
NSMetadataItem reference to find out all of
| | 06:55 | those keys, but for now we're just
going to look at one. Create an NSString.
| | 07:00 | And we're going to call this fileName, and
we're going to set it equal to result
| | 07:08 | valueForAttribute and NSMetadata FSNameKey.
| | 07:11 | So that's going to be the name of the
file, and now we're just going to log the
| | 07:20 | file, so we can see that we can access the
different files within iCloud. So type filename:
| | 07:27 | and %@ symbol for an NSString, and then filename.
| | 07:31 | So this is going to display the
filename that's inside of our metadata, which
| | 07:38 | represents all the files
that are saved in iCloud.
| | 07:41 | So when we instantiate our Cloud
Document, I'm going to call self
| | 07:48 | loadCloudMetadata. And we should see,
when I test this, and I'm not going to
| | 07:54 | show you my phone right now, I'm
going to test this on my phone though, and
| | 07:58 | what I want you to look at is inside
of the bottom view in the output window,
| | 08:03 | I want you to look for the name of
the file, and that should show and it should
| | 08:07 | match the key for all notes.
| | 08:10 | So once the file is loaded, the output
window will then update with the loaded
| | 08:14 | metadata showing the filename of the
file of that we're working with in iCloud.
| | 08:20 | Of course, if you wanted to, cut and
paste this Cloud loading file inside after
| | 08:28 | your metadata is loaded.
| | 08:29 | But since we only have one file we're
working with, its not necessary to do at this time.
| | 08:35 | So what I'm going to do is comment
out the log statement and the NSString
| | 08:39 | statement, and if you choose to do
anything with a metadata with multiple files
| | 08:43 | you can get all the information about
all the file stored in iCloud here. So just
| | 08:48 | remember to use metadata queries,
instantiate the query, set the searchScopes to
| | 08:54 | where you want to search, set the
predicate, which is the search conditions,
| | 08:59 | listen for the notification,
and call query startQuery.
| | 09:04 | Once you're done querying the data, and
you're positive you're not going to be
| | 09:07 | using that query anymore, then
you can call query startQuery.
| | 09:13 | I'll put that at the end of
fileListReceived and save the file.
| | 09:17 | So by working with our metadata, we
can find out when our metadata is updated,
| | 09:22 | check against all the files that exist
in iCloud, load the appropriate ones that
| | 09:27 | we want through this for loop, using
the pre-existing key result values.
| | 09:34 | That way you can keep your app updated
using the document information that's in
| | 09:38 | iCloud and load information as its necessary.
| | Collapse this transcript |
| Deleting a document from iCloud| 00:00 | Let's say at some point you're managing
an app that has multiple cloud documents
| | 00:06 | and you want to delete one of them.
| | 00:08 | It's actually a fairly simple process.
Above where we instantiate cloudDoc in
| | 00:13 | the getAllNotes method of the data class,
I am going to type double brackets and
| | 00:18 | then NSFileManager defaultManager
removeItemAtURL, and the URL for our notes is
| | 00:25 | self notesURL, and for error I'll just put nil.
| | 00:31 | Now to check to see that the file was
actually deleted, inside of the completion
| | 00:35 | handler for cloudDoc's open method, I
am going to go inside of the if statement
| | 00:41 | that checks to see if the file did
not exist, and we create the new one.
| | 00:46 | I'll put an NSLog in there,
and I'll type new file created.
| | 00:53 | This is only going to log to the
console if a new file was created.
| | 00:57 | Let's save this, and what I am going to
do is comment out the line of code that
| | 01:03 | removes the item, and I'll run the app on
my phone and make sure to confirm that
| | 01:08 | it does not say new file
created once the app is loaded.
| | 01:12 | If it doesn't say new file is created in
the log window, then that means that the
| | 01:16 | file exists on the server.
| | 01:18 | So now I'll delete that comment and test it
again, and now I should see new file created.
| | 01:28 | And in the log window, sure
enough, I see new file created.
| | 01:32 | So, if you want to remove a
cloud file, all you do is call the
| | 01:37 | NSFileManager's removeItemAtURL,
passing in the appropriate URL for the item
| | 01:43 | that you want to delete.
| | 01:45 | Also remember, before you delete a file,
make sure it's closed, which you can do
| | 01:50 | by calling the UI Document's
closeWithCompletionHandler method.
| | Collapse this transcript |
|
|
ConclusionNext steps| 00:00 | I'd like to show you where you can find more
information about developing apps for iCloud.
| | 00:06 | The first and most obvious one is the
iCloud design guide, which you can find in
| | 00:11 | the iOS Developer Library.
| | 00:13 | Here on the Table of Contents you can
see all of the topics that are covered, and
| | 00:19 | some tips that Apple gives you for
developing apps that utilize iCloud.
| | 00:22 | If you're curious about more detail,
about anything that we've talked about
| | 00:28 | throughout this course, you can find that here.
| | 00:31 | Another way that you can develop
iCloud applications is by using Core Data.
| | 00:36 | Now unfortunately at this time, the
sample code that I've looked up that's
| | 00:40 | provided by Apple has been known to
produce bugs. And I've seen this on Apple's
| | 00:46 | developer forums that iCloud with Core
Data is not quite ready for prime time,
| | 00:53 | because it doesn't work exactly as expected.
| | 00:56 | If you'd still like to work with Core
Data in iCloud, you can use the Using Core
| | 01:00 | Data With iCloud Programming
Notes in the iOS Developer Library.
| | 01:05 | Here you can get some tips to get
started working with iCloud and Core Data.
| | 01:11 | This link here is a developer forum
that has the Apple sample code for using
| | 01:16 | Core Data with iCloud.
| | 01:18 | So here you can download
iPhoneCoreDataRecipes and make the appropriate
| | 01:23 | adjustments, and see if this is
sufficient for your apps in working with
| | 01:27 | Core Data in iCloud.
| | 01:29 | Again in this forum post you're going
to find page after page of developers
| | 01:34 | discussing bugs with
working with Core Data and iCloud.
| | 01:38 | That's why I chose not to teach how to do it.
| | 01:42 | Finally, this last link with working
with Core Data in iCloud is one developer,
| | 01:47 | dlpasco, who posted an adjustment to the
Apple recipes for Core Data, which is
| | 01:55 | supposed to address many of the
bugs mentioned by the developers.
| | 01:59 | So if Apple's provided developer
recipes aren't enough to get you working your
| | 02:04 | app in iCloud Core Data,
you can try this code as well.
| | 02:08 | Again, there are many in the iCloud
developer community who feel that Core
| | 02:12 | Data with iCloud is not quite ready to
integrate and publish applications at this point.
| | 02:19 | But should you decide to give it a try,
these are some resources that you can use.
| | 02:24 | If you have any questions for me
personally about this course, you can contact me
| | 02:28 | on Twitter at asktodd.
| | 02:30 | Also, I would like to see any apps you
develop using what you learned in this course.
| | 02:35 | So feel free to hit me up there and
I'll make sure to get back to you.
| | 02:39 | Thanks, and I'll see you next time.
| | Collapse this transcript |
|
|