Chapter    3

A Super Start: Adding, Displaying, and Deleting Data

Well, if that last chapter didn’t scare you off, then you’re ready to dive in and move beyond the basic template you explored in Chapter 2.

In this chapter, you’re going to create an application designed to track some superhero data. Your application will start with the Master-Detail Application template, though you’ll be making lots of changes right from the beginning. You’ll use the model editor to design your superhero entity. Then you’ll create a new controller class derived from UIViewController that will allow you to add, display, and delete superheroes. In Chapter 4, you’ll extend your application further and add code to allow the user to edit her superhero data.

Take a look at Figure 3-1 to get a sense of what your app will look like when it runs. It looks a lot like the template app. The major differences lie in the entity at the heart of the application and in the addition of a tab bar at the bottom of the screen. Let’s get to work.

9781430238072_Fig03-01.jpg

Figure 3-1.  The SuperDB application as it will look once you’ve finished this chapter

Setting up the Xcode Project

Time to get your hands dirty. Launch Xcode if it’s not open, and bring up your old friend, the new project assistant (Figure 3-2).

9781430238072_Fig03-02.jpg

Figure 3-2.  Your dear old friend, Xcode’s new project assistant

In the last chapter, you started with the Master-Detail Application template. When you’re creating your own navigation applications, it’s a good template to use as it gives you a lot of the code you’re likely to need in your application. However, to make it easier to explain where to add or modify code and also to reinforce your understanding of how applications are constructed, you’re going to build the SuperDB application from scratch, just as you did throughout most of Beginning iOS 6 Development by David Mark, Jack Nutting, and Jeff LaMarche (Apress).

Select Empty Application, and click Next. When prompted for a product name (Figure 3-3), type in SuperDB. Select iPhone for the device family, and make sure that the “Use Core Data” and “Use Automatic Reference Counting” checkboxes are checked. After clicking Next again, use the default location to save the project and click Create.

9781430238072_Fig03-03.jpg

Figure 3-3.  Entering project details

You need to make some changes to use storyboards. Storyboards were a new feature added to Xcode with iOS 5. They make managing user interfaces and the transitions between those interface much easier. Xcode provides a visual editor, the storyboard editor, to aid in the layout and design of user interfaces. Where Interface Builder let you work with one view at a time, storyboards let you work the entire set of views and their behavior. By default, the Empty Application template did not give you a storyboard. This is easy enough to fix.

First, create a new storyboard file. Select the SuperDB group in the Navigator pane, and create a new file (type imageN or use the menu File image New image File). When the New File Assistant appears (Figure 3-4), select User Interface from under the iOS heading in the left pane, then select Storyboard from the right pane, and click the Next button. Choose iPhone as the device on the next assistant screen, and click Next again. Name the file SuperDB.storyboard, and save it in the project folder (Figure 3-6). The new file, SuperDB.storyboard, should appear in the Navigator pane.

9781430238072_Fig03-04.jpg

Figure 3-4.  Create a new storyboard with the New File Assistant

9781430238072_Fig03-05.jpg

Figure 3-5.   Select the iPhone device

9781430238072_Fig03-06.jpg

Figure 3-6.  Name the storyboard and save it

Finally, you need to tell Xcode that you want to use the new SuperDB.storyboard file. Select the SuperDB project at the top of the Navigator pane. When the project editor appears, select the SuperDB Target and go to the project summary editor (Figure 3-7). In the section titled iPhone/iPod Deployment Info, enter the name SuperDB into the Main Storyboard field.

9781430238072_Fig03-07.jpg

Figure 3-7.  Project editor

Since you have configured SuperDB to use a storyboard, you need to clean up the application delegate. Select AppDelegate.m in the Navigator pane. The code editor should appear. Find the method

- (BOOL)application:(UIApplication *)application
         didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

and edit it to read

-(BOOL)application:(UIApplication *)application
        didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
        return YES;
}

Now you need to actually create your storyboard. Find and select SuperDB.storyboard in the Navigation pane. The editor pane should transform into the storyboard editor (Figure 3-8). Let’s quickly review the storyboard editor. The content of the editor is a grid, similar to the model editor graph style. On the bottom left of the storyboard editor is a disclosure button. The arrow in the button should be pointing to the right. Click it.

9781430238072_Fig03-08.jpg

Figure 3-8.  The storyboard editor

The storyboard document outline (Figure 3-9) should appear on the left of the storyboard editor. Right now this view is empty with no scenes (we’ll define scenes in a little bit). Normally, the storyboard document outline provides a hierarchical view of the scenes, their view controllers, views and UI components.

9781430238072_Fig03-09.jpg

Figure 3-9.  The storyboard document outline and Disclosure button

There is a pair of button groups on the bottom right of the editor (Figure 3-10). The button group on the left is for configuring auto layout of your views; we’ll describe them in Chapter 4. The button group on the right has three buttons: Zoom Out, Zoom-to-Fit, and Zoom In. They change the zoom level of the storyboard editor.

9781430238072_Fig03-10.jpg

Figure 3-10.  Storyboard editor button groups

Adding a Scene

Reset the Xcode workspace so that the storyboard editor looks like Figure 3-8. Open the Utility pane, and find the navigation controller in the Object library (which should be at the bottom of the Utility pane). Drag the navigation controller over to the storyboard editor. Your storyboard editor should look something like Figure 3-11.

9781430238072_Fig03-11.jpg

Figure 3-11.  Storyboard editor with a navigation controller

Scenes and Segues

Interestingly, Xcode decided that along with the navigation controller, you wanted a table view controller and set it up. Click the Zoom-To-Fit (=) button on the lower right to fit everything in the storyboard editor. What you see now are two scenes, labeled Navigation Controller and Table View Controller - Root. Between the two scenes is a segue. It’s the arrow pointing from the Navigation Controller to the Table View Controller - Root. It has an icon in the middle of it that tells you this is a manual segue.

A scene is basically a view controller. The leftmost scene is labeled Navigation Controller, rightmost is the Table View Controller - Root. The navigation controller is used to manage the other view controllers. In Chapter 2, the navigation controller managed the master and detail view controllers. The navigation controller also provided the navigation bar that allowed you to edit and add events in the master view controller and provided the Back button in the detail view controller.

A segue defines the transition from a scene to the next scene. In the application from Chapter 2, when you selected an event in the master view controller, you triggered the segue to transition to the detail view controller.

But what about the arrow to the left of the navigation controller? That simply tells you the navigation controller is the root view controller for this storyboard.

Storyboard Document Outline

Now that you have something in your storyboard editor, let’s take a look at the storyboard document outline. Open it up. Now you can see the hierarchical view of the scenes described earlier (Figure 3-12), with their view controllers, views, and UI components.

9781430238072_Fig03-12.jpg

Figure 3-12.  The story document outline, populated

Let’s take a look at your work so far. Build and run the SuperDB app.  You should see something like Figure 3-13.

9781430238072_Fig03-13.jpg

Figure 3-13.  The SuperDB app so far

Application Architecture

There’s no single right architecture for every application. One obvious approach would be to make the application a tabbed application, and then add a separate navigation controller for each tab. In a situation where each tab corresponds to a completely different view showing different types of data, this approach would make perfect sense. In Chapter 7 of Beginning iOS 6 Development, you used that exact approach because every single tab corresponded to a different view controller with different outlets and different actions.

In this case, however, you’re going to implement two tabs (with more to be added in later chapters), but each tab will show exactly the same data, just ordered differently. When one tab is selected, the table will be ordered by the superhero’s name. If the other tab is selected, the same data will be shown but ordered by the superhero’s secret identity.

Regardless of which tab is selected, tapping a row on the table will do the same thing: drill down to a new view where you can edit the information about the superhero you selected (which you will add in the next chapter). Regardless of which tab is selected, tapping the Add button will add a new instance of the same entity. When you drill down to another view to view or edit a hero, the tabs are no longer relevant.

For your application, the tab bar is just modifying the way the data in a single table is presented. There’s no need for it to actually swap in and out other view controllers. Why have multiple navigation controller instances all managing identical sets of data and responding the same way to touches? Why not just use one table controller and have it change the way it presents the data based on which tab is selected? This is the approach you’re going to take in this application. As a result, your application won’t be a true tabbed application.

Your root view controller will be a navigation controller, and you’ll use a tab bar purely to receive input from the user. The end result that is shown to the user will be identical to what they’d see if you created separate navigation controllers and table view controllers for each tab. Behind the scenes you’ll be using less memory and won’t have to worry about keeping the different navigation controllers in sync with each other.

Your application’s root view controller will be an instance of UINavigationController. You’ll create your own custom view controller class, HeroListController, to act as the root view controller for this UINavigationController. HeroListController will display the list of superheroes along with the tabs that control how the heroes are displayed and ordered.

Here’s how the app will work. When the application starts, an instance of HeroListController is loaded from the Storyboard file. Then an instance of UINavigationController is created with the HeroListController instance as its root view controller. Finally, the UINavigationController is set as the application’s root view controller.  The view associated with the HeroListController contains your tab bar and your superhero table view.

In Chapter 4, you’ll add a table view controller into the mix to implement a detail superhero view. When the user taps on a superhero in the superhero list, this detail controller will be pushed onto the navigation stack and its view will temporarily replace the HeroListController’s view in the UINavigationController’s content view. No need to worry about the detail view now; we just wanted you to see what’s coming.

Designing the View Controller Interface

Your application’s root view controller is now a stock UINavigationController. You didn’t need to write any code for it; you just dropped a navigation controller object into your storyboard. Xcode also gave you a UITableViewController as the root of the navigation controller’s stack. Even though you will be using a table to display the list of heroes, you’re not going to subclass UITableViewController. Because you also need to add a tab bar to your interface, you’re going to create a subclass of UIViewController and create your interface in the storyboard editor. The table that will display the list of heroes will be a subview of your view controller’s content pane.

If not already selected, select the SuperDB.storyboard in the Navigation pane. Also make sure the Utility pane is exposed. The storyboard should have two scenes: the Navigation Controller and the Table View Controller - Root. Zoom out so that both scenes are visible. Select the Table View Controller - Root. Only the Table View Controller - Root scene and label should be highlighted blue. Delete the table view controller by hitting the Delete key or Edit image Delete. You should now only have the navigation controller.

From the bottom of the Utility pane, select a view controller from the Object library and drag it to the storyboard editor. A view controller should appear; place it to the right of the navigation controller (Figure 3-14).

9781430238072_Fig03-14.jpg

Figure 3-14.  The storyboard with a new view controller scene

Before you lay out the new view controller, let’s connect it to the navigation controller. Select the navigation controller. Click the Zoom In button until the navigation controller changes to display three icons (Figure 3-15). Hover the pointer over the left-most icon. A pop-up window should appear with the words navigation controller. Control-drag from the navigation controller icon to the view of the view controller. When you release the pointer, you should see a pop-up menu of possible segue assignments (Figure 3-16). Select root view controller in the Relationship Segue section. You should see a segue appear between the navigation controller and the view controller. Also, the view controller should now have a navigation bar along the top.

9781430238072_Fig03-15.jpg

Figure 3-15.  Navigation controller label icons

9781430238072_Fig03-16.jpg

Figure 3-16.  Possible segue assignments

Now you can design the view controller’s interface. Let’s add the tab bar first. Look in the library for a Tab Bar. Make sure you’re grabbing a tab bar and not a tab bar controller. You only want the user interface item. Drag a tab bar from the library to the scene called View Controller, and place it snugly in the bottom of the window, as shown in Figure 3-17.

9781430238072_Fig03-17.jpg

Figure 3-17.  The tab bar placed snugly against the bottom of the scene

Note  You may need to zoom in to drop the tab bar into the view. You need to zoom in enough so the label changes from view controller to a series of icons. The bottom of Figure 3-17 shows this.

The default tab bar has two tabs, which is exactly the number you want. Let’s change the icon and label for each. With the tab bar still selected, click the star above Favorites. Then click the Attributes Inspector button in the Utility pane selector bar (it should be the fourth button).  Alternately, you can select View image Utilities image Show Attribute Inspector. The menu short cut is ⌥image4.

If you’ve correctly selected the tab bar item, the Attribute Inspector pane should say Tab Bar Item and the Identifier pop-up should say Favorites. In the Attribute Inspector, give this tab a title of By Name and an image of name_icon.png (Figure 3-18). Now click the three dots above the word More on the tab bar to select the right tab. Using the inspector, give this tab a title of By Secret Identity and an image of secret_icon.png.

9781430238072_Fig03-18.jpg

Figure 3-18.  Setting the attributes of the left tab

Note  The files name_icon.png and secret_icon.png can be found in the download archive for this book.

Back in the Object library, look for a table view. Again, make sure you’re getting the user interface element, not a table view controller. Drag this to the space above the tab bar. It should resize automatically to fit the space available. After you drop it, it should look like Figure 3-19.

9781430238072_Fig03-19.jpg

Figure 3-19.  The table view in the scene

Finally, grab a table view cell, and drag it on top of the table view. Xcode should automatically align it to the top the table view (Figure 3-20).

9781430238072_Fig03-20.jpg

Figure 3-20.  The table view cell on the table view

Select the table view cell, and expose the Attribute Inspector in the Utility pane. You need to change some of the attributes to get the behavior you want. First, set the style to Subtitle. This gives a table view cell with a large title text and a smaller subtitle text below the title. Next, give it an identifier value of HeroListCell. This value will be used later when creating table view cells. Finally, change the selection from Blue to None. This means when you tap on a table view cell, it won’t highlight. Your Attribute Inspector should look like Figure 3-21.

9781430238072_Fig03-21.jpg

Figure 3-21.  The table view cell attributes

Your interface is complete. Now you need to define your view controller interface in order to make the outlet, delegate, and datasource connections.

Creating HeroListController

Single-click the SuperDB group in the Navigator pane. Now create a new file (imageN or File image New image File). When the New File Assistant appears (Figure 3-22), select Cocoa Touch from under the iOS heading in the left pane, then select Objective-C class from the right pane, and click the Next button.

9781430238072_Fig03-22.jpg

Figure 3-22.  Selecting the Objective-C class template in the new file assistant

On the second file assistant pane (Figure 3-23), give the class a name of HeroListController and make it a subclass of UITableViewController. Make sure both the “Targeted for iPad” and “With XIB for user interface” items are unchecked. With that done, click the Next button. The file dialog should be selected to the SuperDB project folder, so just click Create. Two files should have been added to the project view: HeroListController.h and HeroListController.m.

9781430238072_Fig03-23.jpg

Figure 3-23.  Selecting the UITableViewController in the file assistant

Wait a minute. When you made the interface in MainStoryboard.storyboard, you used a plain UIViewController, not a UITableViewController. And we said earlier that you didn’t want to use a UITableViewController. So why did we have you make HeroListController a subclass of UITableViewController?

If you look back at Figure 3-1, you can see that your application displays a list of heroes in a table view. That table view will need a data source and delegate. The HeroListController will be that data source and delegate. By asking the New File Assistant to make a subclass of UITableViewController, Xcode will use a file template that will predefine a bunch of table view data source and delegate methods. Select HeroListController.m in the Navigator pane and take a look at the file in the Editor pane. You should see the methods Xcode gave you for free.

However, you do need to make the HeroListController a subclass of UIViewController. Single-click HeroListController.h in the Navigator pane. Find the @interface declaration, and change it from

@interface HeroListController : UITableViewController

to

@interface HeroListController : UIViewController <UITableViewDataSource, UITableViewDelegate>

Next, select HeroListController.m. Find the method

- (id)initWithStyle:(UITableViewStyle)style
{
          self = [super initWithStyle:style];
          if (self) {
          // Custom initialization
          }
          return self;
  }

and delete it.

Now you need to connect to the table view data source and delegate to the HeroListController. While you’re at it, create the outlets needed for the tab bar and table view. You could add them manually, but we assume you know how to do that. Let’s try using an alternate method.

Select SuperDB.storyboardin the Navigator pane and expose the storyboard editor. Make sure your zoom level is such that that view controller label shows the three icons. Hover the pointer over the left-most icon, an orange circle with a white square. Xcode should pop up a label that reads View Controller. Single-click to select it. Over in the Utility pane, select the Identity Inspector (Figure 3-24). Change the Class field (under the Custom Class header) to HeroListController.

9781430238072_Fig03-24.jpg

Figure 3-24.  View Controller’s Identity Inspector

What have you done here? You’ve told Xcode that your view controller is not a plain old UIViewController, but now a HeroListController. If you hover the pointer over the view controller icon, the pop-up will read Hero View Controller now.

Making the Connections and Outlets

First, make the HeroListController the table view data source and delegate. Control-drag from the Table View area to the HeroListController (Figure 3-25). When you release, an Outlets pop-up window should appear (Figure 3-26). Select dataSource. Repeat the control-drag, this time selecting delegate.

9781430238072_Fig03-25.jpg

Figure 3-25.  Control-drag from the table view to the HeroListController

9781430238072_Fig03-26.jpg

Figure 3-26.  Table view Outlets pop-up window

Now you’re going to add the outlets for the tab bar and table view. On the toolbar, change the editor from the Standard editor to the Assistant editor. The Editor pane should split into two views (Figure 3-27). The left view will have the storyboard editor; the right view will have a Code editor showing the interface file of HeroListController.

9781430238072_Fig03-27.jpg

Figure 3-27.  The Assistant editor

Again, control-drag from the Table View area, but this time go to the Code editor, just between the @interface and @end declarations (Figure 3-28). Once you release, a Connection pop-up window should appear (Figure 3-29). Enter heroTableView in the Name field and leave the rest of the fields at their default settings. Click the Connect button.

9781430238072_Fig03-28.jpg

Figure 3-28.  Control-drag from the table view to the HeroListController interface file

9781430238072_Fig03-29.jpg

Figure 3-29.  The Connection pop-up window

The following line should be added after the @interface declaration:

@property (weak, nonatomic) IBOutlet UITableView *heroTableView;

Repeat the process, this time control dragging from the tab bar to the just underneath the new @property declaration. Use heroTabBar for the name. You should get the following new @property declaration for the tab bar:

@property (weak, nonatomic) IBOutlet UITabBar *heroTabBar;

Navigation Bar Buttons

If you build and run the SuperDB app, you should get something like Figure 3-30.

9781430238072_Fig03-30.jpg

Figure 3-30.  SuperDB so far. Looking good!

Let’s add the Edit and Add (+) buttons. Make sure the Standard Editor toggle is selected in the toolbar, then select HeroListController.m in the Navigator pane. In the Editor pane, find the method

- (void)viewDidLoad

At the bottom of the method, you should see the following lines:

// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;

Uncomment the second line and change the rightBarButtonItem to leftBarButtonItem, like so:

// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
self.navigationItem.leftBarButtonItem = self.editButtonItem;

To add the Add (+) button, you need to go back to the storyboard editor. Select SuperDB.storyboard in the Navigator pane. Drag a bar button item from the Object library to the right side of the navigation bar in the Hero view controller. In the Utility pane, select the Attribute Inspector. You should see the Attribute Inspector for a bar button item (Figure 3-31). If not, make sure the bar button item you just added is selected. Change the Identifier field to Add. The bar button item’s label should change from Item to + .

9781430238072_Fig03-31.jpg

Figure 3-31.  Bar button item’s Attribute Inspector

Toggle back to the Assistant editor. Control-drag from the bar button item to just below the last @property in the HeroListController interface file. When the connection pop-up window appears, add a connection named addButton. Control-drag from the bar button item again to just above the @end. This time, when the connection pop-up appears, change the connection value to Action and set the name to addHero (Figure 3-32). Click connect. You should see a new method declaration:

9781430238072_Fig03-32.jpg

Figure 3-32.  Adding the addHero: Action

- (IBAction)addHero:(id)sender;

If you go to the HeroListController implementation file, you’ll see the (empty) method implementation:

- (IBAction)addHero:(id)sender {
}

Build and run the app. Everything looks in place (Figure 3-33). Click the Edit button. It should turn into a Done button. Click the Done button, and it should go back to Edit. Press the Add (+) button. Nothing happens. That’s because you haven’t written the -addHero: method to do anything. You’ll get to that soon.

9781430238072_Fig03-33.jpg

Figure 3-33.  Everything is in the right place

However, right now the tab bar does not have either tab button selected. When you start the app, both buttons are off. You can select one, then toggle between the two. But shouldn’t one of the buttons be selected at launch? You’ll implement that next.

Tab Bar and User Defaults

You want the application to start with one of the tab bar buttons selected. You can do that pretty easily, by adding something like this

// Select the Tab Bar Button
UITabBarItem *item = [self.heroTabBar.items objectAtIndex:0];
[self.heroTabBar setSelectedItem:item];

to the viewDidLoad: method of the HeroListController. Go ahead and try it. The application starts and the By Name tab is selected. Now, select the By Secret Identity tab and stop the app in Xcode. Restart the app. The By Name tab is selected. Wouldn’t it be nice if the application remembered your last selection? You can use user defaults to do that remembering for you.

In the HeroListController interface, add the following lines before the @interface declaration:

#define kSelectedTabDefaultsKey @"Selected Tab"

enum {
    kByName,
    kBySecretIdentity,
};

The kSelectedTabDefaultsKey is the key you’ll use to store and retrieve the selected tab bar button index from the user defaults. The enumeration is just a convenience for the values 0 and 1.

Switch over to the HeroListController implementation file, and add the following to the end of the viewDidLoad: method:

// Select the Tab Bar button
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSInteger selectedTab = [defaults integerForKey:kSelectedTabDefaultsKey];
UITabBarItem *item = [self.heroTabBar.items objectAtIndex:selectedTab];
[self.heroTabBar setSelectedItem:item];

(If you entered the earlier tab bar selection code, make sure you make the changes correctly.)

Build and run the app. Toggle the tab bar. Quit the app, and make sure the By Secret Identity button is selected. Quit the app, and start it again. It should remember the selection, right? Not yet. You haven’t written code to write the user default when the tab bar selection changes. You’re only reading it at launch. Let’s write the default when it changes.

Select the SuperDB.storyboard, and control-drag from the tab bar to the Hero view controller. When the Outlets pop-up appears, select delegate (it should be your only choice). Next, select HeroListController.h, and change the @interface declaration from

@interface HeroListController : UIViewController < UITableViewDataSource, UITableViewDelegate>

to

@interface HeroListController : UIViewController < UITableViewDataSource, UITableViewDelegate, UITabBarDelegate>

Now UITabBarDelegate has a required method -tabBar:didSelectItem:. Select
HeroListController.m, and navigate the Editor to just above the -addHero: method. Add these lines:

#pragma mark - UITabBarDelegate Methods

- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSUInteger tabIndex = [tabBar.items indexOfObject:item];
    [defaults setInteger:tabIndex forKey:kSelectedTabDefaultsKey];
}

Now when you quit and launch the application, it remembers your last tab bar selection.

Designing the Data Model

Now you need define the application’s data model. As we discussed in Chapter 2, the Xcode model editor is where you design your application’s data model. In the Navigator pane, click on SuperDB.xcdatamodel. This should bring up the model editor (Figure 3-34).

9781430238072_Fig03-34.jpg

Figure 3-34.  The empty model editor awaiting your application’s data model

Unlike the data model from Chapter 2, you should be starting with a completely empty data model. So let’s dive right in and start building. The first thing you need to add to your data model is an entity. Remember, entities are like class definitions. Although they don’t store any data themselves, without at least one entity in your data model, your application won’t be able to store any data.

Adding an Entity

Since the purpose of your application is to track information about superheroes, it seems logical that you’re going to need an entity to represent a hero. You’re going to start off simple in this chapter and track only a few pieces of data about each hero: name, secret identity, date of birth, and sex. You’ll add more data elements in future chapters, but this will give you a basic foundation upon which to build.

Add a new entity to the data model. A new entity, named Entity, should appear in the Top-Level Components pane. This entity should be selected, and the text Entity should be highlighted. Type Hero to name this entity.

Editing the New Entity

Let’s verify that your new Hero entity has been added to the default configuration. Select the Default Configuration in the Top-Level Configuration pane. The Data Editor Detail pane to the right should have changed with a single table named Entities. There should be one entry in this table, the entity you just named Hero.

Next to Hero is a checkbox called Abstract. This checkbox allows you to create an entity that cannot be used to create managed objects at runtime. The reason why you might create an abstract entity is if you have several properties that are common to multiple entities. In that case, you might create an abstract entity to hold the common fields and then make every entity that uses those common fields a child of that abstract entity. Thus if you needed to change those common fields, you’d only need to do it in one place.

Next, the Class field should blank. This means the Hero entity is a subclass of NSManagedObject. In Chapter 6, you’ll see how to create custom subclasses of NSManagedObject to add functionality.

You can see more detail by selecting this row, and then exposing the Utility pane. Let’s expose the Utility pane. Once the Utility pane appears, select the Data Model Inspector (the third button on the Inspector Selector bar). The Utility pane should be similar to Figure 3-35.

9781430238072_Fig03-35.jpg

Figure 3-35.  The Utilities pane for the new Entity

The first three fields (Name, Class, and Abstract Entity) mirror what you saw in the Data Detail pane. Below the Abstract Entity checkbox is a pop-up menu labeled Parent Entity. Within a data model, you have the ability to specify a parent entity, which is very similar to subclassing in Objective-C. When you specify another entity as your parent, the new entity receives all the properties of parent entity along with any additional ones that you specify. Leave the parenting pop-up set to No Parent Entity.

Note  You may be wondering about the additional areas in the Data Model Inspector, titled User Info, Versioning and Entity Sync. These settings give you access to more advanced configuration parameters that are only rarely used. You won’t be changing any of the configurations.

If you’re interested in finding out more about these advanced options, you can read more about them in Pro Core Data for iOS, 2nd Edition (www.apress.com/9781430236566). Apple has the following guides online as well: the Core Data Programming Guide at http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/CoreData/cdProgrammingGuide.html and the Core Data Model Versioning and Data Migration Guide at http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/CoreDataVersioning/Articles/Introduction.html.

Adding Attributes to the Hero Entity

Now that you have an entity, you must give it attributes in order for managed objects based on this entity to be able to store any data. For this chapter, you need four attributes: name, secret identity, birth date, and sex.

Adding the Name Attribute

Select the Hero entity in the data component pane, and add an attribute. Once added, an entry named Attribute should appear in the Attributes Property table in the detail pane. Just as when you created a new entity, the newly added attribute has been automatically selected for you. Type name, which will cause the name of the new attribute to be updated. The Attributes Property pane should look like Figure 3-36.

9781430238072_Fig03-36.jpg

Figure 3-36.  Attributes detail

Tip  It’s not an accident that you chose to start your entity Hero with a capital H, but your attribute name with a lowercase n. This is the accepted naming convention for entities and properties. Entities begin with a capital letter; properties begin with a lowercase letter. In both cases, if the name of the entity or property consists of more than one word, the first letter of each new word is capitalized.

The Type column of the table specifies the data type of the attribute. By default, the data type is set to Undefined.

Now, let’s expose the Utilities pane again (if it’s not already open). Make sure the name attribute is selected in the detail pane, and choose the Data Model Inspector (Figure 3-37). The first field should be titled Name, and it should have the value of name.

9781430238072_Fig03-37.jpg

Figure 3-37.  The Data Model Inspector for the new name attribute

Below the Name field are three checkboxes: Transient, Optional, and Indexed. If Optional is checked, then this entity can be saved even if this attribute has no value assigned to it. If you uncheck it, then any attempt to save a managed object based on this entity when the name attribute is nil will result in a validation error that will prevent the save. In this particular case, name is the main attribute that you will use to identify a given hero, so you probably want to require this attribute. Single-click the Optional checkbox to uncheck it, making this field required.

The Transient checkbox allows you to create attributes that are not saved in the persistent store. They can also be used to create custom attributes that store non-standard data. For now, don’t worry too much about Transient. Just leave it unchecked; you’ll revisit this checkbox in Chapter 6.

The final checkbox, Indexed, tells the underlying data store to add an index on this attribute. Not all persistent stores support indices, but the default store (SQLite) does. The database uses an index to improve search speeds when searching or ordering based on that field. You will be ordering your superheroes by name, so check the Indexed checkbox to tell SQLite to create an index on the column that will be used to store this attributes data.

Caution  Properly used, indices can greatly improve performance in a SQLite persistent store. Adding indices where they are not needed, however, can actually degrade performance. If you don’t have a reason for selecting Indexed, leave it unchecked.

Attribute Types

Every attribute has a type, which identifies the kind of data that the attribute is capable of storing. If you single-click the Attribute Type drop-down (which should currently be set to Undefined), you can see the various datatypes that Core Data supports out of the box (Figure 3-38). These are all the types of data that you can store without having to implement a custom attribute, like you’re going to do in Chapter 6. Each of the data types correspond to an Objective-C class that is used to set or retrieve values, and you must make sure to use the correct object when setting values on managed objects.

9781430238072_Fig03-38.jpg

Figure 3-38.  The data types supported by Core Data

The Integer Data Types

Integer 16, Integer 32, and Integer 64 all hold signed integers (whole numbers). The only difference between these three number types is the minimum and maximum size of the values they are capable of storing. In general, you should pick the smallest-size integer that you are certain will work for your purposes. For example, if you know your attribute will never hold a number larger than a thousand, make sure to select Integer 16 rather than Integer 32 or Integer 64. The minimum and maximum values that these three data types are capable of storing are shown in Table 3-1.

Table 3-1. Integer Type Minimums and Maximums

Data Type Minimum Maximum
Integer 16 −32,768 32, 767
Integer 32 −2,147,483,648 2,147,483,647
Integer 64 −9,223,372,036,854,775,808 9,223,372,036,854,77`5,807

At runtime, you set integer attributes of a managed object using instances of NSNumber created using a factory method such as numberWithInt: or numberWithLong:.

The Decimal, Double, and Float Data Types

The decimal, double, and float data types all hold decimal numbers. Double and float hold floating-point representations of decimal numbers similar to the C datatypes of double and float, respectively. Floating-point representations of decimal numbers are always an approximation due to the fact that they use a fixed number of bytes to represent data. The larger the number to the left of the decimal point, the less bytes there are available to hold the fractional part of the number. The double data type uses 64 bits to store a single number while the float data type uses 32 bits of data to store a single number. For many purposes, these two datatypes will work just fine. However, when you have data, such as currency, where small rounding errors would be a problem, Core Data provides the decimal data type, which is not subject to rounding errors. The decimal type can hold numbers with up to 38 significant digits stored internally using fixed-point numbers so that the stored value is not subject to the rounding errors that can happen with floating-point numbers.

At runtime, you set double and float attributes using instances of NSNumber created using the NSNumber factory method numberWithFloat: or numberWithDouble:. Decimal attributes, on the other hand, must be set using an instance of the class NSDecimalNumber.

The String Data Type

The string data type is one of the most common attribute types you will use. String attributes are capable of holding text in nearly any language or script since they are stored internally using Unicode. String attributes are set at runtime using instances of NSString.

The Boolean Data Type

Boolean values (YES or NO) can be stored using the Boolean data type. Boolean attributes are set at runtime using instances of NSNumber created using numberWithBOOL:.

The Date Data Type

Dates and timestamps can be stored in Core Data using the date data type. At runtime, date attributes are set using instances of NSDate.

The Binary Data Type

The binary data type is used to store any kind of binary data. Binary attributes are set at runtime using NSData instances. Anything that can be put into an NSData instance can be stored in a binary attribute. However, you generally can’t search or sort on binary data types.

The Transformable Data Type

The transformable data type is a special data type that works along with something called a value transformer to let you create attributes based on any Objective-C class, even those for which there is no corresponding Core Data data type. You would use transformable data types to store a UIImage instance, for example, or to store a UIColor instance. You’ll see how transformable attributes work in Chapter 6.

Setting the name Attribute Type

A name, obviously, is text, so the obvious type for this attribute is string. Select string from the Attribute Type drop-down. After selecting it, a few new fields will appear in the Detail pane (Figure 3-39). Just like Interface Builder’s inspector, the Detail pane in the model editor is context-sensitive. Some attribute types, such as the string type, have additional configuration options.

9781430238072_Fig03-39.jpg

Figure 3-39.  The Detail pane after selecting the string type

The Min Length: and Max Length: fields allow you to set a minimum and maximum number of characters for this field. If you enter a number into either field, any attempt to save a managed object that has less characters than the Min Length: or more characters than Max Length: stored in this attribute will result in a validation error at save time.

Note that this enforcement happens in the data model, not in the user interface. Unless you specifically enforce limitations through your user interface, these validations won’t happen until you actually save the data model. In most instances, if you enforce a minimum or maximum length, you should also take some steps to enforce that in your user interface. Otherwise, the user won’t be informed of the error until they go to save, which could be quite a while after they’ve entered data into this field. You’ll see an example of enforcing this in Chapter 6.

The next field is labeled Default Value. You can use this to, well, set a default value for this property. If you type a value into this field, any managed object based on this entity will automatically have its corresponding property set to whatever value you type in here. So, in this case, if you were to type Untitled Hero into this field, any time you created a new Hero managed object, the name property would automatically get set to Untitled Hero. Heck, that sounds like a good idea, so type Untitled Hero into this field.

The last field is labeled Reg. Ex., which stands for regular expression. This field allows you to do further validation on the entered text using regular expressions, which are special text strings that you can use to express patterns. You could, for example, use an attribute to store an IP address in text and then ensure that only valid numerical IP addresses are entered by entering the regular expression d{1,3}.d{1,3}.d{1,3}. d{1,3}. You’re not going to use regular expressions for this attribute, so leave the Reg. Ex. field blank.

Note  Regular expressions are a very complex topic on which many full books have been written. Teaching regular expressions is way beyond the scope of this book, but if you’re interested in using regular expressions to do data model-level validation, a good starting point is the Wikipedia page on regular expressions at http://en.wikipedia.org/wiki/Regular_expression, which covers the basic syntax and contains links to many regular expression-related resources.

Finally, for good measure, save.

Adding the Rest of the Attributes

Your Hero entity needs three more attributes, so let’s add them now. Click the Add Attribute button again. Give this one a name of secretIdentity and a type of string. Since, according to Mr. Incredible, every superhero has a secret identity, you’d better uncheck the Optional check box. You will be sorting and searching on secret identity, so check the Indexed box. For Default Value, type in Unknown. Because you’ve made the field mandatory by unchecking the Optional check box, it’s a good idea to provide a default value. Leave the rest of the fields as is.

Caution  Be sure to enter default values for the name and secretIdentity attributes. If you don’t, the program will behave badly. If your program crashes, check to make sure you’ve saved your source code files and your nib files.

Click the plus button a third time to add yet another attribute, giving it a name of birthdate and a type of date. Leave the rest of the fields at their default values for this attribute. You may not know the birthdate for all of your superheroes, so you want to leave this attribute as optional. As far as you know now, you won’t be doing a lot of searching or ordering on birthdate, so there’s no need to make this attribute indexed. You could do some additional validation here by setting a minimum, maximum, or default date, but there really isn’t much need. There’s no default value that would make sense, and setting a minimum or maximum date would preclude the possibility of an immortal superhero or a time-traveling one, which you certainly don’t want to do!

This leaves you with one more attribute for this first iteration of your application: sex. There are a number of ways that you could choose to store this particular piece of information. For simplicity’s sake (and because it will help us show you a few helpful techniques in Chapter 6), you’re just going to store a character string of either Male or Female. Add another attribute and select a type of string. Let’s leave this as an optional setting; there might just be an androgynous masked avenger or two out there. You could use the regular expression field to limit inputs to either Male or Female but, instead, you’re going to enforce that in the user interface by presenting a selection list rather than enforcing it here in the data model.

Guess what? You’ve now completed the data model for the first iteration of the SuperDB application. Save it and let’s move on.

Declaring the Fetched Results Controller

In order to populate the table view, you need to fetch all the Hero entities stored in your persistent store. The best way to accomplish this is to use a fetched results controller inside the HeroListController. In order to use a fetched results controller, you need to define its delegate to be notified when the fetched results change. To make things easy, you’ll make the HeroListController the fetched results controller delegate.

Select HeroListController and change the @interface declaration to this:

@interface HeroListController : UIViewController < UITableViewDataSource, UITableViewDelegate, UITabBarDelegate, NSFetchedResultsControllerDelegate>

Now that you’ve declared the NSFetchedResultsControllerDelegate, you need the controller. You could declare the property in HeroListController.h, but you don’t actually need this property to be public. You’re only going use it within the HeroListController. So you’ll make this a private property.

Select HeroListController.m, and scroll the editor to the top of the file, if necessary. Right above the @implementation declaration, you should add these lines:

#import "HeroListController.h"
@interface HeroListController ()
@end

The @interface declaration is actually a category declaration. Note the empty parenthesis at the end of the line. This is just a convention for declaring a category inside the implementation file. So you’ll change it to read

#import "HeroListController.h"
#import "AppDelegate.h"

@interface HeroListController ()
@property (nonatomic, strong, readonly) NSFetchedResultsController *fetchedResultsController;
@end

You added the #import because you’ll need it later on. Add the appropriate @synthesize declaration after the @implementation, like so:

@synthesize fetchedResultsController = _fetchedResultsController;

Note  You may hear developers claim that using the underscore prefix is reserved by Apple and that you shouldn’t use it. This is a misconception. Apple does, indeed, reserve the underscore prefix for the names of methods. It does not make any similar reservation when it comes to the names of instance variables. You can read Apple’s naming convention for instance variables, which makes no restriction on the use of the underscore, at http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/CodingGuidelines/Articles/NamingIvarsAndTypes.html.

Notice that the fetchedResultsController property is declared with the readonly keyword. You will be lazily loading the fetched results controller in the accessor method. You do not want other classes to be able to set fetchedResultsController, so you declare it readonly to prevent that from happening.

Implementing the Fetched Results Controller

Somewhere in the @implemention of HeroListController you need your managedObjectContext and fetchedResultsController methods. Let’s add it just above the addHero: method.

#pragma mark - FetchedResultsController Property

- (NSFetchedResultsController *)fetchedResultsController
{
}

Like they use to say, where’s the beef? Well, we wanted to step through this line by line and explain the code. The full listing is available at the end of the chapter if you want to look ahead.

First, we said the fetched results controller was going to be lazily loaded, so here’s the code to handle that:

if (_fetchedResultsController ! = nil) {
    return _fetchedResultsController;
}

If you get past this point, it means that the _fetchResultsController instance variable (or ivar) is nil, so you’ll have to create one. First, you need to instantiate a fetch request.

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

Now you get the entity description for your Hero entity and set the fetch request entity. While you’re at it, set the fetch batch size, which breaks the fetch up into batches for performance reasons.

AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *managedObjectContext = [appDelegate managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Hero"
                                          inManagedObjectContext:managedObjectContext];

[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:20];

The order of the fetch results is going to depend on which tab you’ve selected, so you’ll get that value. As a sanity check, read the user defaults if no tab is selected.

NSUInteger tabIndex = [self.heroTabBar.items indexOfObject:self.heroTabBar.selectedItem];
if (tabIndex == NSNotFound) {
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    tabIndex = [defaults integerForKey:kSelectedTabDefaultsKey];
}

Now you set the fetch request’s sort descriptors. A sort descriptor is a simple object that tells the fetch request what property (attribute) should be used to compare instances of entities, and whether it should be ascending or descending. A fetch request expects an array of sort descriptors, the order of sort descriptors determines the order of priority when comparing.

NSString *sectionKey = nil;
switch (tabIndex) {
    // Notice that the kByName and kBySecretIdentity Code are nearly identical.
    // A refactoring opportunity?

    case kByName: {
        NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:@"name"
                                                                        ascending:YES];

        NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:@"secretIdentity"
                                                                        ascending:YES];

        NSArray *sortDescriptors =
            [[NSArray alloc] initWithObjects:sortDescriptor1, sortDescriptor2, nil];
        [fetchRequest setSortDescriptors:sortDescriptors];
        sectionKey = @"name";
        break;
    }
    case kBySecretIdentity:{
        NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:@"secretIdentity"
                                                                        ascending:YES];

        NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:@"name"
                                                                        ascending:YES];

        NSArray *sortDescriptors =
            [[NSArray alloc] initWithObjects:sortDescriptor1, sortDescriptor2, nil];
        [fetchRequest setSortDescriptors:sortDescriptors];
        sectionKey = @"secretIdentity";
        break;
    }
}

If the By Name tab is selected, you ask the fetch request to sort by the name attribute, then the secretIdentity. For the By Secret Identity tab, you reverse the sort descriptors. You set a sectionKey string, which you’ll use next.

Now you finally instantiate the fetched results controller. Here’s where you use the sectionKey, and assign it a cache name of Hero. You assign the fetched results controller delegate to the HeroListController.

_fetchedResultsController =
    [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                        managedObjectContext:managedObjectContext
                                          sectionNameKeyPath:sectionKey
                                                   cacheName:@"Hero"];

_fetchedResultsController.delegate = self;

return _fetchedResultsController;

Finally, you return the fetched results controller.

Fetched Results Controller Delegate Methods

Since you assigned the fetched results controller delegate to the HeroListController, you need to implement those methods. Add the following below the fetchedResultsController method you just created:

#pragma mark - NSFetchedResultsControllerDelegate Methods

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
    [self.heroTableView beginUpdates];
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    [self.heroTableView endUpdates];
}

- (void)controller:(NSFetchedResultsController *)controller
  didChangeSection:(id < NSFetchedResultsSectionInfo>)sectionInfo

           atIndex:(NSUInteger)sectionIndex
     forChangeType:(NSFetchedResultsChangeType)type

{
    switch(type) {
        case NSFetchedResultsChangeInsert:
            [self.heroTableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                                            withRowAnimation:UITableViewRowAnimationFade];

            break;

        case NSFetchedResultsChangeDelete:
            [self.heroTableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                                            withRowAnimation:UITableViewRowAnimationFade];

            break;
    }
}

- (void)controller:(NSFetchedResultsController *)controller
   didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath
     forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath

{
    switch(type) {
        case NSFetchedResultsChangeInsert:
            [self.heroTableView insertRowsAtIndexPaths:@[newIndexPath]
                                      withRowAnimation:UITableViewRowAnimationFade];

            break;

        case NSFetchedResultsChangeDelete:
            [self.heroTableView deleteRowsAtIndexPaths:@[indexPath]
                                      withRowAnimation:UITableViewRowAnimationFade];

            break;

        case NSFetchedResultsChangeUpdate:
        case NSFetchedResultsChangeMove:
            break;

    }
}

For an explanation of these methods, refer to the “Fetched Results Controller Delegate” section of Chapter 2.

Making All Work

You’re almost done. You still need to

  • Implement the Edit and Add (+) buttons.
  • Code the table view data source and delegate methods correctly.
  • Make the tab bar selector sort the table view.
  • Run the fetch request at launch.
  • Handle errors.

It seems like a lot, but it’s not. Let’s start with the error handling first.

Error Handling

You’ll make things simple by using a simple alert view to display errors. In order to use an alert view, you need to implement an alert view delegate. Like the fetched results controller, you’ll make the HeroListController the alert view delegate. Edit the HeroListController @interface declaration to read

@interface HeroListController : UIViewController < UITableViewDataSource, UITableViewDelegate, UITabBarDelegate, 
NSFetchedResultsControllerDelegate, UIAlertViewDelegate>

In HeroListController.m, you’ll add a simple alert view delegate method.

#pragma mark - UIAlertViewDelegate Methods

- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
    exit(−1);
}

All this method does is cause your application quit.

Implementing Edit and Add

When you click the Add (+) button, the application does more than just add a row to the table view. It adds a new Hero entity to the managed object context. In the HeroListController.m file, modify the addHero method to read

- (IBAction)addHero:(id)sender
{
     NSManagedObjectContext *managedObjectContext =
         [self.fetchedResultsController managedObjectContext];

     NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
     [NSEntityDescription insertNewObjectForEntityForName:[entity name]
                                    inManagedObjectContext:managedObjectContext];

     NSError *error = nil;
     if (![managedObjectContext save:&error]) {
         UIAlertView *alert =              [[UIAlertView alloc]
                  initWithTitle:NSLocalizedString(@"Error saving entity",
                                                  @"Error saving entity")

                       message:[NSString stringWithFormat:NSLocalizedString(@"Error was: %@, quitting.",
                                                                          @"Error was: %@, quitting."),
                                                          [error localizedDescription]]

                      delegate:self
             cancelButtonTitle:NSLocalizedString(@"Aw, Nuts", @"Aw, Nuts")
             otherButtonTitles:nil];
         [alert show];
     }
}

When the Edit button is clicked, the setEditing:animated: method is automatically called. So you just need to add that method to your HeroListController.m without having declare it in the interface file.

- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
    [super setEditing:editing animated:animated];
    self.addButton.enabled = !editing;
    [self.heroTableView setEditing:editing animated:animated];
}

All you do here is call the super method, disable the Add (+) button (you don’t want to be adding heroes while editing!), and call setEditing:animated on the table view.

Coding the Table View Data Source and Delegate

Using the CoreDataApp from Chapter 2 as an example, you need to change the following table view data source methods:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
   return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
   id < NSFetchedResultsSectionInfo > sectionInfo =
          [[self.fetchedResultsController sections] objectAtIndex:section];
   return [sectionInfo numberOfObjects];
}

Next, you handle the table view cell creation, like so:

- (UITableViewCell *)tableView:(UITableView *)tableView
          cellForRowAtIndexPath:(NSIndexPath *)indexPath

{
    static NSString *CellIdentifier = @"HeroListCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    // Configure the cell. . .
    NSManagedObject *aHero = [self.fetchedResultsController objectAtIndexPath:indexPath];
    NSInteger tab = [self.heroTabBar.items indexOfObject:self.heroTabBar.selectedItem];
    switch (tab) {
        case kByName:
            cell.textLabel.text = [aHero valueForKey:@"name"];
            cell.detailTextLabel.text = [aHero valueForKey:@"secretIdentity"];
            break;
        case kBySecretIdentity:
            cell.textLabel.text = [aHero valueForKey:@"secretIdentity"];
            cell.detailTextLabel.text = [aHero valueForKey:@"name"];
            break;
    }
    return cell;
}

Finally, you uncomment tableView:commitEditingStyle:forRowAtIndexPath: to handle deleting rows.

// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView
         commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
         forRowAtIndexPath:(NSIndexPath *)indexPath

{
    NSManagedObjectContext *managedObjectContext = [self.fetchedResultsController managedObjectContext];
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        // Delete the row from the data source
        [managedObjectContext deleteObject:[self.fetchedResultsController
                                                 objectAtIndexPath:indexPath]];

        NSError *error;
        if (![managedObjectContext save:&error]) {
            UIAlertView *alert =                 [[UIAlertView alloc]
                     initWithTitle:NSLocalizedString(@"Error saving entity",
                                                     @"Error saving entity")

                          message:
                              [NSString stringWithFormat:NSLocalizedString(@"Error was: %@, quitting.",
                                                                            @"Error was: %@, quitting."),
                                                           [error localizedDescription]]

                         delegate:self
                cancelButtonTitle:NSLocalizedString(@"Aw, Nuts", @"Aw, Nuts")
                otherButtonTitles:nil];
            [alert show];
        }
    }
}

Sorting the Table View

Finally, you need to make the table view order change when you toggle the tab bar. You need to add the following code to tabBar:didSelectItem: delegate method:

- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSUInteger tabIndex = [tabBar.items indexOfObject:item];
    [defaults setInteger:tabIndex forKey:kSelectedTabDefaultsKey];
    [NSFetchedResultsController deleteCacheWithName:@"Hero"];
    _fetchedResultsController.delegate = nil;
    _fetchedResultsController = nil;
    NSError *error;
    if (![self.fetchedResultsController performFetch:&error]) {
        NSLog(@"Error performing fetch: %@", [error localizedDescription]);
    }
    [self.heroTableView reloadData];
}

Loading the Fetch Request At Launch

Add the following to the HeroListController viewDidLoad method:

// Fetch any existing entities
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
    UIAlertView *alert =       [[UIAlertView alloc]           initWithTitle:NSLocalizedString(@"Error loading data",
                                           @"Error loading data")

                message:[NSString stringWithFormat:NSLocalizedString(@"Error was: %@, quitting.",
                                                                      @"Error was: %@, quitting."),
                                                   [error localizedDescription]]

               delegate:self
      cancelButtonTitle:NSLocalizedString(@"Aw, Nuts", @"Aw, Nuts")
      otherButtonTitles:nil];
    [alert show];
}

And that’s pretty much everything.

Let ‘Er Rip

Well, what are you waiting for? That was a lot of work; you deserve to try it out. Make sure everything is saved, then build and run the app.

If everything went okay, when the application first launches, you should be presented with an empty table with a navigation bar at the top and a tab bar at the bottom (Figure 3-40). Pressing the right button in the navigation bar will add a new unnamed superhero to the database. Pressing the Edit button will allow you to delete heroes.

9781430238072_Fig03-40.jpg

Figure 3-40.  The SuperDB application at launch time

Note  If your app crashed when you ran it, there’s a couple of things to look for. First, make sure you saved all your source code and nib files before you ran your project. Also, make sure that you have defaults specified for your hero’s name and secret identity in your data model editor. If you did that and your app still crashes, try resetting your simulator. Here’s how: bring up the simulator, and from the iPhone Simulator menu, select Reset Contents and Settings. That should do it. In Chapter 5, we’ll show you how to ensure that changes to your data model don’t cause such problems.

Add a few unnamed superheroes to your application and try out the two tabs to make sure that the display changes when you select a new tab. When you select the By Name tab, it should look like Figure 3-1, but when you select the By Secret Identity tab, it should look like Figure 3-41.

9781430238072_Fig03-41.jpg

Figure 3-41.  Pressing the By Secret Identity tab doesn’t change the order of the rows yet, but it does change which value is displayed first

Done, but Not Done

In this chapter, you did a lot of work. You saw how to set up a navigation-based application that uses a tab bar, and you learned how to design a basic Core Data data model by creating an entity and giving it several attributes.

This application isn’t done, but you’ve now laid a solid foundation upon which to move forward. When you’re ready, turn the page and start creating a detail editing page to allow the user to edit their superheroes.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.196.175