Chapter 38: DreamLister

Learn all about CoreData in this app that will let you make a wish list of all your most coveted items.


What you will learn

  • How to use Core Data
  • How to use NSFetchedResultsController
  • How to sort Core Data results
  • How to use PickerViews

Key Terms

  • Core Data
  • NSFetchedResultsController
  • Picker View

Resources

Download here: https://github.com/devslopes/book-assets/wiki

In this chapter you will be building the DreamLister app. Figure 5.6.0 shows what this app will look like. The main goal of this app is to do a deep dive on CoreData, learning about when to use it and how to use it.

You will learn about how to use the NSFetchedResultsController which facilitates displaying data in a Table View while using CoreData. You will also learn about how to use Picker Views. So let's get started!

Figure 5.6.0 A, B

Figure 7.0 A FinalScreen1.png Figure 7.0 A FinalScreen2.png

When and Why to Use Core Data

In the myHood app we learned about saving data using UserDefaults. The other common way is using CoreData. You may have heard about how scary and complex Core Data is, and a lot of programmers get scared off by that. And it is a little bit complex. But thanks to the recent changes in iOS 10, it is a little easier than it was before, and I am going to do my very best to break it down and simplify it for you.

But first, WHAT is Core Data, and when would you use it? Core Data is a framework provided by Apple that allows you to persist, or save, complex data to your device so it can be accessed offline. The keywords here being complex and offline.

If you are just saving simple things like user settings, or login name, then you are totally fine using UserDefaults. Or if you don’t need data off line, then you may not need Core Data.

These days just about everywhere you go you have Internet connection in some form, so these are things you need to think about when deciding whether or not to use Core Data. Just because you need to save something, doesn’t always mean Core Data is the right option. Another thing to think about is now days you have some services like Firebase that provide both on and off line syncing.

So just to reiterate, you want to use Core Data when you have complex data you need to save off line. And I should clarify, its not just for offline use, you can also download data from the Internet, and save to Core Data, then fetch it and serve it up locally. And this provides for a very fast experience in serving up the data.

So what are some other benefits to using Core Data?

Core Data is set up to manage a lot of things for you:

  • Like Save and Undo functionality, sorting and filtering based on customizable attributes. (Apple claims Core Data typically decreases 50 to 70 percent the amount of code you would write)

  • Core Data tracks changes for you. Better memory management. (when you have changed or updated objects you don't have to change the entire data set, just the objects being modified)

  • Makes it easy to display your data ( i.e. NSFetchResultsController)

  • Provides a graphical user interface to manage:

    1. Your entities
    2. Attributes
    3. Relationships And other details related to your model data.

So, now that you know WHEN and WHY to use Core Data, lets go over HOW Core Data works at a high level, as well as the changes made in iOS 10.

How Core Data Works

Here, we have a model of the Core Data stack based on versions by Apple and the site objc.io. This is the entire Core Data stack basically. The whole framework boiled down.

Figure 5.6.1 Figure 7.1 CoreDataStack.png

So, lets talk about storage and databases. With Core Data or really any storage, you have a database. And the most common one for iOS and Core Data is SQLite. In the past, you used to have to work with SQLite directly. And thats a huge pain. So Core Data was born, to help ease data management. It was introduced in April 2005 and has been making improvements ever since, and with iOS 10 is easier now more than ever.

So how does it work. You are familiar with custom classes by now. In the myHood app we worked with a custom Post class with properties like title and description, etc. With Core Data we have entities that have attributes. These entities are NSManagedSubclasses, and reside in the NSManagedContext.

Figure 5.6.2 Figure 6.7.2 CoreDataEntity.png

Think of the context like a scratch pad. When you create or insert an entity into the context, it is created in memory. You can modify it and display it, but it is not yet persisted (or saved) to memory.

Once you are ready to save it, the persistent store coordinator and managed object model work to send it down to the persistent store and then to the database, where it is stored. That data is now there permanently, until it is deleted.

With the changes in iOS 10, the NSManagedObject context, managed object model and persistent store coordinator that were previously separate functions in the app delegate are all now combined under the NSPersistentContainer.

Figure 5.6.3 Figure 6.7.3 Persistent Container.png

Now the nice thing about Core Data, is that you don't actually need to worry about most of this chart. Most of this is handled behind the scenes by Core Data. What you need to understand is how to create NSManagedObject entities, define attributes, relationships, and how insert into the context, save, and fetch those results to be used at a later point in time.

That's basically Core Data from a very high level. It's really not too bad once it is broken down. So, remember this terminology and the flow chart, because that will really help you get a handle on things quickly. In fact, once you’re done reading this for the first time. Go back and read this a couple times just to get the high level terminology and flow ingrained. Then, we’ll be ready to start our project!

Creating an Xcode Project

Open Xcode and, from the File menu, select New and then New Project…. A new workspace window will appear, and a sheet will slide from its toolbar with several application templates to choose from. On the top, make sure to select iOS. In the Application section, select Single View Application and click Next. (Figure 5.6.4). (Note that Apple changes these templates and their names often. But it should be very similar to what is shown here)

Figure 5.6.4

Figure 6.7.4 SingleViewApp.png

Then fill out the product name. I'm calling mine DreamLister. You can select a Team and Organization Name. The organization identifier is usually in reverse DNS format. Make sure the language is Swift and select which devices you want your app to run on. We’ll keep it simple for this app and just do iPhone. We DO need Core Data so make sure that is checked! We do not need Unit or UI Tests for this chapter, so you can leave those unchecked (Figure 5.6.5).

Figure 5.6.5

Figure 6.7.5 NameandSave.png

Finally choose a location for your app to be saved to, and I recommend checking the Create Git repository so that you can push your project to your Github (or Bitbucket) account and show off your awesome app to friends and potential employees!

Once the project loads, you will be looking at a brand new project that is just waiting to be turned into something great!

Exploring a CoreData project

First lets take a look into the AppDelegate file. Because we selected to include Core Data when we created our project, the app delegate was created with the following functions:

// MARK: - Core Data stack

    lazy var persistentContainer: NSPersistentContainer = {
        /*
         The persistent container for the application. This implementation
         creates and returns a container, having loaded the store for the
         application to it. This property is optional since there are legitimate
         error conditions that could cause the creation of the store to fail.
        */
        let container = NSPersistentContainer(name: "DreamLister")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.

                /*
                 Typical reasons for an error here include:
                 * The parent directory does not exist, cannot be created, or disallows writing.
                 * The persistent store is not accessible, due to permissions or data protection when the device is locked.
                 * The device is out of space.
                 * The store could not be migrated to the current model version.
                 Check the error message to determine what the actual problem was.
                 */
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container
    }()

    // MARK: - Core Data Saving support

    func saveContext () {
        let context = persistentContainer.viewContext
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                let nserror = error as NSError
                fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
            }
        }
    }

As we saw in Figure 5.6.3 the NSPersistenContainer now includes the NSManagedObjectContext, the Managed Object Model, as the NSPersistentStoreCoordinator. We will mostly only be working directly with the Context.

Then we have the saveContext() function at the bottom. This is a nice baked in function that we call when we're ready to save an entity to the database.

Next, look in the Project Navigator on the left, and you will see a new file we have not yet encountered call DreamLister.xcdatamodelid as seen in Figure 5.6.6.

Figure 5.6.6

Figure 6.7.6 DataModel.png

When you click on it you will see + buttons for adding an Entity and Attribute. Remember that an Entity is equivalent to a custom class, and an Attribute is equivalent to a property of a custom class.

So let’s think about our app. We have a Table View that displays some information about an item we want. It has an image, a name, description, and a store we can select from a Picker View.

Let's go ahead and create our first entity. The first entity we create will be for the Item itself. Click on the plus button to Add Entity. The entity will then appear under the Entities header. Double click on it to rename it to Item.

Figure 5.6.7 Figure 6.7.7 CreateEntity.png

Then to the right there is a header called Attributes. There is a small + sign to add attributes. Click the + and set the name of the attribute to title. Then there is an option to set the type. The same way we declare the type of a property in a custom class, for example var name: String!. Here we also set the type. Since our title is a String, go ahead and select String as the type. Good job, you just created your first CoreData entity with an attribute!

Figure 5.6.8 Figure 6.7.8 .png

Now add the following attributes to your Item entity.

details of type String created of type Date price of type Double

It should end up looking like this:

Figure 5.6.9 Figure 6.7.9.png

Next we want to add some more entities. We are going to need an Image, a Store, and an ItemType. Create each of those Entities, and add the Attributes as follows:

For Image, make an attribute titled image of type Transformable. For Store, make an attribute titled name of type String For ItemType, make an attribute titled type of type String

Your end result should look like the following.

Figure 5.6.10 Figure 6.7.10.png Figure 5.6.11 Figure 6.7.11.png Figure 5.6.12 Figure 6.7.12.png

Now you may be wondering, why are we having separate entities for things like Image and ItemType? Couldn’t those just be Attributes of the Item entity itself? Yes, they could be. But, I want to teach you about data relationships as well.

For instance, if we wanted the Store to also be able to have an Image, instead of both the Item AND the Store having Image attributes; instead you have them both reference an Image entity.

So what are relationships? In life we have relationships. In your family, lets say you have a Mom, brothers, sisters and a house. There's relationships between all of these. Now lets pretend that your Mother is an instance of the entity Parent.

You and your siblings are instances of the entity Child. And your house is an instance of the entity House. There is a relationship between you and your Mother. But what kind is it?

For you (a Child entity) it is one to one. Meaning that you have one Mother. But for your Mother, who has several children, the relationship of a Parent entity to a Child entity is one to many. And the relationship of both the Child and Parent entity to the House is one to one, while the relationship of the House to both Parent and Child entity is one to many.

So looking at our data set of entities, how do we want our relationships to look?

For each Item there should be one Store. But for each Store there could be many associated Items. For each Item there will be one type, but for each type there potentially could be many Items. For each Item and Store there will be one Image, and for each Image there will be one Store or Item.

So lets see how to make this work in Xcode.

Lets start with the Item entity. Below Attributes, you will see a section under Relationships. Click the + button to add a relationship and then select a destination value.

So, to start out lets create the relationship that goes from the Item to the Image.

Click the + button to create a relationship, then name that relationship image. Select the Destination as Image. At this point we will not add the Inverse value. On the right hand Utilities Pane, in the Data Model Inspector, you will see a dropdown menu with Type. You can choose To One or To Many. For the Item to the Image relationship, we want it to be To One.

Figure 5.6.13 Figure 6.7.13.png

Now add relationships for the remaining entities. We want Item to be able to connect as follows, and each of them should be of Type: To One.

Figure 5.6.14 Figure 6.7.14.png

Next lets add relationships to the Image entity. We will add relationships to the Item and Store entities, titled item and store with destinations to their respective entities. The relationship type will be To One for both of these. Also, now that we have added a relationship from Image to Item, and we already have a relationship from Item to Image, we can select the Inverse relationship from the dropdown, which is image. This won’t be available yet for the store relationship. It should appear as follows:

Figure 5.6.15 Figure 6.7.15.png

Next add relationships for Store. We will create two relationships of title image and item, supply their Destinations, and at this point the Inverse will be available for both.

This time though, for the item relationship type, it will be To Many. This is because we want a Store type to be able to be applied to many different types of Items. I buy tons of stuff from Amazon. So Amazon has to have a relationship type of To Many to Items.

Figure 5.6.16 Figure 6.7.16.png

Lastly we need to add the relationships for the ItemType. The relationship is titled item and has the type of To Many. Again, you could have many Items of ItemType: electronics or cars or whatever you want!

It should appear as follows:

Figure 5.6.17 Figure 6.7.17.png

Now that all the relationships have been defined, do a quick run through of your entities, to make sure the inverse values have been set for all relationships.

Now for those of you that are visual learners, there is a graphical representation of the Entities and the Relationships between them that can be seen by clicking in the bottom right and toggling the Editor Style.

You may need to drag the Entities around a little the first time you open it, and you will see the arrows that represent the relationships. The arrows that end with a single arrow represent To One relationship. The double arrows represent To Many relationships.

Figure 5.6.18 Figure 6.7.18 Graphical View.png

Now that we have created our data models, we need to create our NSManagedObjects (refer back to Figure 5.6.1). Select all 4 of our entities, then go to Editor and select Create NSManagedObject Subclass. (Figure 6.7.19) A window will pop up (Figure 5.6.20) with the project selected, click Next. Then a second window will pop up with the entities selected (Figure 5.6.21), and we do want all entities selected, so click Next, then click Create.

Figure 5.6.19

Figure 6.7.19.png

Figure 5.6.20 Figure 6.7.20.png

Figure 5.6.21 Figure 6.7.21.png

This will create two files for each Entity as shown below:

Figure 5.6.22

Figure 6.7.22.png

Let’s clean up our file system a little. Create a new group called Model. Within that group create another one called Generated. Then place the four new files that end with Class in the Model group. Then select the four new files that end in Properties in the Generated folder.

Figure 5.6.23

Figure 7.23.png

Now lets take a look at these files, and talk about them a little. Lets look at the file called Item+CoreDataClass.swift.

import Foundation
import CoreData

public class Item: NSManagedObject {

}

This is the class for the Item entity that we created in our .xcdatamodelid. If we need to implement any code for these classes, this is where you would do that. Now let’s look at the extension file called Item+CoreDataProperties.swift.

import Foundation
import CoreData
import

extension Item {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<Item> {
        return NSFetchRequest<Item>(entityName: "Item");
    }

    @NSManaged public var title: String?
    @NSManaged public var price: Double
    @NSManaged public var created: NSDate?
    @NSManaged public var details: String?
    @NSManaged public var image: Image?
    @NSManaged public var itemType: ItemType?
    @NSManaged public var store: Store?

}

At the time of writing this, Xcode still has a bug (even though its the official release!) where it adds an extra empty import statement. If you are getting an error, that is probably why, if there is no empty import statement then don’t worry about this.

So this file is an extension of the previous Item class we looked at. You don’t want to change anything here. This is auto generated code that manages the relationships and fetchRequests. If you do need to make a change to the data model, make the change in the .xcdatamodelid file, then re-generate this file.

Now lets go back to the Item+CoreDataClass file and add just a little custom code. You remember that one of the attributes of the Item entity is created of type ‘Date’. We want to know the time and date that an item is created so that later on, we can add sorting by date added. So we are going to add some code to set the created attribute equal to the time the Item was created in the Context (or scratchpad).

public class Item: NSManagedObject {

    public override func awakeFromInsert() {
        super.awakeFromInsert()
        self.created = NSDate()
    }
}

So all we're doing here is saying when the Item is inserted into the Context, set the created attribute equal to the current date and time.

I also want to make a quick change by renaming the ViewController.swift file to MainVC.swift just to eliminate confusion when I am referencing this file and not a generic ViewController. So just make the changes as shown in Figure 5.6.24. You will rename the file in the Project Navigator, rename the class, and rename the commented part at the top.

Figure 5.6.24 Figure 7.24.png

Lastly, we need to change the class of the View Controller in the Storyboard to our newly renamed file MainVC

Figure 5.6.25 Figure 7.25.png

Building the User Interface

Now we are ready to start building the user interface for the MainVC screen.

Open the Main.storyboard file and select the View Controller and go to Editor then Embed In and select Navigation Controller. We have learned about Navigation Controllers in previous chapters, but basically it provides some built in functionality for menu bars, and moving between screens.

Lets start putting our view together. If we go back to Figure 5.6.1 A, we see we need a Table View, a Segmented Control, a Title, and a + button to add new posts.

Lets start from the top and add that + button. In the object library search for bar button item and drag it into the top right of your View Controller and it will snap into place. Select the button, and open the Attribute Inspector in the Utilities pane and set the System Item to Add. Change the Tint to Dark Gray Color.

Figure 5.6.26

Figure 7.26.png

Then select the Navigation Item and set the title to Dream Lister.

Figure 5.6.27 Figure 7.27.png

Next, select the Navigation Bar and set the Bar Tint to White, and the Title Color to Dark Gray Color.

Figure 5.6.28 Figure 7.28.png

Now we need to add the segmented control. Search for segmented control in the object library and drag it to the top below the navigation bar. Set the constraints with Constrain to margins checked 0 from the left, 20 from the top, and 0 from the right and check the height. We also need to set the number of segments to 3.

Figure 5.6.29 Figure 7.29.png

To change the name of the segment sections select the drop down menu by Segment that you want to change the name for, then just change the Title. We want segment 0 to be ‘Newest’, segment 1 to be ‘Price’ and segment 2 to be ‘Title’. Then scroll down a little and change the Tint to Dark Gray Color.

Figure 5.6.30 Figure 7.30.png

Next we need to add our Table View. Search for Table View in the object library and drag it into the View Controller under the segmented control. Add constraints with Constrain to margins checked, 0 from the right, 20 from the top, 20 from the left, and 0 from the bottom. Click add constraints.

Figure 5.6.31

Figure 7.31.png

Now drag in a Table View Cell and with the Table View Cell selected, set the Row Height to 150 in the Size Inspector. Also set the background color to a different color to help visualize the size of the cell. With the Table View Cell selected, go to the Attributes Inspectors and set Selection to none.

Figure 5.6.32 A Figure 7.32 A.png

Figure 5.6.32 B

Figure 7.32 B.png

Next we want to add another view INSIDE the Table View Cell that will contain the information. We do this so that we have separation between the cells. Search for uiview in the object library and drag it into the Table View Cell Add constraints with Constrain to margins checked and set left, top, right, and bottom to zero. The height of the inner view should then be 133.

Figure 5.6.33

Figure 7.33.png

Next add the image view on the left of the inner view we just added. Make it 100 x 100 and set constraints 8 from the right. Set height and width and click add constraints. Then add alignment constraint Vertically in Container.

Figure 5.6.34

Figure 7.34.png

Figure 5.6.35

Figure 7.35.png

Now we need to add three labels for the title, price, and description. Lets add them now. For the top Title label, set the constraints to 8 from the top, left and right. Check height, and click add constraints.

For the Price label, add constraints 8 from the top, left, and right. Check height and click add constraints. For the bottom label, set left, top, right, and bottom constraints to 8 and click add constraints.

Figure 5.6.36

Figure 7.36.png

Now select all three labels, and change the color to Dark Gray Color and change the font to Helvetica Neue. Set the style for the top Title label to Medium. And change the Lines to 3 for the bottom description label, and drop the font size to 15.

Figure 5.6.37 Figure 7.37.png

Now lets remove the blue background by selecting the Content View and changing the Background to Clear Color. Then do the same thing for the Table View.

Figure 5.6.38 Figure 7.38.png

Now to make the prototype cell look nicer while we are working on this, I am going to drag in a test image of a Tesla into the Assets.xcassets and set the image view to the file name (you can choose any image you like, or you can go with the Tesla image that can be found in the assets folder) then change the Content Mode to Aspect Fit.

Figure 5.6.39 Figure 7.39.png

View Styling

Now that we have the Table View Cell all set up with constraints, we're going to add a little styling to it to make it really pop.

So with your left Navigator pane open, select your project folder, right-click and select New Group and name it View. Then right click on that new folder and select New File, Select Cocoa Touch Class and click Next. Make the subclass inherit from UIView, and name it MaterialView. Then go ahead and delete the auto generated comments in green. And we are ready to get started.

First off change the class to an extension:

Was: class MaterialView: UIView { Change to: extension UIView {

What we're doing here is instead of creating a class that inherits from UIView, we are making an extension of the UIView that will be available to anything that inherits from it. We'll then be able to toggle the MaterialView properties we add on and off depending on whether we want it to apply. Since pretty much all the UI elements inherit from UIView, this styling will be available to all of them.

Next modify the file as follows:

private var materialKey = false

extension UIView {

    @IBInspectable var materialDesign: Bool {

        get {
            return materialKey
        }
        set {
            materialKey = newValue
    }

    }
}

First we define a variable materialKey outside the extension and initialize it to false. This is the variable that will determine whether the view is using this styling. Then we create an IBInspectable. This is what actually creates the interface to select in Storyboard. Next we create a getter and setter for the materialKey.

Then under the materialKey = newValue add this if statement.

if materialKey {

                self.layer.masksToBounds = false
                self.layer.cornerRadius = 3.0
                self.layer.shadowOpacity = 0.8
                self.layer.shadowRadius = 3.0
                self.layer.shadowOffset = CGSize(width: 0.0, height: 2.0)
                self.layer.shadowColor = UIColor(red: 157/255, green: 157/255, blue: 157/255, alpha: 1.0).cgColor

            } else {

                self.layer.cornerRadius = 0
                self.layer.shadowOpacity = 0
                self.layer.shadowRadius = 0
                self.layer.shadowColor = nil
            }

All we're doing here is saying, "if the user has selected to use this MaterialView styling, then this is the styling we will implement, followed by the corner radius, shadow, shadow color etc. If the user does NOT select the MaterialView option, then we remove any styling to return it to the default state."

So now, when we head back over to the Main.storyboard file and select the inner view of the Table View Cell, we'll see the option for Material Design. Set it to On.

Figure 5.6.40 Figure 7.40.png

Now that you can see the behavior of the code you wrote, it should make a little more sense. So lets quickly revisit what we did. The IBInspectable is what implements the ability to view a property in the Storyboard. We initialized the default behavior to be off, and then checked for the newValue that the user enters (on, off). Then we set the styling depending on which is selected.

Creating the Custom Cell

Next up is creating the custom cell. Hopefully you remember how to do this from the myHood app. Right-click on the View group and select New File, then select Cocoa Touch Class. Set the subclass to UITableViewCell and name it ItemCell then click Next and then Create.

We are creating this custom class that inherits from UITableViewCell so that we can have outlets that hook up to the UI elements we created in the Table View Cell in the Storyboard.

Replace the contents of the ItemCell.swift file with the following:

class ItemCell: UITableViewCell {

    @IBOutlet weak var thumb: UIImageView!
    @IBOutlet weak var title: UILabel!
    @IBOutlet weak var price: UILabel!
    @IBOutlet weak var details: UILabel!

    func configureCell(item: Item) {

        title.text = item.title
        price.text = "$\(item.price)"
        details.text = item.details

    }

}

Here we have written out our IBOutlets and created our configureCell function. Let’s come back to the configureCell function and hook up our IBOutlets. Go to the Storyboard and the first thing we need to do is change the Class to ItemCell in the Identity Inspector.

Figure 5.6.41 Figure 7.41.png

Then right-click on ItemCell in the Document Outline and drag over from the IBOutlet labels to the corresponding UI element. thumb goes to the Image. title to the top label. price to the second label. details to the bottom label.

Figure 5.6.42 Figure 7.42.png

Now lets talk a little about the configureCell function. This function will be used in the MainVC View Controller. What it does is accept an Item object, then set the attributes of the Table View Cell to the the corresponding properties of the item that is passed into the configureCell function. We are not going to worry about the image at this point.

Next we are going to hook up the Table View to the MainVC. Open up the MainVC file and create an IBOutlet for the Table View as follows:

@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var segment: UISegmentedControl!

and place this above the viewDidLoad() file.

Then in Storyboard, right-click on the Dream Lister view and connect the tableView outlet to the table view. Then connect the segment outlet to the segmented control.

Figure 5.6.43 Figure 7.43.png

Lets head back to the MainVC file and start implementing the code we will need to work with the Table View. We need to add two protocols, and set the delegates. So add the following code:

class MainVC: UIViewController, UITableViewDelegate, UITableViewDataSource {

then in viewDidLoad() add:

 override func viewDidLoad() {
        super.viewDidLoad()

        tableView.delegate = self
        tableView.dataSource = self

    }

What we've done here is add the two protocols to our class that will allow us to work with the Table View. We then set the delegate and dataSource to self, meaning that this class will handle both of those.

Now you will probably be getting an error that says “Type MainVC does not conform to protocol UITableViewDataSource". This means that we have not yet implemented the required Methods that go along with these protocols. For the Table View Protocols, those methods are the following:

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        return UITableViewCell()
    }

  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {


        return 0
    }

    func numberOfSections(in tableView: UITableView) -> Int {

        return 0
    }

   func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 150
    }

We're familiar with these Methods. The first one is used to dequeue and fill the cells. The second returns the number of rows. The next one returns the number of sections in our Table View. And the last one sets the height of the row to 150. Right now, its still pretty boiler plate stuff.

NSFetchedResultsController

We are finally ready to start working with some CoreData code. First thing to do is at the top of the MainVC.swift file, import CoreData.

Next we need to add a new protocol for the NSFetchedResultsController. Lets modify the class declaration as follows:

class MainVC: UIViewController, UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate

So what is an FRC? From the Apple documentation: “You configure an instance of this class using a fetch request that specifies the entity, optionally a filter predicate, and an array containing at least one sort ordering. When you execute the fetch, the instance efficiently collects information about the results without the need to bring all the result objects into memory at the same time.

As you access the results, objects are automatically faulted into memory in batches to match likely access patterns, and objects from previous accessed disposed of. This behavior further serves to keep memory requirements low, so even if you traverse a collection containing tens of thousands of objects, you should never have more than tens of them in memory at the same time.”

So basically, it is a handy dandy class Apple has made for us to make it easier to connect the data in CoreData with displaying it in a Table View (or Collection View). It has built in functionality for things like memory saving, filtering, saving and deleting entries, and more. It's really pretty cool once you get it all set up. So lets keep trucking.

First we will declare the variable for our FRC right under the IBOutlets in the MainVC.swift file. Add the following:

var controller: NSFetchedResultsController<Item>!

We're declaring our FRC, but what is important to note here is that we are required to state what Entity we will be working with, so thats why we have <Item>.

Now I gotta warn you, that we're going to be writing a LOT of code here without being able to test anything. So strap in tight!

Next thing I think we will do is head into the AppDelegate file, and at the very bottom, even outside of the last curly brace, add the following:

let ad = UIApplication.shared.delegate as! AppDelegate
let context = ad.persistentContainer.viewContext

What we did is created a constant called ad that is a path to the app delegate. So for instance, now when we want to access the saveContext() function that lives in the appDelegate, all we have to do is say ad.saveContext(). We also made it easier to call the context from the app delegate by creating the constant context. Now press save, so that these constants will be available in other files.

Now lets go back into MainVC.swift file. We're going to add a BIG function called attemptFetch(). This can be placed at the bottom of the file, but still inside the class as follows:

func attemptFetch() {

        let fetchRequest: NSFetchRequest<Item> = Item.fetchRequest()
        let dateSort = NSSortDescriptor(key: "created", ascending: false)
        fetchRequest.sortDescriptors = [dateSort]

        let controller = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)

        controller.delegate = self

        self.controller = controller

        do {

            try controller.performFetch()

        } catch {

            let error = error as NSError
            print("\(error)")

        }

    }

Lets break this down line by line. First we create a fetchRequest. This is like saying, “hey, go down into the database and see what you can find that's of the type entity, Item”

Then we have a sort descriptor. This class allows you to compare attributes of an entity. In this case we have put “created” because we're going to sort on Newest as default. Then we create the controller and pass in the fetch request and the context from the app delegate. We can then put nil for the last two parameters.

Next we set the Controller variable we declared at the beginning to the Controller we just instantiated.

Last, we attempt the fetch, using the do-catch method.

Next we are going to add a few more methods below the attemptFetch() function as follows:

  func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        tableView.beginUpdates()
    }

    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        tableView.endUpdates()
    }

What these methods do is listen for when changes are about to be made and when they have been made, respectively. When they're about to be made, they get ready to update the Table View with the beginUpdates() function. This is analogous to the tableView.reloadData() you should be familiar with.

Next we have another big function to write. This one you can get to auto generate by typing didChange and finding the method as shown in following figure:

Figure 5.6.44 Figure 7.44.png

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {

    }

This is another helper method that we get with CoreData and the FRC. It listens for specific changes and can perform actions based on the type of change. If you command click on the NSFetchedResultsChangeType you will see the following types of changes:

 case insert

 case delete

 case move

 case update

We need to write code to handle each of those cases so lets get to it. Modify the function as follows:

func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {

        switch(type) {

        case.insert:
            if let indexPath = newIndexPath {
                tableView.insertRows(at: [indexPath], with: .fade)
            }

        case.delete:
            if let indexPath = indexPath {
                tableView.deleteRows(at: [indexPath], with: .fade)
            }

        case.update:
            if let indexPath = indexPath {
        let cell = tableView.cellForRow(at: indexPath) as! ItemCell
        //come back later
            }

        case.move:
            if let indexPath = indexPath {
                tableView.deleteRows(at: [indexPath], with: .fade)
            }
            if let indexPath = newIndexPath {
                tableView.insertRows(at: [indexPath], with: .fade)
            )

        }
    }

It may seem like a lot, but it is mostly repetitive and self explanatory. We have a switch statement and cases that represent each type of possible changes. Then for each type, we have the suitable action.

For insert, we grab a new index path (since it is new) and insert a new row. If the case is delete we grab the indexPath that we want to delete, and delete it! The update case is an interesting one, we'll come back later to it. Finally we have the case of move, where we take the row at one location, delete it, and then insert it at another location.

Next we are ready to start updating some of our Table View methods. Now in the past while working with Table Views, you would usually have an array with data in it and return the .count value for number of rows in the section. You would also manually select the number of sections. Since we are working with the FRC, the number of rows and sections depends on what the FetchRequest returns. We use the following code:

    func numberOfSections(in tableView: UITableView) -> Int {

        if let sections = controller.sections {
            return sections.count
        }

        return 0
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if let sections = controller.sections {
            let sectionInfo = sections[section]
            return sectionInfo.numberOfObjects
        }
        return 0
    }

Next we want to update the cellForRowAt function. We’re going to do something a little different than we're used to doing. Remember in our ItemCell.swift file, we created a configureCell function. Normally we'd call that directly in the cellForRowAt function. But we actually need to use that function twice in the MainVC file. So what we're going to do is create a secondary configureCell function inside the MainVC file right below the cellForRowAt function as follows:

  func configureCell(cell: ItemCell, indexPath: NSIndexPath) {

        let item = controller.object(at: indexPath as IndexPath)
        cell.configureCell(item: item)

    }

This function accepts a cell and an index path, then calls the original configureCell function in ItemCell.swift.

Now we can update the cellForRowAt function as follows:

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath) as! ItemCell
        configureCell(cell: cell, indexPath: indexPath as NSIndexPath)
        return cell
    }

Here we are creating a cell from a dequeued cell, then passing that cell into our secondary configureCell function, which is then passed to the ItemCell configureCell function that actually updates the cell.

And we can also return to our case.update function down at the bottom of MainVC that we said we would return to. So go ahead and update it as follows:

case.update:
            if let indexPath = indexPath {
                let cell = tableView.cellForRow(at: indexPath) as! ItemCell
                configureCell(cell: cell, indexPath: indexPath as NSIndexPath)

            }

When we update a cell, it will grab that cell, and send it to the MainVC configureCell function which then sends it to the ItemCell configureCell function that makes the updates to the modified cell.

With a final addition to our code we will be ready to run it for the first time! In viewDidLoad under the tableView delegate and data source add:

attemptFetch()

And run it! It won’t be very exciting yet because we don't have any data, but it should look something like this.

Figure 5.6.45 Figure 7.45.png

Let’s make a quick change to the TableView in Storyboard. With the TableView selected, go into the Attributes Inspector in the right Utilities pane, find Separator and change it from Default to None. Then un-check both the Scroll Indicators Shows Horizontal Indicator and Shows Vertical Indicator.

Lets go back to our MainVC and put in some test data so that we can actually test that our FRC is working correctly.

At this point we finally get to save something to the database using CoreData. Create a new function called generateTestData() and create it as follows:

    func generateTestData() {

        let item = Item(context: context)
        item.title = "MacBook Pro"
        item.price = 1800
        item.details = "I can't wait until the September event, I hope they release new MPBs"

        let item2 = Item(context: context)
        item2.title = "Bose Headphones"
        item2.price = 300
        item2.details = "But man, its so nice to be able to blaock out everyone with the noise canceling tech."

        let item3 = Item(context: context)
        item3.title = "Tesla Model S"
        item3.price = 110000
        item3.details = "Oh man this is a beautiful car. And one day, I willl own it"

        ad.saveContext()

    }

Let's look at one of these items. To create an item in the Context, or our scratchpad, all we have to do is let item = Item(context: context). You create the name of the variable, the type of Entity, then pass in the Context we created the path too in the appDelegate.

Now that entity has been created in the context and we can assign values to its attributes. We did that three times. Last but most important, we add ad.saveContext().

If we did not have the ad.saveContext() and then generate the test data, it would still show up in our simulator. BUT if you stopped the simulator, removed the test data, and ran it again, the Table View would return nothing because the ad.saveContext() command was not included. Just because you have an entity created in the context, does not mean it is saved to disc!

Add generateTestData() to viewDidLoad above the attemptFetch() command and run the simulator. Ta-da! You should have three items displaying in your simulator as follows:

Figure 5.6.46

Figure 7.46.png

If you run it again, you will see there are now 6 entries. This is because each time you run generateTestData() it will add three more entries to your database. If you don't want to keep adding entries, you can just comment out the generateTestData() function. So go ahead and do that.

Create Second ViewController

In the Project Navigator on the left, create a new group by right-clicking on your project and select New Group and name it Controller. Then right-click on the group Controller and select New File, select Cocoa Touch Class and click Next. Name the file ItemDetailsVC and set the subclass to UIViewController then click Next and Create.

Then head over to Storyboard and drag a new View Controller into the Storyboard next to the initial screen. Now make sure to go to the Identity Inspector, and change the class to ItemDetailsVC. Don’t forget it!

So what do we need? We have a + button on the MainVC screen, so we will need a segue from the button to the ItemDetailsVC screen. But we also want to be able to tap a cell that will open a second screen for editing that cell. So we are going to have two segues, one from the ‘+’ button and one from the MainVC View Controller to the ItemDetailsVC View Controller.

Control click and drag from the + button to the new View Controller and select show for the segue type. Then name the identifier ItemDetailsVCNew.

Figure 5.6.47 Figure 7.47.png

Figure 5.6.48 Figure 7.48.png

Next control click and drag from the initial View Controller icon to the new View Controller and select show for the segue type. Then name the identifier ItemDetailsVC.

Figure 5.6.49 Figure 7.49.png

Figure 5.6.50 Figure 7.50.png

Now lets start adding our UI elements. Below is a reference to our end goal.

Figure 5.6.51

Figure 7.51.png

So we need an image view that we will add to the top left, and make it 100 x 100, and constrain it 8 from the top, 0 from the left and set the width and height. At this point, drag in the imagePick.png file to the Assets.xcassets folder and set the image to ‘imagePick.png’.

Figure 5.6.52

Figure 7.52.png

Figure 5.6.53 Figure 7.53.png

Drag a button over the Image View, and set the same size of 100 x 100 and the same constraints as the image, 0 from the left, and 8 from the top. With Constrain to margins set.

Next we need to add three text fields. Make sure you are adding text fields and not text views or labels. Place two, to the right of the image/button and one below the image as shown below. For all three text fields remove the border, and set the Background to a light gray color. Change the height to 40. Quick note: at this point in the book we will not be having screenshots for every single step. You should have the experience by now to know how to do things like setting height, changing background colors etc.

Add placeholder text to Title, Price, and Details as seen in the following figure. Change the font of all three to Helvetica Neue and drop the size to 15.

Add constraints to the Title field: 8 from the left, 20 from the top, and 0 from the right, with Constrain to margins checked. Set height, click Add Constraints.

Figure 5.6.54 Figure 7.54.png

Add constraints to the Price field of 8 from the left, top, and 0 from the right. Set height, and add constraints.

Add constraints to the Details field of 0 from the left and right, 8 from the top, and set height = 60 and add constraints. The details field is a little taller because it may hold more text.

Next we are going to add a file called CustomTextField that is available in the resources, or you can create a new file in the View group of Cocoa Touch Class, name it CustomTextField and set subclass to UITextField and paste the following code into it.

import UIKit
/** extension to UIColor to allow setting the color
 value by hex value */
extension UIColor {
    convenience init(red: Int, green: Int, blue: Int) {
        /** Verify that we have valid values */
        assert(red >= 0 && red <= 255, "Invalid red component")
        assert(green >= 0 && green <= 255, "Invalid green component")
        assert(blue >= 0 && blue <= 255, "Invalid blue component")

        self.init(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: 1.0)
    }

    /** Initializes and sets color by hex value */
    convenience init(netHex:Int) {
        self.init(red:(netHex >> 16) & 0xff, green:(netHex >> 8) & 0xff, blue:netHex & 0xff)
    }

}

@IBDesignable
class CustomTextField: UITextField {


    // MARK: - IBInspectable
    @IBInspectable var tintCol: UIColor = UIColor(netHex: 0x707070)
    @IBInspectable var fontCol: UIColor = UIColor(netHex: 0x707070)
    @IBInspectable var shadowCol: UIColor = UIColor(netHex: 0x707070)


    // MARK: - Properties
    var textFont = UIFont(name: "Helvetica Neue", size: 14.0)

    override func draw(_ rect: CGRect) {
        self.layer.masksToBounds = false
        self.backgroundColor = UIColor(red: 230, green: 230, blue: 230)
        self.layer.cornerRadius = 3.0
        self.tintColor = tintCol
        self.textColor = fontCol
        self.layer.borderWidth = 1
        self.layer.borderColor = UIColor(red: 255, green: 255, blue: 255).cgColor

        if let phText = self.placeholder {
            self.attributedPlaceholder = NSAttributedString(string: phText, attributes: [NSForegroundColorAttributeName: UIColor(netHex: 0xB3B3B3)])
        }

        if let fnt = textFont {
            self.font = fnt
        } else {
            self.font = UIFont(name: "Helvetica Neue", size: 14.0)
        }
    }

    // Placeholder text
    override func textRect(forBounds bounds: CGRect) -> CGRect {
        return bounds.insetBy(dx: 10, dy: 0)
    }

    // Editable text
    override func editingRect(forBounds bounds: CGRect) -> CGRect {
        return bounds.insetBy(dx: 10, dy: 0)
    }

}

I'm not going to go over this file in detail, simply because it is outside the scope of our purpose in this chapter which is learning CoreData. Also, we already went over a styling section in detail for the MaterialView file. But basically, it just supplies some nice styling to text fields.

So select all three text fields, and go to the Identity Inspector, and change the Class to CustomTextField. And then you will see the Storyboard rebuild, and you will see some subtle shifts in the text field appearance. The text is slightly indented, and the corners are slightly rounded.

Figure 5.6.55 Figure 7.55.png

Now for the bottom half of the screen we need a label, picker view, and a button. Lets start from the bottom with the button. Add the following constraints, 0 from the left, bottom, and right. And set the height to 40. Click Add constraints.

Figure 5.6.56

Figure 7.56.png

Change the font to Helvetica Neue, bump up the text size to 20 with style of Medium. Change the font color to light gray, and the background color to dark gray. Change the button title to Save Item

Figure 5.6.57 Figure 7.57.png

For the picker view, set constraints with Constrain to margins checked, 0 from the right and left, 8 from the bottom and a height of 216 is fine. And set margins.

Figure 5.6.58

Figure 7.58.png

Finally set the text of the label to Select Store, change the color to dark gray, change the font to Helvetica Neue, and center the label horizontally with the alignment constraint. Pin it 8 for the bottom and set height and width.

Figure 5.6.59 Figure 7.59.png

Now let's work on the menu. From the object library, search for navigation item and drag it into the top of the View Controller. Change the title to Add/Edit.

Figure 5.6.60 Figure 7.60.png

Next we need a way to delete entries, so drag in a Bar Button Item from the object library, change the System item to Trash and the Tint to a red color.

Figure 5.6.61 Figure 7.61.png

Now go ahead and run it and make sure the segue from the plus button is working to take you to the new screen. The Table View segue wont work quite yet. You should see the following:

Figure 5.6.62

Figure 7.62.png

It's looking pretty good... except I don't like that ugly blue back button with the prior VC title. So lets get rid of that. To fix the blue tint, select the Navigation Bar in the Navigation Controller Scene VC and set the Tint to Dark Gray Color.

Figure 5.6.63 Figure 7.63.png

Then open the ItemDetailsVC.swift file and modify the viewDidLoad() function as follows:

 override func viewDidLoad() {
        super.viewDidLoad()

        if let topItem = self.navigationController?.navigationBar.topItem {
            topItem.backBarButtonItem = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.plain, target: nil, action: nil)
        }
    }

All we are doing here, is programmatically changing the title of the existing backBarButtonItem to not have a title, leaving just the back arrow. It should look like this:

Figure 5.6.64

Figure 7.64.png

Now lets hook up all our UI elements to the code. Open the ItemDetailsVC.swift file and delete the didReceiveMemoryWarning() function and any commented code. Then right below the class declaration add the following code:

    @IBOutlet weak var storePicker: UIPickerView!
    @IBOutlet weak var titleField: CustomTextField!
    @IBOutlet weak var PriceField: CustomTextField!
    @IBOutlet weak var detailsField: CustomTextField!
    @IBOutlet weak var thumgImg: UIImageView!

Then below the rest of the functions add the IBAction for the Save Post button, the Add Picture button and the Trash button.

@IBAction func savePressed(_ sender: UIButton) {

    }

@IBAction func addImage(_ sender: UIButton) {

   }

@IBAction func deletePressed(_ sender: UIBarButtonItem) {

   }

Head back to the Storyboard and right click the View Controller Add/Edit with the yellow circle, and drag it to hook up the IBOutlets created in code for the UI elements as shown in below figures. For the buttons, you will be asked to select a type of event, choose Touch Up Inside.

Figure 5.6.65 Figure 7.65.png

Figure 5.6.66 Figure 7.66.png

Picker View

Now we have everything hooked up to our code, lets get to work on making that Picker View work. There is more than one way to work with CoreData, you don’t have to only use an NSFetchedResultsController. You can use regular Yable Views, or you can simply save data into arrays and display them in a Picker View like we are going to do here.

So first off, like Table Views, Picker View has protocols and methods we must implement. So modify the class declaration as follows:

class ItemDetailsVC: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate {

Then just like we do with table views, add the following to viewDidLoad:

storePicker.delegate = self
storePicker.dataSource = self

And before we forget, go ahead and add import CoreData to your file. Next, we are going to create an array of objects of entity Store eventually, so lets create that variable now, underneath the IBOutlets:

var stores = [Store]()

Now we need to add the methods to conform with the protocols and those are as follows:

    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {

        let store = stores[row]
        return store.name
    }

This function is in charge of displaying the row information. So we create a store from our array of stores that corresponds to the picker row, then display the name of that store at that specific row. And remember, the store.name comes from the attribute that you created in the .xcdatamodelid at the very very beginning.

    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return stores.count
    }

This method simply counts the number of objects in the stores array and returns that number to be the number of rows in the Picker View.

    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }

This method determines how many columns there are in the Picker View. For instance if you have a date picker, you'll usually have a few columns for month, day, year. In that case you would return 3.

So lets create some stores so that we can populate the Picker View. Create a function down at the bottom of your file as follows:

func generateStores() {

                let store = Store(context: context)
                store.name = "Best Buy"
                let store2 = Store(context: context)
                store2.name = "Tesla Dealership"
                let store3 = Store(context: context)
                store3.name = "Frys Electronics"
                let store4 = Store(context: context)
                store4.name = "Target"
                let store5 = Store(context: context)
                store5.name = "Amazon"
                let store6 = Store(context: context)
                store6.name = "K Mart"

                ad.saveContext()
    }

Just like we did with the test data for the items in MainVC.swift, we create store objects of entity Store, insert them into the context, then assign values to the store.name attribute. Then most importantly, we call the built in saveContext() function that is found in the app delegate.

We need a way to fetch those object we just saved to core data, so create the following function:

func getStores() {

        let fetchRequest: NSFetchRequest<Store> = Store.fetchRequest()

        do {

            self.stores = try context.fetch(fetchRequest)
            self.storePicker.reloadAllComponents()

        } catch {

            // handle error
        }
    }
    ```

This is a little different from what we saw with the **FetchedResultsController**, so lets break it down.
We create a `fetchRequest` which we’ve seen before.
Then since a fetch request can fail, we have our **do-catch** block.
We are directly assigning the result of the fetch to the stores array in this line `self.stores = try context.fetch(fetchRequest)`.

Then we tell the store picker to `reloadAllComponents`. This is analogous to the Table View `reloadData` function.
When changes have been made to the **Picker View**, it will reload and show the data. If there is an error, you can write some code to handle it.

Next add those two functions to `viewDidLoad` and run it!

```javascript
        generateStores()
        getStores()

Once you have run it once, go ahead and comment out the generateStores() function in viewDidLoad or it will continually add those stores to the stores array and you’re Picker View will be really long!

Figure 5.6.67

Figure 7.67.png

Saving an Item

We are now ready to implement the Save Item button. So let's think about what we need to have happen for that to work. First we are going to create an item of entity type Item, then we are going to check to see if there is anything written in each of the text fields. If there is, we are going to assign the item attributes to those values. To assign the store for the item, we need to use the relationship to the store, so we say item.store is equal to the store that is picked.

At this point we need to clarify something. We are not assigning the name of the store we picked to the item. We are connecting two independent entities to each other. If you wanted to access the name of the store we just associated with the item (which we will do later), you would need to say something like item.store.name, or in other words, get the name (attribute) of the store (entity) that is associated with this item (entity).

Then we’ll save it and segue back to initial screen. So here it goes, modify the savePressed function as follows:

    @IBAction func savePressed(_ sender: UIButton) {

        let item = Item(context: context)

        if let title = titleField.text {
            item.title = title
        }

        if let price = priceField.text {
            item.price = Double(price)!
        }

        if let details = detailsField.text {
            item.details = details
        }

        item.store = stores[storePicker.selectedRow(inComponent: 0)]

        ad.saveContext()

        _ = navigationController?.popViewController(animated: true)
    }

Now that should work! Run it! Add an item, save it, and watch it pop up in the Table View!

Edit existing items

We can already add new items. But now we want to make it so we can edit existing items by clicking on them and changing their values. So what we are going to need is a way to pass the item that was clicked on in the first screen, to pass that item to the second screen for editing. We'll create a function that loads the items information into the second screen.

So first, we're going to create a variable in ItemDetailsVC called var itemToEdit: Item? and place it right under the stores array variable. We are saying it is optional, because a user could create a new item via the + button instead of editing an item. So we will do a check in viewDidLoad() to see if there actually is an item to edit.

So in viewDidLoad add the following:

        if itemToEdit != nil {
            loadItemData()
        }

And if you don't remember creating a function called loadItemData you would be correct, so lets create it now, beneath the savePressed function.

    func loadItemData() {

        if let item = itemToEdit {

            titleField.text = item.title
            priceField.text = "\(item.price)"
            detailsField.text = item.details


            if let store = item.store {

                var index = 0
                repeat {

                    let s = stores[index]
                    if s.name == store.name {

                        storePicker.selectRow(index, inComponent: 0, animated: false)
                        break
                    }
                    index += 1

                } while (index < stores.count)
            }
        }
    }

We have checked in viewDidLoad to make sure that there is an itemToEdit, meaning that we can be sure we have arrived at this screen by selecting an item from the Table View.

So what we want to happen when we call this function, is load into the text fields and set the picker view to the data of that item, so they can then be edited as desired. The text fields are pretty straight forward. All we have to do is retrieve the values of the attributes of the item and set the text fields to those values. Getting the title of the store is bit trickier.

What we do is create a repeat while loop that loops through the stores in our stores array, and compares the name of the store of our item with that in the stores array. If it matches, it grabs the index and sets the storePicker row to that index.

Now we're ready to receive the item in ItemDetailsVC from the MainVC screen. We start by implementing the didSelectRowAt method in MainVC. Be careful when you get this function, many many people accidentally grab the didDEselectRowAt. So add the following function under the cellForRowAt function:

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        if let objs = controller.fetchedObjects , objs.count > 0 {
            let item = objs[indexPath.row]
            performSegue(withIdentifier: "ItemDetailsVC", sender: item)
        }
    }

This method is called whenever the user taps on a cell. So what we are doing, is checking first to make sure that there is in fact an object in the fetchedObjects. That way we don't get a crash. Then we're assigning the item object at the row that is connected to the constant item. Next we are going to perform the segue, and send item.

To actually send item, we need to use another function called prepareForSegue:

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "ItemDetailsVC" {
            if let destination = segue.destination as? ItemDetailsVC {
                if let item = sender as? Item {
                    destination.itemToEdit = item
                }
            }
        }
    }

So lets break this one down. This function is called in preparation to change screens. So the first thing we do is check which segue we are going to use, then we assign what the destination screen is going to be. Next we assign what the item we are sending is. And lastly we assign the item we are sending to the variable in the destination View Controller that it will be assigned to.

So that inner most assignment is saying, “that variable over in the next screen called itemToEdit, we’re assigning the item we created in this screen to that one.”

Let’s run it and make sure everything we have written so far is working. What we should expect to see is that when we click on an item, it will segue to the next screen and fill in the text fields and select the correct store on the picker view. You can then edit the text fields and store selection, save it, and it will update the table view accordingly! Pretty cool!

But there is a catch, it didn't actually update the existing entry, it simply created a new entry with the updated info, so lets fix that.

What we need to do in the savePressed function is implement another check as to whether we are saving a new item, or saving an edited item. At the top of the function replace let item = Item(context: context)

with:

        var item: Item!

        if itemToEdit == nil {

            item = Item(context: context)

        } else {

            item = itemToEdit

        }

Here we are declaring an item of entity type Item. Then we do a check to see if we have received an item from the first screen to edit. If not, then we continue as before, creating a new item in the context and saving it. However, if we have an item to edit, all we do is set the item we declared equal to that item. Then CoreData actually knows what to do with it and will update the cell accordingly when we press save! Pretty cool right? And that is part of the big didChangeFunction in MainVC where we check for .update changes.

Deleting an Item

Now that we can add and edit, its time to delete. Make sure you’re still in the ItemDetailsVC.swft file and go to the deletePressed function and modify it as follows:

    @IBAction func deletePressed(_ sender: UIBarButtonItem) {

        if itemToEdit != nil {
            context.delete(itemToEdit!)
            ad.saveContext()
        }

        _ = navigationController?.popViewController(animated: true)
    }

How easy is that?? All we have to do is check that we have an itemToDelete. Then say context.delete() and pass in the item to be deleted and save. That is the power of using the NSFetchedResultsController with CoreData. Imagine if you had to write all the code to do that yourself.

So try and run it, select an existing item and try to delete it. It should pop you back to the table view and watch it disappear.

Adding Images

Lets get to adding images. Just like we did with the myHood app we are going to start out by adding the necessary protocols for working with UIImagePickers.
So add the following to your class declaration:

class ItemDetailsVC: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource, UIImagePickerControllerDelegate, UINavigationControllerDelegate {

Then declare a variable for our imagePicker along with the stores array and itemToEdit called: var imagePicker: UIImagePickerController! The UIImagePickerController is a class that manages taking pictures and video, and accessing user media. Next in the viewDidLoad instantiate the imagePicker and set the delegate.

            imagePicker = UIImagePickerController()
            imagePicker.delegate = self

Now we need to implement the imagePickerController method as follows, you can add this at the bottom of the file:

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {

        if let img = info[UIImagePickerControllerOriginalImage] as? UIImage {

            thumgImg.image = img
        }

        imagePicker.dismiss(animated: true, completion: nil)
    }

This method tells the delegate that the user picked a still image or movie. Next we grab the picked image and set it equal to the thumbnail image, and then dismiss the picker view.

Now we are ready to present the imagePickerController when the addImage button is pressed. So modify the addImage function as follows:

    @IBAction func addImage(_ sender: UIButton) {
        present(imagePicker, animated: true, completion: nil)
    }

Last thing we need to do before we can just test that the imagePicker is working is provide permissions in the info.plist.

Open the info.plist from the left hand pane and in the last entry, when you hover over there should be a + sign that pops up. Click on it and type Privacy. You should get some auto completed entries, and we are looking for "Privacy - Photo Library Usage Description". On the right there is space available to enter a message to the user why you would like to access their photos. Say something like "DreamLister needs to access your photos."

Figure 5.6.68 Figure 7.68.png

Go ahead and run it, and verify that when you click the "Add Pic" button, you are asked to allow access to photos. Then when you click a photo, it returns to the ItemDetailsVC and the image you selected is now displayed as seen in following figure:

Figure 5.6.69

Figure 7.69.png

(the waterfall is from Amazon…. get it?)

Next thing we need to do is save the image to CoreData. So go to the savePressed function and create a new Image entity. Then set the image attribute of that entity equal to the image we just selected. Add the following under var item: Item!

        let picture = Image(context: context)
        picture.image = thumgImg.image

So we are creating picture of entity Image then setting the attribute of image to the thumbnail image we picked using our ImagePicker.

Right below we do our check to see whether we are editing or creating a new entity:

        if itemToEdit == nil {

            item = Item(context: context)

        } else {

            item = itemToEdit

        }

add:

     item.image = picture

And again, just reiterating the fact that here we are associating two independent entities. And when we access the image to load it upon editing, we do it as seen in the loadItemData() function by adding:

swift
thumgImg.image = item.image?.image as? UIImage

When an edited cell is loaded, we simply access the image entity associated with our item, grab the image out of it and set it equal to the thumbnail image.

And finally, to enable the images being set in the Table View, let's modify the ItemCell.swift configureCell function as follows, just add:

        thumb.image = item.image?.image as? UIImage

Now if you run it, you will see that any existing test data or entries you had will not have any images in the Table View. That's because there had not been any image associated with the item before. But now you should be able to either edit one of the existing cells, or add a new one and add an image. It should work! And if you don’t add an image, it will use the placeholder image instead.

Sorting

Next up is sorting. Open the MainVC.swift file and find your attemptFetch() function. We currently have only one SortDescriptor, which compared the created attributes. We want to add a few more in order to sort by price and alphabetically. So right under the current SortDescriptor add these two:

        let priceSort = NSSortDescriptor(key: "price", ascending: true)
        let titleSort = NSSortDescriptor(key: "title", ascending: true)

Then we need to write some logic to determine which SortDescriptor to use when the segmented controller is changed. Replace the fetchRequest.sortDescriptors = [dateSort] with:

        if segment.selectedSegmentIndex == 0 {

            fetchRequest.sortDescriptors = [dateSort]

        } else if segment.selectedSegmentIndex == 1 {

            fetchRequest.sortDescriptors = [priceSort]

        } else if segment.selectedSegmentIndex == 2 {

            fetchRequest.sortDescriptors = [titleSort]
        }

All we're doing here is checking which segment is selected, and depending on which one is selected, we'll apply a different sortDescriptor.

But the problem is, we don't currently have any way of knowing when a user chooses a different sort method. So we need to add a listener for that. Add the following IBAction below the attemptFetch function:

    @IBAction func segmentChange(_ sender: AnyObject) {

        attemptFetch()
        tableView.reloadData()

    }

Go into Storyboard and right-click the MainVC View Controller and hook up the segmented control. When you do, it is going to ask what type of event to select, and we want Value changed. What this action does, is listen for whenever a different segment is selected, then runs the code inside the method. In this case, we are going to run the attemptFetch function. This will reload the CoreData, then we will reload the tableView. Give it a try!

Add some items that are different in price and name, and see how the sorting goes. Play with the ascending value in the SortDescriptor declaration to learn how it works.

Wrapping up

And that's a wrap for DreamLister! This has been a huge section and we have gone over a LOT. We have learned all about core data, NSFetched Results Controller, picker views, sorting, and tons more.

Exercise

I am going to leave you with a final challenge, and that is to implement ItemType. Come up with a way to assign an item type and then sort by item type. Possibly a second picker view that has item types such as electronics, games, etc. Then like you did with store, you would save that item entity and associate it with a specific item. Happy coding!