In App Design and Development for iOS, the third course of the iOS App Development with Swift specialization, you will be developing foundational programming skills to support graphical element presentation and data manipulation from basic functions through to advanced processing. You will continue to build your skill set to use and apply core graphics, touch handling and gestures, animations and transitions, alerts and actions as well as advanced algorithms, threading and more. By the end of this course you will be able to develop a more advanced, fully functioning app.
Currently this course is taught using Swift 2. The team is aware of the release of Swift 3 and will be making edits to the course in time. Please be aware that at this time the instruction is entirely with Swift 2.

教學方

Parham Aarabi

腳本

[MUSIC] Our final section is a basic introduction to core data. In the previous video on NS key to archiver, we learned how to create an archive of a complete object graph consisting of our feed and its feed items. But we had to write everything at once and read everything back into memory in one go. There was no way to load just the data that we needed from a file. Core data manages collections of entities that can be loaded incrementally from persistent storage just when you need them. And it keeps track of changes to those entities so that your app can change them and rewrite them to the data base without having to manage the entire data set in memory at once. You might think of Core Data as something like having a local SQL database for your app. And indeed, core data can use a SQL database to persist the entities that it manages, but core data is so much more. And thinking of it as a relational database is kind of a trap. Try not to think about tables rows in foreign keys. Think of a core data context as a space where you create new instances of mutable objects and relations that represent your data. Core data will keep track of change to those objects and relations and store them when necessary. And it helps you search for just the objects that you want when you need them. Okay, before we get started with core data, I'm going to show you a little bit about how I've changed the project since the last video. So you can actually download this starter project from the module so you don't have to kind of copy of all this stuff, but I'll just quickly go over it. First we have the same image feed table view controller, but I've actually wrapped it in a navigation controller now so that we have a navigation bar and we also have a tags button here. So hitting that tags button in the navigation bar will take us to the tags table view controller, and that will show us a list of tags. And then hitting a particular tag in that will take us to another copy of that image feed table view controller. But this time, it will only show the images for that particular tab. So we're actually just recycling that class again. And using that to show the list of images for that tag. And I've also added or tweaked the app delegate a little bit. This time instead of getting the root controller, getting the view controller from the root controller. We're going to get a navigation controller instead, and then we're going to look for that image feed table view controller under that. So that's just a small tweak there in the code. Let's just run that to quickly see what we have going on here in action. So I'm going to click run, and open up the thing here. And we'll see it's loading up. Okay, so we have our list of images here, and then if I hit tags, it's going to just show us a blank table. There's nothing in here so far. So we'll go back. And we'll go back here. So our goal is to create a data model that will allow us to show some tags and store some images under those tags so I'll just quit out of that. Okay next thing I'm going to do is I've created a core data group here and under that we're going to store a whole bunch of files. The first file we're going to create is a model. So the way core data works is that you give it a model that you've designed and it will manage the data under that model. But it needs to know how you want to store your data. So, we're going to create a new file called a model file, so I'll go to new file here and then that one is going to be under the core data tab and its called data model. So, we'll click next and just create that. So, in this model we can define several different things. So the most important thing is entities and then each of those entities can have attributes and relationships. So it's kind of similar to when you're creating other types of relational databases, but the important thing to remember is that these entities actually mapped to objects when you access the data in them. So I'm going to create two entities here. One is going to be called image and that's going to store the url and the title of the image that we're tagging. And one is going to be tags. And so each tag is going to have a tag entity associated with it. And this tag is going to have a title and then a list of images that are stored under that tag. So I'll add first the Attributes. So for the tag, we're going to have just a simple title Attribute. And each Attribute has a Name and a Type. So I go down here and select the String type for this title, since it's a String. And then under image, we're going to add two attributes. So one is going to also be called title, this is the title of the image. We go down to string. And then the next is going to be for the image URL, so imageURL. There we go. And also that's going to be a String. Okay, so we have an imageURL and a title. Now, we want to actually store a relationship between these entities as well. So each Tag is going to have a bunch of images, right? So, when we're running, when we're looking at say this story-board here we have our tags view controller. And that's going to have a list of all the tags and then if you hit a particular tag it's going to have a list of all the images for that tag. So our tags need to know what images are stored under themselves. So, we're going to add a relationship that points to all the images that are stored with that particular tag. So we'll just call that Images. And under destination, we select the entity that it's going to actually point to, so we'll point to image. Now if I look at the editor style here, you can actually see now it's going to show me kind of a graphical layout and you can see that now images is pointing at Image. So it has this property called images and it's going to point to Image entity. Well you'll see that this arrow, what actually is happening if I click on it and I go to my Core Data Inspector here. Let's see if we can actually click on that. Actually may have to go here. So we'll go to the editor style again. Click on that relationship. And yea, okay. So here we go as you can see that this is a to one relationship. We actually have to change that to be a to many relationship now. So it knows that it can store multiple images under that tag. And if I go back to the editor, you'll see now it's double arrows showing that it's to many relationship. Now we want each image to also kind of be aware of what tag it has. So because each image here is only going to have one tag at a time, we're going to go back, and add a relationship, and this time we'll just call it Tag. And we'll call it Tag here. And that can stay as a To One relationship. So let's see here. Now we have kind of two relationships here, one pointing at Image, and one pointing at Tag. But we can see there's two arrows here. We want to tell Core Data that actually these are inter related relationships. So if we add an image to a Tag, we want the image to have a relationship to that tag. They're kind of connected, right? So we want to actually tell that we have an inverse relationship between these two. So whenever an image is related to a tag, a tag is also related to an image. So we can go back and see now its just one double arrow here. Now each of these entities is also going to be accessed in our code. To make that easier, we're going to create classes that encapsulate these entities and one easy way to do that is actually to use the automatic generation command. So I'm going to go down and select create [INAUDIBLE] subclass. And what that is going to do is. Let me chose the model. Then I make sure I have both entities checked here. And go Next. Make sure that the language is Swift and click Create. So that's going to generate some classes that map to those entities in our model. So if I look up here I have a tag and an image class and they are both subclasses of ns managed object. And if I look at these other files, they're also related to these classes. They're extensions of the classes. And they actually have the properties that we've defined on those objects. So you can see that the tag class has its title as a string, here. And then it also shows that relationship images. And each image, it can have multiple images, so it actually has a set of images that are related to that tag. And then, under image, we also have the title imageURL, and also the tag maps back to the tag class here. Okay, so we've done that. We've got our model here. The next thing to do is take, to actually start generating all the instances of core data classes that we need to set up this core data stack. Okay, next, before we start creating some code, I just want to share with you a little slide here that kind of details all the objects that are going to be in play here. The object that you're going to mainly talk to in your app is going to be the NSManagedObjectContext, and all your tag entities and all your image entities are going to live under this, inside of this object context. Anything that's really an NSManagedObject or a subclass of that is going to be taken care of by the ManagedObjectContext. And it manages the entire life cycle of those objects, so you can, you to create a tag or to fetch one, you use the NSManagedObjectContext to access and create those instances. The managed object context also talks to a persistent store coordinator which takes care of storing the data in some kind of persistent store. Which could be like a local SQL database or an XML file or some kind of other class. The format of the actual file is really an implementation detail that's handled internally by the system. You don't have to worry about it. You don't access the SQL database directly. You access everything through this managed object context instead. But, you still have to create instances of the persistent store coordinator and the persistent store just to set everything up. And that's what we're going to do next. So, to do that, we're going to create a class just to keep everything in one place and keep it out of the app delegate. So I'm just going to go here. We're just going to move these under our core data folder here and I'm going to create a new swift class. So let's go up here under chord, sorry under Source, and just create a standard swift file. Let's call it Data. DataController and we're going to have to import the core data API for this. And we're going to call it DataController. Okay, so let's set up the initializer here and the property this DataController is going to store an instance, keep track of an instance of NSManagedObjectContext. So let's create the initializer. Okay, so it has one property that stores that NSManagedObjectContext and we've created a designated initializer that just supplies that context here. Okay so, let's to create a convenience initializer for this that's actually going to do the work of setting up all of those objects in the stacks. So we'll just do that. Here we go. So, this is a little bit longer. This is the convenience initializer. Okay, so let's see what's going on here. First, we're going to get a reference to that model that we created. So here's the model file. So we're going to store that model and it's going to be stored inside the main bundle of the app. We're getting a URL for that, but notice that the extension is MOND, or M-O-N-D. It's not XC Data Model D. That's because when your app is compiled it turns those XC model D files into MON D files. Which are sort of complied versions of those data models. Okay so we've got a url for that. Then we're going to create a class called the NS Managed Object Model. That will encapsulate that model file. Then we're going to create that persistent store coordinator using that object model file that we just created. Then finally we get to creating the actual object managed context. And we're going to assign the persistentStore that we just created to that context. Now the last step is to set up the persistentStore itself. That's where the file where everything is stored. All right we're going to store that in the DocumentDirectory just like the other, instead of the cache's directory since this is a user file, we want it to be backed up. This is data that the user is creating. We can't really recreate this in the future if we lose it so we'll put it in the document directory. And then we're going to add a file named Ford. I've just put sqLite the file name as the extension. The SQLite extension doesn't really matter, it's arbitrary. But if you happen to want to take a look at that, then you can more easily do that if you have that SQLite as the file name extension. While typically the user doesn't actually even see that file name really. Okay, and then we're going to add this persistent store to the persistent store coordinator. And we're going to tell it what type of store we want. So SQLite is probably what you will almost always use for an iPhone app. Give it the URL where we want to store it. Everything is kind of default, there's no extra configuration or options. And we're just going to catch a fatal error and crash the app if we can't do that, because we expect there to be a persistent store. And then we're going to call the designated initializer are with our managed object model. So this chunk of code creates the entire core data stack. And once we've got this up and running, then we can actually start creating entities, persisting them to the persistent store, searching them. And doing all that good stuff. Okay. So I'll just add a line and a property under Delegate. Just to keep track of that, keep an instance of that globally so we can access it. So I will create a variable here for that. Data. Okay so we've created that and I'm just going to create a line under did finish launching, just to set that up. There we go. Okay, so kind of the last ingredient here in the puzzle is actually creating objects and storing them in the In the data store. So I'm going to go back to the data controller file, and scroll down here, create some place. We're going to add a method here. Sort of convenient method to take a feed item, and a string for a tag name, and add it to our document here. So I'm going to type in here. Okay, I have added this function here, it is called TagFeed item. So it takes a title, for a tag, so it could be like any tag you want to give to an item. And a feed item, it is going to store a tag and an image in the database. So, okay, so first thing we're going to do is, we don't want to have duplicate tags everywhere. So if I say, hey, cool image and I want to tag it with that, I don't have, like every time I type in cool image I get a whole new tag for that, I want to find out if there's already one located in our document. So this is an example of how to actually fetch entities out of your document, so let's take a look at this. So we're going to create a FetchRequest, that's how we find objects, and we want to give it the name of the entity that we're fetching. So that matches what we've created in the MDs list in our model. So, in this case, it's tag and then we can add a predicate. So this is kind of like a sequel predicate in that you can give it the name of a field and give it a condition. So we want it to equal the name of the tag title that we've requested. So, just beware though, this is an internal kind of syntax, this is not a SQL syntax, this is a Cocoa Predicate syntax, so just be aware of that if you want to do complicated queries. You're not actually doing a SQL query, this is a different type of query. So for now this is adequate, we're looking for a title that equals the title that we've requested. Then what we do is we call the managedObjectContext, that's where everything lives, and we're going to execute that FetchRequest. And then we're going to get back those fetchedTags as an array of Tag objects, so we've cast that to an array of tags. Now, we're going to do a little bit of business logic here, if we don't find any tags already with that title, we're going to create a new one, otherwise we're going to use the tag that was already there. So this is how you actually create a new object. You don't actually initialize a new Swift object in the standard way. Remember the managedObjectContext actually manages the life cycle of it. So we have to ask it for the Tag entity instead. So I'm going to call a class method on this EntityDescription object, and it's called insertNewObjectForEntityForName. So I give it the entity name, and I give it the managed object context, and it's going to give me back a casted tag object here. And for that, I'm just going to initialize it with that title that we called this function with. Okay, otherwise, if it's already existing from that fetch, we're going to instead assign tag to the fetched object as well. Once you fetch an object, then you can start manipulating that object and adding stuff to it. So we're going to create a new image as well, using the same InsertNewObjectForEntityForName. And that's going to be instead using the Image entity as well, I name, image entity name this time. And it's going to be in the same ManagedObjectContext of course, because they're all kind of live together, and we're going to set the properties on that new image as well. So newImage.title and we're going to give it the feedItem title, so remember, we received a feedItem as well as a parameter to this function. So we're going to copy that feed item title here and we're going to copy the URL as a string into that imageURL. And then here's where a little bit of magic happens. We're going to assign the tag field to the image to the tag. So remember that tag is part of the relationship between tags and images. So when I set that tag, because there's an inverse relationship, the corresponding image is going to be added to that tag's set on the tag as well. So, let's just switch over here, let's take a look. So what we're setting is, we're setting that tag to the subclass tag, which is a subclass of NSManagedObject. And then simultaneously that image is going to be included in the set of images in this property of tag. We don't have to manually make sure that that relationship is always correct. Core Data actually does that for us, because we have defined that as an inverse relationship in our model. So I'm just going to switch back to the control here. So we've done that, we've associated the image and the tag together, and the next step is to save it, right? So, what you have to is, once you make changes to your objects then you click save, or, you don't click save, you call the save function on the managedObjectContext. And what that does is it looks for all the entities that you've created and any entities that have changed or been updated are marked for saving and they'll be saved back into that persistent store. So that's another thing that the managedObjectContext does, is that it tracks changes to these entities. And as long as you call save it's going to make sure to save anything that's changed. And that's just a little bit different then our, remember, using KeyedArchiver, that was pretty static, right? So, we had to load everything into memory at once. If we wanted to make any changes to it, if you can imagine maybe adding new feed items to it, you'd have to save everything back all at once, regardless of whether it's been changed. Core Data is much more flexible in that. You don't have to load everything into memory. It's loaded only when you search for it, and when you try to access properties on those objects, and then it only saves back the objects that have been changed or updated. So this function is pretty important. It lets us create these tags and it kind of demonstrates how to search for items using FetchRequest and how to create new items and then how to set properties on those items as well and then also how to set a relationship. Okay, so let's add some code to the imageFeed dataController to ad a tag when we click on an image. So I'm just going to switch over to the imageFeed dataController. And we're going to scroll down here, make a little bit of room, and we're going to add a method so that when the user taps on a cell in that table, it's going to ask for a tag for that image. So I'm just going to ImageDidSelect, add my function here. So you notice that we're overriding the didSelectRowAtImagePath. And the important thing here is that we are creating an alertController. And that's going to have a text field in it, and when it's got a title, when it comes back if the users tabs OK, it's going to call that tagFeedItem on the dataController. And you'll notice that we're getting the dataController from the appDelegate. That's where it's stored, remember? So let's run this and we should be able to create a tag. Okay, so our app is running here. I'm just going to maybe change the scale of that just to make it a bit bigger. Okay, let's tap on one of the ones here. Okay, so we've got our alert view and just do shaking, whatever, say OK, and then it's stored that tag in the database. Now we haven't added any code to actually show those tags, so we'll do that next. So we'll add that code in the PrepareForSegue method of that viewController, the imageFeed viewController. So, I'm going to switch back to Xcode here, and go down here and add that code in, imagePrepareForSegue. Okay, so, what this does is when we start changing over to the tags viewController, it's going to create another FetchRequest. So we're going to look for all entities name tags. So, we're not actually putting any predicates on this FetchRequest. If we just give it the name Tag, it's going to give us back all of the tags for all of the entities called Tag in the NSManagedObjectContext. What we're actually going to do instead of directly fetching those Those tags used in that fetch request. We're going to create something called a fetch results controller. This is a special class that works well with table views, in that it can manage a fetch request. And then, it can be very helpful in returning the data for your TableView. So we're going to actually give that to our TagsTableViewController. And we're just going to give it an instance of this NSFetchedResultsController. So lets go into here, this is the tags table view controller. Right now, it's just got all the defaulting template, default template code here. I am just going to remove all that and put in our code to show the tags this list. So, lets delete all that and here. [SOUND] Add those methods in. So these are just standard table view controller methods. But we're going to have an NS fetched results view controller here as a property. And let's make sure we import core data, as well, there we go. And also, we're going to have to import Core Data in our image table view controller, as well, since. It's accessing, it's using that NSFetchedResultsController. Okay, so now you'll see the standard table view controller methods here. It's going to ask, you have the number of sections. You have number of rows in section. But instead of having a an array of objects, we're going to called the NSFetchedResultsController to do that for us. It can give us a count for each section and it can also give us the number of objects in each section. And then in the self arrow at index path, we actually, again, send the index path directly to the fetched results controller. And get back the actual tag that its fetched. So we don't actually have to have an array of objects. We actually use this results controller as well. And then, to make sure tha tit actually gets those objects we're going to tell it to perform a fetch, when their table view is about to appear. This is a very simple use of the fetched results controller. I mean, almost you could just use a standard array for this. But, just keep in mind that this fetched results controller has a whole bunch of other functionality for managing changes to your data. So, if you are adding or deleting cells. Using this table view, you can sort of set up a delegate call back for that fetch results control that will notify your table view of changes. To the resulting data set. So, this is just a very simple use of it, but we're not really going to go into adding and removing tags in this video. But, just keep in mind that you can add a whole bunch of stuff as well, using the ns fetch results controller. Okay, so let's bill and run that, and hopefully we'll see tags that we've added previously. Just originally when we last tested it. So we're up and running, change over here. All right, so that's the tag we originally added. We added that shaking tag. And remember we saved it, so you know, even though we stopped the app and restarted it. It's already been saved into our document and it comes back so it's persistent. All right, okay, so we have one more thing to fill in here and that is, we want to load the images, right. So, when you click on a tab or tap on a tag in that table view, you want to load at all the images that go with that tag. So, I'm just going to create another perform, will perform Segway method here. So go here, tag, okay, prepare for Segway. Okay so, under prepare for the Segway this is going to be called when we tap on one of those. And this time, we're actually going to be using our image feed table view controller again and you'll remember that it takes a feed object. So, we're actually going to artificially construct that feed object based on the images that are related to a tag. So this time, since we already have a tag entity object loaded. We're actually going to make use of that image is property of that text. So, we don't have to do a whole fetch for images this time. We already have and they're already part of that set. So, we can just call that images property and get those images back as an array. That makes it a little bit easier. So, once we have those images, we can just loop through them. And then create new feed items based on the title and the imageURL that we had stored. And then, we'll just create a new feed based on all of those items. We're just giving it a blank URL this time. And then, we're setting that feed as the feed on our destination view controller. So, that's kind of a good pattern for core data. You get an object, and then if you want to select objects that are related to that. You can actually just access them through a property on that object. So let's click run and go. And, we should see now the image that we stored under that tag. So just go here, go to tags and then we see that image back again. You know, so if I go down here, maybe I can choose one here, this guy, we'll click that one. We'll call him the cool pug, then we'll click tags. Now, we have our cool pug here as well. If I go back and I create another one here, let's say, call this guy. If given the same tag, we'll now I see that we have two of those stored under that tag. Because remember that we were only creating one tag per kind of title. We're not duplicating them. Okay, so that is the basics of Core Data. That's how you can basically set up the Core Data stack. And set up a model and access Data objects within your core data context. There's a lot more than can be done. There's tons of stuff to do with performance. And all sorts of interesting stuff you can do with NS Managed object sub classes. I mean really, Core Data could be an entire book or even an entire course on its own. But, this is just the beginning and I hope you enjoyed this video.