DPS923 MAP523 notes – Wed Mar 23 2016

Classes that describe real-world objects

Before working more with Core Data, we will briefly review how to design and write a custom class that describes a real-world object.

Entity classes use properties for data/state storage, and methods for behaviour. Similar to other languages and frameworks.

In our apps, we work with objects and collections of objects, similar to what you’d do in other languages and frameworks.

What about persistence? How do we persist the object graph? Well, Cocoa offers at least two techniques, archiving, and the Core Data framework, which we study next.

We should note that small and simple object graphs can be persisted in a plist, “property list”. However, this kind of storage is perhaps more suited to, for example, configuration settings, and is usually inadequate for persisting the app’s data. When you have related objects, do NOT use a plist.

The result will be a class that is much more pleasurable to use when writing code.

What is a “managed object”?

“NSManagedObject is a generic class that implements all the basic behavior required of a Core Data model object.” (From the NSManagedObject class reference document.)

NSManagedObject inherits from NSObject.

In addition, we normally will use Xcode to create a custom class for an entity, which inherits from NSManagedObject. For example, if we created a “Person” entity, and generated a custom class, the inheritance hierarchy would look like this:

NSObject > NSManagedObject > Person

What is the “managed object context”?

“An instance of NSManagedObjectContext represents a single “object space” or scratch pad in an application. Its primary responsibility is to manage a collection of managed objects. The context is a powerful object with a central role in the life-cycle of managed objects, with responsibilities from life-cycle management … to validation, inverse relationship handling, and undo/redo.” (From the NSManagedObjectContext class reference document.)

Think of it as an in-memory “scratch pad” or temporary “work area” that you use.

What ‘management’ tasks can I perform on my objects?

All the tasks you would expect:

Fetch: Get all, or get one, or get some filtered, or get a scalar value (e.g. the number of objects). Done with a “fetch request”, introduced below.

Add: Add new object.

Edit: Edit an existing object.

Remove: Remove an existing object.

These tasks are implemented as methods in the Model class.

Where is the data (object graph) persisted?

In your app’s “Documents” directory.

The Core Data stack manages access to the store file. We don’t have to worry about it.

The data format of the store file is private, and is NOT important to us. We repeat, it is NOT important to us. Understood?

What’s a “fetched results controller”?

A wonderful and awesome object.

“You use a fetched results controller to efficiently manage the results returned from a Core Data fetch request to provide data for a UITableView object.” (From the NSFetchedResultsController class reference document.)

Are you planning to use a table view? Then you will want to ‘bind’ it to a fetched results controller. The result? Happiness.

If we have only one entity in our project, we create one fetched results controller.

If we have multiple entities in our project, we create a fetched results controller for each entity.

They are created in our Model class, as properties, with custom getters.

How do I…

How do I define an entity object?
Use the Core Data model editor.
Add an entity, and then add and configure properties.
Finally, use Xcode to generate a custom class for the entity.

How do I write code to manage the entity?
Do most of your work in the Model class.
Create a property for the entity’s fetched results controller.
Then write methods for other fetch requests, and for handling object creation, modification, and removal.

How do I perform searches and handle results?
You can use the fetch request object in the fetched results controller if you plan to ‘bind’ the results to a table view.
Alternatively, you can use a fetch request object to do so.
In either case, results are available as an NSArray of zero-or-more objects.

How do I add, edit, and remove objects?
As noted above, write methods that handle object creation, modification, and removal in the Model class.
For ‘add’, create and configure a new object.
For ‘edit’, fetch the object. Then change its property values.
For ‘remove’, fetch the object. Then ask the context to remove it.
Always “save changes”.

Can I study a code example?

Yes.

The CanadaAdd code example (in the GitHub repository) is a good example of an app that works with one entity.

You should attempt to re-create this example, using your own copy of the ClassesV2 template. Strongly recommended.

Show me a diagram of the objects in an iOS app that uses Core Data

In the style that we have been using in class, here is a diagram. (Click to open it full-size in a new tab/window.)

The ‘add/edit item’ pattern

At this point in time, you have some knowledge of and experience with:

navigation-based app style

table view

model object

persistent store, using Core Data

What if you want to add new items to your app?

New items can be added from a number of different sources:

a data entry view that you create as part of the app

a select list with items fetched from the network

on-devices sources, including the camera

How is this done? Using the add/edit item pattern. A central feature of this pattern is the modal view concept.

What is a modal view? It is a view “that provides self-contained functionality in the context of the current task or workflow.” Continuing from the Apple iOS Human Interface Guidelines document:

A modal view:

Can occupy the entire screen, the entire area of a parent view (such as a popover), or a portion of the screen

Contains the text and controls that are necessary to complete the task

Usually displays a button that completes the task and dismisses the view and a Cancel button that abandons the task and dismisses the view

Use a modal view when you need to offer the ability to accomplish a self-contained task related to your app’s primary function. A modal view is especially appropriate for a multistep subtask that requires UI elements that don’t belong in the main app UI all the time.

Modal views are often used in iOS apps for these situations:

The user is completing a fill-in form that has user interface controls (an “add item” pattern)

The user must select one or more items from a lengthy list (a “select item(s)” pattern)

Here is the design and coding approach:

Create a controller

Add code that declares a protocol (with at least one method), and add a delegate property to the controller

Add a new scene to the storyboard (and embed it in a nav controller)

Alternate between the scene and controller code to build the user interface and handle events

Notice that this protocol has only one method. You can declare as many methods as you need.

The implementation code – the tasks that the method actually performs – is written in the class that adopts the protocol.

Next, define a “delegate” property for the controller class.

var delegate: EditItemDelegate?

Add a new scene to the storyboard (and embed it in a nav controller)

On the storyboard, add a new View Controller scene from the object library. On the Editor menu, embed it in a navigation controller.

On the Identity inspector, set its Custom Class property to be the just-added ProvinceEdit class.

Also, in the “Identity” section (just below the Custom Class section), enter a string for the “Storyboard ID”. A suggested name is “AddEditProvince”.

Set the scene’s nav bar title.

Add the user interface objects. (For this app, we need three text fields, and a date picker.)

Add two ‘bar button item’ controls – one for ‘Cancel’ (left side), and the other for ‘Save’ (right side).

On the storyboard, create a segue from the presenter scene (ProvinceList) to the new scene. Here’s how:

Add a ‘bar button item’ control to the presenter scene’s nav bar, right side

Control+click-drag from the control to the new scene

On the segue popup, choose ‘modal’, then add an identifier (e.g. ‘toProvinceEdit’)

.

Alternate between the scene and controller code to build the user interface and handle events

As you have done before, alternate your work between the scene and controller code to build the user interface, and handle events. Add outlets, and actions for ‘cancel’ and ‘save’.

.

In the ‘add item’ controller, add code that packages the user input, and call the delegate method

Add properties for the model object, and an entity object:

var model: Model! // will be configured by the presenting controller
var detailItem: Province? // not used in this example, but will be used in the future

In the ‘cancel’ method, we simply call back into the delegate method, and pass nil as an object.

In the ‘save’ method, your code will package the user input, and call the delegate method, passing along the user input package.

You can decide on the packaging format. If you have a single string or number item, just send that along. If you have multiple items to send, package them into an NSDictionary and/or an NSArray object. In this situation, package it into a new ‘Province’ object.

In the presenting controller, adopt the protocol, code the segue, and implement the delegate method(s)

In the presenting controller’s declaration, adopt the new ‘add item’ protocol.

Code the segue.

Add a method (defined by NSFetchedResultsControllerDelegate) that will respond to changes in the ‘fetchedObjects’ result set. The method is controllerDidChangeContent().

Then, implement the delegate method(s).

Finally, dismiss the ‘add item’ controller.

Standalone fetch request

A fetched results controller object is used with a table view.

If you are not using a table view, how do you query the on-device data store?

With a fetch request object.

Previously, you have learned that a fetched results controller includes a fetch request property.

You can use a fetch request object to perform a get-all, get-some-filtered, or get-one query. For all of these situations, the results come back as an array that has zero or more objects.

A get-all query will not need a predicate.

A get-some-filtered or get-one query will need a predicate. Predicate string format examples are fully documented here (although some will not work with a Core Data store).

For any query that will return a collection, you can choose to configure sort behaviour, if you wish.

Follow this guidance to code your first standalone fetch request. In this scenario, we are fetching a ‘Sport’ object that’s in the Winter 2015 programming Assignment 1. We are using a get-one predicate, so the fetched results will be an array with exactly one object in it.

For best results, in your Model class, create a method that will accept a fetch request argument, execute the fetch request, and deliver the results. (We do this because controllers do not have direct access to the Core Data stack.)

Here’s how to create a fetch request object:

let f = NSFetchRequest(entityName: "Sport")

Next, ask yourself whether you need a predicate, or sorting.

In our situation, we do need a predicate. We can configure it like this: