CPSC 91 Spring 2011: Lab 06


Introduction

In this week's lab, you will implement an app that uses Core Data to store information about baseball games. I have provided you with a .plist file containing all 2429 games played during the 2010 Major League Baseball season. This data comes from retrosheet.org. When the app runs, you will display a table with each of the Major League Baseball teams. When a user clicks on that team, you will present another table with a list of all of the games played by that team, sorted by the day that the game was played. The title of each cell will be the two teams that played in the game and the score. The subtitle of each cell will be the date that the game was played.

Like last week's lab, your code:

Data Model

In this app, you will create a Core Data model with two entities: Team and Game:

  1. The Team entity will represent a baseball team. The only attribute a Team has is its teamName, a String.
  2. The Game entity will have three attributes, the home team's score, homeScore, the away team's score, awayScore, both of type Integer 16, and the day the game was played, gameDate, of type Date.
In addition to these attributes, there are relationships between these entities.
  1. The Team has a To-Many relationship to the Game entity called homeGames. The inverse of this relationship is a relationship called homeTeam which is not a To-Many relationship. A team can play many home games, but each game has only one home team.
  2. Similarly, Team has a To-Many relationship called awayGames with an inverse relationship (not To-Many) called awayTeam.

NSManagedObjects

After you have created your data model, create the NSManagedObject subclasses called Team and Game. To do this, first select either entity. Then, choose File->New File and from the window that pops up, choose Managed Object Class. Hit Next on the first page. On the second page, select both the Team and the Game entity and hit Finish. Four files, Game.h, Game.m, Team.h, and Team.m will be created. You may want to drag these files to the Classes folder in Xcode.

One of the things we didn't get to talk very much about at the end of yesterday's class was the method +departmentWithName:inManagedObjectContext: located in the Department.m file of the StudentManager-CDTVC+Dept project. What this method, a class method, does is sets up an NSFetchRequest for a Department whose name matches the first parameter, using the NSManagedObjectContext that is the second parameter. If a matching Department is found, that becomes the return value. If a matching Department is not found, a new Department is inserted into the table.

To use this class method, you might say something like this:

Department *csDept = [Department departmentWithName:@"Computer Science"
                             inManagedObjectContext:context];
Assuming the context was set up properly, csDept would point to the row in the Department table containing the CS department. If the CS department had not existed in the table before, csDept points to a row that was newly created. If it did exist in the table before, csDept points to the existing row.

In your Team.m file, you will need to create a similar method with the following header (which you'll put in the Team.h file before the first @end:

+ (Team *)teamWithName:(NSString *)aName inManagedObjectContext:(NSManagedObjectContext *)context;
This method will allow you to fetch a Team from the database, creating it if it didn't yet exist.

Converting from Property List to Core Data

Download the games.plist file. If you double-click on it once it's downloaded, the Property List Editor program will allow you to view the data in the games.plist file. What you should notice is that the top-level item in the property list is an Array (NSArray). Each of the items in the Array is a Dictionary (NSDictionary). Each Dictionary has 5 NSString keys: date associated with an NSDate value, homeTeam and visitingTeam each associated with an NSString value, and homeScore and visitingScore each associated with an NSNumber value. The team names are the three letter abbreviations for each team. (There is no need to convert these to their full names, e.g. MIN=Minnesota Twins, but if you want to do this, you should probably do so in the method described in the next paragraph.)

In your AppDelegate, write a method called -convertToCoreData that will read the games.plist file and save it into your database. These two lines will read in the games.plist file into an NSArray:

NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"games" ofType:@"plist"]; 
NSArray *gamesArray = [NSArray arrayWithContentsOfFile:plistPath];

You should now iterate through the gamesArray. For each game (represented as a dictionary as described above), you will want to:

  1. Create a new row in the Game table.
  2. Set the homeScore, awayScore, and gameDate fields in the table using the values in the dictionary.
  3. Look up the homeTeam in the Team database using +teamWithName:inManagedObjectContext: and save that Team as the homeTeam field of the Game. Similarly, look up the awayTeam (the dictionary key is "visitingTeam") and save the Team as the awayTeam in the Game. Setting the homeTeam and awayTeam in the Game table will automatically add these games to the homeGames and awayGames relationship in the Team entity.

When you have finished this, you should add [self convertToCoreData]; to your -application:didFinishLaunchingWithOptions: and run your app. If all goes well, you should have now populated your Core Data database with all of the baseball games and you should now comment out that line since you don't want to keep adding the games over and over to your database. You can, of course, check this by using sqlite3 on the command line, or you can write some code similar to what we did in class yesterday by writing and executing an NSFetchRequest and NSLog all of the data, or you can just continue to the next step and hope it all worked...

UITableViewController and CoreDataTableViewController

As I showed in class yesterday, writing a UITableViewController that hooks up to an NSFetchedResults controller is pretty straightforward. The downside is that the UITableViewController that you write will look pretty much like every other UITableViewController you write when using an NSFetchedResults controller. We can pull out nearly all of the common code and use that as the parent class for any new UITableViewController we want to make. Grab the CoreDataTableViewController.zip file. This file was created for the Stanford CS193P class and contains all of the common code. Unzip it and drag the .h and .m files (copying them) to your project.

TeamTableViewController

Now that you have CoreDataTableViewController in your project, we can add a table view controller that will display the list of Teams. Create a New File that is an Objective-C class, subclass of NSObject and call this TeamTableViewController. Inside of TTVC.h, you will need to change the declaration so that your view controller inherits from CoreDataTableViewController instead of NSObject. (Be sure to import CoreDataTableViewController.h.) Inside of TTVC.m, you will need to write only two methods.

- (id)initInManagedObjectContext:(NSManagedObjectContext *)context;
- (void)managedObjectSelected:(NSManagedObject *)managedObject;
The first method (the init method) you will also want to put in your TTVC.h file.

The init method will have a similar format to the init method in the DeptTableViewController.m file with the following differences:

  1. You will create an NSFetchRequest using the entity named @"Team"
  2. You will set the NSSortDescriptor to sort by @"teamName"
  3. You will not use sections
  4. The title key will be the @"teamName"

The second method, -managedObjectSelected:, will look similar to its counterpart in DeptTableViewController.m file with the following differences:

  1. You will cast the (NSManagedObject *) parameter to be a Team.
  2. You will create a new instance of a new table view controller called GamesByTeamTableViewController (which you have not yet written) and you will initialize this table view controller by passing it the Team that was clicked on.

At this point, you can't build and run your app because you need to write the GamesByTeamTableViewController, so let's do it.

GamesByTeamTableViewController

Like the TeamTableViewController, create a new Objective-C class, subclass of NSObject. Call this class GamesByTeamTableViewController. Like before, you will want to import the CoreDataTableViewController.h file and have GamesByTeamTableViewController inherit from CoreDataTableViewController instead of NSObject.

Like before, we need to write two methods, though our init method will take the Team we selected as the parameter:

- (id)initWithTeam:(Team *)team;
- (void)managedObjectSelected:(NSManagedObject *)managedObject;

Your -initWithTeam: method will look a lot like the -initWithDepartment: method in StudentTableViewController.m except:

  1. You will set up a fetch request on the @"Game" entity.
  2. You will set up your predicate to match either the @"homeTeam" or the @"awayTeam" to the (Team *) parameter passed in. You will need to use [NSCompoundPredicate orPredicateWithSubpredicates:(Array *)] to create a predicate that matches either the home team or the away team.
  3. Ignore sections for now. We'll come back to that.
  4. Your titleKey should be set to @"gameScore". You will need to write a -gameScore: method in Game.m (declared in Game.h) that returns an NSString that you will use as the text of the cell. If MIN beat ANA 5-3, and MIN was the away team, you want the text to say "MIN 5 - ANA 3". If MIN beat BOS 5-2, and BOS was the away team, you want the text to say "BOS 2 - MIN 5".
  5. You should have a subtitleKey set to @"dayPlayed". You will have to declare this in Game.h. The implementation of -dayPlayed is below:
    - (NSString *)dayPlayed 
    {
      return [NSDateFormatter localizedStringFromDate:self.gameDate
                                            dateStyle:NSDateFormatterMediumStyle 
                                            timeStyle:NSDateFormatterNoStyle];
    }
    
It would nice to have sections for the GamesByTeamTableViewController. However, there are some interesting design questions. The default implementation of the CoreDataTableViewController is to make the labels for the index on the righthand side equal to the first letter of the section headings. However, the index labels also combine consecutive indexes that are the same. So, here are some ideas:

Extensions

That is the end of the required portion of the lab. If you are interested in extended this, you can download the verboseGames.plist file. This plist contains an enormous amount of data about each game, including the starting lineups, winning pitchers, number of hits, runs, errors, line scores, the names of the umpires, etc. (It does not contain a full box score, however.) Use some of the data in that larger plist file to display something interesting when the user clicks on a game. For example, maybe you want to display the players who played on each team in a table, with two sections, one for each team. (And, you probably want to add a Player entity to the data model with a To-Many relationship to the Team and an inverse To-Many relationship.) Another idea is to simply display the line score and the winning and losing pitchers. Whatever you decide!

Final Project Ideas

Start thinking about some final project ideas. At this point, your ideas can be quite rough. Next week, you'll present ideas to the class for what you'd like to do. Think about your models and your views. You can certainly present multiple ideas.

Resources

If you're interested, you can download the Python source code that generated the plist files you're working with: gamelog.py.