I am working on a simple app to track my daughter's hockey games. My problem is that when I stop and restart the app some of the data doesn't get loaded back in. (I'm checking with a log statement that just shows zero regardless of what was there before.)I'm not sure if the problem is in the loading or the saving.
(Sorry for the long post, but I'm not sure where to look.)
It uses a Games class that looks like this:
#import <Foundation/Foundation.h>
@interface Game : NSObject <NSCoding>//conforms to this protocol so data can be prepared for read/write to file
//used primarily in GameFactsViewController
@property (nonatomic, strong) NSString *opponent;
@property (nonatomic, strong) NSDate *dateOfGame;
//used primarily in PlayerActionsViewController
@property (nonatomic) NSInteger shotsOnGoal;
@property (nonatomic) NSInteger shotsNotOnGoal;
@property (nonatomic) NSInteger passesCompleted;
@property (nonatomic) NSInteger passesNotCompleted;
@property (nonatomic) NSInteger takeaways;
@property (nonatomic) NSInteger giveaways;
@property (nonatomic) NSInteger faceoffsWon;
@property (nonatomic) NSInteger faceoffsLost;
@property (nonatomic) NSInteger shifts;
@property (nonatomic) NSInteger blockedShots;
@end
My problem is that the opponent and dateOfGame properties and getting saved and loaded when I start the app again, but none of the other properties are.
Tha main controller is a tableview controler with each game as a row.The opponent and dateOfGame properties are set in a tableview controller and the others in a view controller inside a tab bar controllers. The seques to these controllers are made from a disclosure indicator for the first and by clicking on the row for the second. These work fine, using this code:
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
//check for proper seque
if ([segue.identifier isEqualToString:@"AddGame"]) {
//identify the top level view controller holding the GameFactsVC (see storyboard)
UINavigationController *navigationController = segue.destinationViewController;
//go down one level to get the GFVC
GameFactsViewController *controller = (GameFactsViewController *) navigationController.topViewController;
//set the current controller (the HSVC) as the delegate for the GFVC
controller.delegate = self;
} else if ([segue.identifier isEqualToString:@"EditGame"]) {
//as above to get to the right place
UINavigationController *navigationController = segue.destinationViewController;
GameFactsViewController *controller = (GameFactsViewController *) navigationController.topViewController;
controller.delegate = self;
//"sender" here is what was clicked, the detail disclosure icon
//this identifies what game data to load to edit
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
controller.gameToEdit = _games[indexPath.row];
} else if ([segue.identifier isEqualToString:@"GameDetails"]) {
UITabBarController *tabBarController = segue.destinationViewController;
PlayerActionsViewController *controller = (PlayerActionsViewController *)[[tabBarController viewControllers] objectAtIndex:0];
controller.delegate = self;
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
controller.gameToEditPerformance = _games[indexPath.row];
NSLog(@"Data passed %ld", (long)controller.gameToEditPerformance.shotsOnGoal);
}
}
I am returning to the main controller with this method for the opponent and dateOfGame and I can log the data coming back.
-(IBAction) done
{
//Use delegate to capture entered data and pass to HSVC
//methods here are delegate methods listed above and defined in the HSVC
//see if the data was empty when view loaded
if (self.gameToEdit == nil) {
Game *game = [[Game alloc] init];
game.opponent = self.opponentField.text;
game.dateOfGame = [self convertToDate:self.dateLabel.text withFormat:@"MMM d, yyyy"];
[self.delegate gameFactsViewController:self didFinishAddingGame:game];
} else {
self.gameToEdit.opponent = self.opponentField.text;//updates data model; will update display in delegate method
self.gameToEdit.dateOfGame = [self convertToDate:self.dateLabel.text withFormat:@"MMM d, yyyy"];
[self.delegate gameFactsViewController: self didFinishEditingGame:self.gameToEdit];
}
}
Similarly, I am passing the rest of the data back from the other controller with this line at the end of a long method that sets the data values and I can log the correct data coming back here, too.
[self.delegate playerActionsViewController:self didEditGameData:self.gameToEditPerformance];
I'm calling the Save method with these two similar methods. (The key difference as I see it as I have updated the display in the other table already, while I still need to update the main view.)
- (void) gameFactsViewController:(GameFactsViewController *)controller didFinishEditingGame:(Game *)game
{
//edit already made in data model in GFVC
//need to update display
NSInteger index = [_games indexOfObject: game];//locate the item being edited in the games array
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];//get the right cell
[self configureDataForCell:cell withGame:game];//puts the game data into the labels in the cell
[self saveGames];//write the current data to the file
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void) playerActionsViewController: (PlayerActionsViewController *) controller didEditGameData: (Game *) game
{
NSLog(@"Paased back shots %ld", (long) game.shotsOnGoal);
[self saveGames];
}
Now for data saving and loading. I'm using this code:
- (void) loadGames
{
NSString *path = [self dataFilePath];//for convenience below
//if there is already a data file, unarchive/decode and load games array
//else create an empty arry to hold games
if ([[NSFileManager defaultManager] fileExistsAtPath: path]) {
NSData *data = [[NSData alloc] initWithContentsOfFile: path];//data structure created and loaded with file data
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData: data];//archiver created and connected to data
_games = [unarchiver decodeObjectForKey:@"Games"];
[unarchiver finishDecoding];//data now in games array
} else {
_games = [[NSMutableArray alloc] initWithCapacity:50];
}
}
- (void) saveGames
{
NSMutableData *data = [[NSMutableData alloc] init];//data structure to hold the data to be saved after encoding
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:_games forKey:@"Games"];//I believe key here needs to match class name that will be saved. It tells archiver how to encode object properly
[archiver finishEncoding];//finish encoding, with data now in data structure
[data writeToFile:[self dataFilePath] atomically:YES];//write data structure to file determined above
}
Somewhere here there is a difference in the _games array, I guess, between the two situations, but I'm not seeing it. Or it's some other problem.
Thanks.
The problem was in my Games class. I forgot to include the keys to encode and decode the NSIntegers using
[aCoder encodeInteger:self.shotsOnGoal forKey:@"ShotsOnGoal"];
self.shotsOnGoal = [aDecoder decodeIntegerForKey:@"ShotsOnGoal"];
Etc.