Using Core Data: A Simple Task Manager ❘ 139 Next, you need to build a new screen for entering the Task text. In a production application, you would probably want to use Interface Builder to build a nice user interface for this screen. However, to keep this example simple, you will just build a very simple interface in code. Add a new UIViewController subclass to your project. Make sure you clear the checkboxes for “ UITableViewController subclass ” and “ With XIB for user interface. ” Call your new class TaskEntryViewController . Modify the TaskEntryViewController.h header to include instance variables for a UITextField and the Managed Object Context. When the RootViewController calls up the TaskEntryViewController , it will set the reference to the context. Declare the properties for the UITextField and the managed object context. Finally, modify the interface defi nition to indicate that you will be implementing the UITextFieldDelegate protocol. You use this protocol to receive messages from the UITextField . Specifi cally, you will be implementing the textFieldShouldReturn: method that runs when the Return button is pressed in a TextField . The code for the TaskEntryViewController.h header should look like this: #import < UIKit/UIKit.h > @interface TaskEntryViewController : UIViewController < UITextFieldDelegate > { UITextField *tf; NSManagedObjectContext *managedObjectContext; } @property (retain, nonatomic) UITextField *tf; @property (retain, nonatomic) NSManagedObjectContext *managedObjectContext; @end TaskEntryViewController.h Now you will implement the TaskEntryViewController . First, synthesize the TextField and the context properties: @synthesize tf, managedObjectContext; Next, you need to add code to TaskEntryViewController.m to programmatically create the UI in the loadView method: - (void)loadView { [super loadView]; self.tf = [[UITextField alloc] initWithFrame:CGRectMake(65, 20, 200, 20)]; [self.tf setBackgroundColor:[UIColor lightGrayColor]]; [self.tf setDelegate:self]; [self.view addSubview:self.tf]; UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(5, 20, 60, 20)]; CH005.indd 139CH005.indd 139 9/18/10 9:34:28 AM9/18/10 9:34:28 AM 140 ❘ CHAPTER 5 INTRODUCING CORE DATA [lbl setText:@”Task:”]; [self.view addSubview:lbl]; [lbl release]; } TaskEntryViewController.m The code creates a TextField object with the specifi ed screen coordinates. Then, it sets the background color so it is easy to see. The delegate is then set and the code adds the control to the main view. The code also creates a UILabel so that users will know what the text fi eld represents. Remember that when you are creating a production application, you will not need this code. You will most likely use Interface Builder to build a nicer interface than a plain text label and a gray text entry fi eld. The next step is to add the textFieldShouldReturn: method to TaskEntryViewController.m . The framework calls this UITextFieldDelegate method when Return is pressed. The code inserts a new Task object into the context: - (BOOL)textFieldShouldReturn:(UITextField *)textField { // Create a new instance of the entity managed by the fetched results // controller. NSManagedObjectContext *context = self.managedObjectContext; NSEntityDescription *entity = [NSEntityDescription entityForName:@”Task” inManagedObjectContext:context]; NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context]; // If appropriate, configure the new managed object. [newManagedObject setValue:[NSDate date] forKey:@”timeStamp”]; [newManagedObject setValue:[self.tf text] forKey:@”taskText”]; // Save the context. NSError *error = nil; if (![context save: & error]) { /* Replace this implementation with code to handle the error appropriately. */ NSLog(@”Unresolved error %@, %@”, error, [error userInfo]); abort(); } [self dismissModalViewControllerAnimated:YES]; return YES; } TaskEntryViewController.m CH005.indd 140CH005.indd 140 9/18/10 9:34:29 AM9/18/10 9:34:29 AM Using Core Data: A Simple Task Manager ❘ 141 First, this code gets a pointer to the context. Then, you defi ne a new Task entity from the context. Next, it inserts a managed object into the context. Then, the code confi gures the managed object using a timestamp and the text entered in the TextField . The context is then committed to disk and you dismiss the modal view controller. The interface will look like Figure 5 - 6. The last thing to do is modify the RootViewController to use the new Task entity and to call up the Text Entry interface when a user clicks the plus button to enter a new task. Because you will be referencing the TaskEntryViewController , you will need to add an import statement to RootViewController.m for TaskEntryViewController.h : #import “TaskEntryViewController.h” Next, you need to modify the fetchedResultsController getter method to use the Task entity instead of the Event entity. I have presented the modifi ed code in bold. - (NSFetchedResultsController *)fetchedResultsController { if (fetchedResultsController != nil) { return fetchedResultsController; } /* Set up the fetched results controller. */ // Create the fetch request for the entity. NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; // Edit the entity name as appropriate. NSEntityDescription *entity = [NSEntityDescription entityForName:@”Task” inManagedObjectContext:managedObjectContext]; [fetchRequest setEntity:entity]; RootViewController.m Now, delete the old insertNewObject method. You will need to recode this method to present the TaskEntryViewController like this: - (void)insertNewObject { // Ask for text from TaskEntryViewController TaskEntryViewController *tevc = [[TaskEntryViewController alloc] init]; FIGURE 5 - 6: Text entry interface CH005.indd 141CH005.indd 141 9/18/10 9:34:29 AM9/18/10 9:34:29 AM 142 ❘ CHAPTER 5 INTRODUCING CORE DATA tevc.managedObjectContext = self.managedObjectContext; [self presentModalViewController:tevc animated:YES]; [tevc release]; } RootViewController.m This code simply creates a TaskEntryViewController , sets the context, and presents it as a modal controller. The fi nal step is to modify the tableView:(UITableView *)tableView cellForRowAtIndexPath: method to use the taskText attribute for the main text and the timeStamp attribute for the detail text: - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @”Cell”; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease]; } // Configure the cell. NSManagedObject *managedObject = [fetchedResultsController objectAtIndexPath:indexPath]; cell.textLabel.text = [[managedObject valueForKey:@”taskText”] description]; cell.detailTextLabel.text = [[managedObject valueForKey:@”timeStamp”] description]; return cell; } RootViewController.m First, this method tries to dequeue a cell. If it fails, it creates a new cell using the UITableViewCellStyleSubtitle cell style so that you can display both the task text and the timestamp. Next, the code retrieves that managed object for the cell using the fetchedResultsController . Then, the code sets the cell ’ s textLabel.text property to the string contained in the taskText attribute of the managed object and sets the detailTextLabel.text property to the timeStamp attribute. CH005.indd 142CH005.indd 142 9/18/10 9:34:30 AM9/18/10 9:34:30 AM You should now be able to build and run the application. Try clicking on the plus icon to add new tasks. After typing in the text and hitting Return, you should see your task listed in the TableView . Try adding a few more tasks and then swiping across a task in the TableView . You should see a Delete button, which will allow you to delete the task. Tap the Edit button and notice that you go into edit mode. If you tap a red circle icon in Edit mode, you see a Delete button, which allows you to delete the row. The completed application should look like Figure 5 - 2. MOVING FORWARD In this chapter, you have learned about the basics of Core Data. You examined the Core Data architecture and you learned the basic terminology necessary to understand the Core Data API stack. Then, you explored the template code to understand how to implement the Core Data architecture. Finally, you modifi ed the template application to produce a functional task manager application. In the next few chapters, your knowledge of Core Data will go from cursory to detailed. In the next chapter, you learn to use the Xcode data modeler to develop a model that incorporates many of the features of Core Data. Then, in Chapter 7 you learn how to bring that model to life through code. Moving Forward ❘ 143 CH005.indd 143CH005.indd 143 9/18/10 9:34:30 AM9/18/10 9:34:30 AM CH005.indd 144CH005.indd 144 9/18/10 9:34:31 AM9/18/10 9:34:31 AM Modeling Data in Xcode WHAT ’ S IN THIS CHAPTER? Defi ning entities and their attributes Expressing the relationships between entities Creating fetched properties and fetch requests using the predicate builder Generating custom subclasses of NSManagedObject from your model In the previous chapter, you learned the fundamentals of working with Core Data. In this chapter, you explore the Xcode Data Modeling tool and learn how to create data models graphically. In this chapter, you learn how to use the Xcode Data Modeling tool to graphically create your Core Data model. The tool is easy to use and can dramatically speed the development time of your next data - driven application. Think of the Data Modeling tool as Interface Builder for your data. You can use the tool to model the entities and attributes that you will use in your application. You can also use the tool to defi ne relationships between entities, create fetched properties, and even build fetch requests. MODELING YOUR DATA In Chapter 2, you learned about the importance of modeling your data before you begin work on your application. I showed you how I use Omni Graffl e to create my Entity - Relationship Diagrams. Then, you explored how to turn those diagrams into an SQLite database. In this chapter, you learn how to use the Xcode Modeling tool to model your data and turn the model into Core Data objects. ➤ ➤ ➤ ➤ 6 CH006.indd 145CH006.indd 145 9/20/10 2:32:28 PM9/20/10 2:32:28 PM 146 ❘ CHAPTER 6 MODELING DATA IN XCODE The Xcode Modeling tool uses a multi - pane interface, as shown in Figure 6 - 1. There are three panes at the top of the tool. They are (from left to right) the Entities pane, the Properties pane, and the Detail pane. The Entities pane provides a list of all of the entities in your model. The Properties pane lists the properties of the currently selected entity, and the Detail pane shows details related to whatever is currently selected, either an entity or a property. The bottom pane, called the Diagram view, displays a graphical representation of your model. As you walk through the creation of the various aspects of a data model, you will learn how each pane is used. FIGURE 6 - 1: The Xcode data modeler Defi ning Entities and Their Attributes In order to show all of the features of the data modeler, I will walk you through the creation of a data model for a catalog application like the one you created in Chapters 1 through 3. Then, at the end of the chapter, you will create an extended version of the Task data model from Chapter 5, which you will use in the example code in Chapter 7. Figure 6 - 2 shows the Entity - Relationship Diagram that you developed in Chapter 2. If you recall, this diagram represents the data that you needed to build a catalog application. The application presented a company ’ s product catalog and contained information about the products, manufacturers, and countries of origin of the products. This chapter presents the creation of the catalog data model as a tutorial, but you do not necessarily need to follow along — you will not CH006.indd 146CH006.indd 146 9/20/10 2:32:31 PM9/20/10 2:32:31 PM be using it in code. I am just walking you through the process to demonstrate all of the capabilities of the data - modeling tool. At the end of this chapter, you will create another data model for a task application that you will use going forward into Chapter 7. FIGURE 6 - 2: Catalog database Entity - Relationship Diagram ProductID Name Details Price QuantityOnHand Image Product Has Name ManufacturerID Country Country CountryID 1 Manufacturer 1 1 The main entity in the catalog application is the Product. So, let ’ s create a Product entity in the tool. There are a couple of ways to create entities. You can right - click in the Diagram view and select Add Entity from the contextual menu. You can also create an entity from the menu bar by selecting Design ➪ Data Model ➪ Add Entity. Finally, you can create a new entity by clicking the plus icon at the bottom - left of the Entity pane. Choose one of these methods and create a new entity. After you create a new entity, the Diagram view shows the entity as selected: A selected entity is blue and has resize handles. Because you have selected the entity, you can see its details in the Detail pane. Entity Details The Detail pane has four tabs — General, User Info, Confi gurations, and Synchronization — as you can see in Figure 6 - 3. The General tab contains general information about the selected entity. In the Name fi eld, you can change the name of your entity. Change the name of this entity from Entity to Product. The class fi eld displays the name of the class that represents the entity in code. The tool sets the class fi eld to NSManagedObject by default. Later in the FIGURE 6 - 3: Entity detail tabs Modeling Your Data ❘ 147 CH006.indd 147CH006.indd 147 9/20/10 2:32:31 PM9/20/10 2:32:31 PM 148 ❘ CHAPTER 6 MODELING DATA IN XCODE chapter, when you create custom subclasses of NSManagedObject , you will see the name change to the name of the custom subclass. Using the parent fi eld, you can implement an inheritance hierarchy in your model between entities like those that you would implement between classes in code. Suppose that all of your products shared common attributes such as Name, Price, and Quantity, but each category of product had different sub - attributes. You could design your model such that different data entities would represent different categories. For example, if your company was selling screws, a screw entity might have attributes such as head type, thread pitch, and length. An entity to model hammers would not need the attributes of screws, but might need attributes such as weight and claw length. You could defi ne the hammer and screw as entities in your model with a parent type of Product. That way, the hammer and screw classes would both inherit all of the attributes and relationships of the parent class, Product, while still being able to defi ne their own unique relationships and attributes. Create two new entities in the model and call them Hammer and Screw. For each of these new entities, set the Parent in the Detail pane to Product. You will see an inheritance arrow drawn from the subclasses to the superclass, as in Figure 6 - 4. You can tell Core Data that an entity is abstract by selecting the abstract checkbox. You cannot instantiate abstract classes and can use them only as base classes. The next tab in the Detail pane is the User Info tab. The NSManagedObject class that you create to represent your Core Data entity in code has a property called entity that returns an NSEntityDescription . The NSEntityDescription has a userInfo property that returns an NSDictionary containing user - defi ned key - value pairs. The User Info tab allows you to create key - value pairs and associate them with the current entity. At runtime, you can access these values using the userInfo property of the NSEntityDescription . Use the plus at the bottom of the window to add a new key - value pair to the entity and use the minus to remove the selected pair. The third tab is the Confi gurations tab. Confi gurations enable you to name various groups of entities in your data model. You create a confi guration using the plus button at the bottom of the tab and delete confi gurations with the minus button. Once you create a confi guration, that confi guration is available for all entities. Then you can go through each entity individually and assign it to one or more confi gurations. At runtime, you can retrieve the entities in a confi guration using the entitiesForConfiguration method on the NSManagedObjectModel class. This method will return an NSArray of entities in the chosen confi guration. You use Confi gurations to split the entities in a model across multiple data stores. When developing applications for the iPhone, you will generally use only one data store, so you may not have much use for confi gurations. However, it is useful to know that you can group entities in the modeler and retrieve your groups at runtime. The fi nal tab in the Detail pane is the Synchronization tab. You use the Synchronization tab to confi gure entities for use with Sync Services. You can use Sync Services to synchronize data between a mobile application and a desktop application. Developing a desktop application and using Sync FIGURE 6 - 4: Subclassed entities CH006.indd 148CH006.indd 148 9/20/10 2:32:32 PM9/20/10 2:32:32 PM . Moving Forward ❘ 143 CH005.indd 143CH005.indd 143 9 /18/ 10 9:34:30 AM9 /18/ 10 9:34:30 AM CH005.indd 144CH005.indd 144 9 /18/ 10 9:34:31 AM9 /18/ 10 9:34:31 AM Modeling Data in Xcode WHAT ’ S IN. userInfo property that returns an NSDictionary containing user - defi ned key - value pairs. The User Info tab allows you to create key - value pairs and associate them with the current entity. At. TaskEntryViewController like this: - (void)insertNewObject { // Ask for text from TaskEntryViewController TaskEntryViewController *tevc = [[TaskEntryViewController alloc] init]; FIGURE 5 - 6: Text entry interface