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.
Related
In one class called LevelSelectViewController, I have this public property
#property (nonatomic, strong, getter=getLevelNumber) NSNumber *levelNumber;
which stores an int value based on a UIButton* touch selection using the method
- (IBAction)buttonPressedSoWhatNumber:(id)sender
{
UIButton *button = (UIButton *)sender;
int row = button.tag;
_levelNumber = [NSNumber numberWithInt:row];
}
When I put a breakpoint at the end of the method to see if my touch interaction triggers the correct result based on what I coded (when I press button 1, really), _levelNumber reads 0 (which it should). I also have a getter method written out for it.
Now, in this second class called GameViewController, I have a method setUpBoards which (should) obtain that value for *levelNumber. It looks like this:
- (void)setUpBoards {
LevelSelectViewController* level = [[LevelSelectViewController alloc] init];
[level getLevelNumber];
[self createLevelModel:(int)level];
}
In that same class, the method createLevelModel:(int)levelIndex uses that value to be passed to 5 initialization methods that access a Levels.plist file to load data for my game.
Basically, that number represents what level button I pressed and uses that number to load the correct level. In another manner, I have verified that those 5 initialization methods work along with loading data from my Levels.plist file.
Now, between the transition from LevelSelectViewController to GameViewController, I receive the NSRangeException error message:
'NSRangeException', reason: '-[__NSCFArray objectAtIndex:]: index (554166800) beyond bounds (1)'
even when pressing the 1 button (which should work considering I only have Item 0 in my plist typed out.......which, again, I verified worked using another manner).
TO ADD ON TO THIS. Here's another important method:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"cvCell";
CVCell *cell = (CVCell *)[collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
NSMutableArray *data = [self.dataArray objectAtIndex:indexPath.section];
NSString *cellData = [data objectAtIndex:indexPath.row];
[cell.buttonClick setTag:indexPath.row];
[cell.buttonClick addTarget:self action:#selector(buttonPressedSoWhatNumber:)
forControlEvents:UIControlEventTouchUpInside];
[cell addSubview:cell.buttonClick];
return cell;
}
Any insight?
Here's the push controller method from LevelSelectViewController to GameViewController:
-(IBAction)buttonPressed:(id)sender {
GameViewController* obj = [[GameViewController alloc] initWithNibName:#"GameViewController" bundle:nil];
[self.navigationController pushViewController:obj animated:YES];
}
buttonPressed: is another method given to the UIButton*
You need a simple int property in LevelSelectViewController that you can use to store the level that has been selected:
#property int levelSelected;
The store the selected value in your button press handler:
- (IBAction)buttonPressedSoWhatNumber:(UIButton *)sender
{
self.levelSelected = sender.tag;
}
Then you can pass this to a corresponding int property on your GameViewController;
-(IBAction)buttonPressed:(id)sender {
GameViewController* obj = [[GameViewController alloc] initWithNibName:#"GameViewController" bundle:nil];
obj.level = self.selectedLevel
[self.navigationController pushViewController:obj animated:YES];
}
The problem is that you are casting a pointer to an int.
#property (nonatomic, strong, getter=getLevelNumber) NSNumber *levelNumber;
Defines a pointer to an object of type NSNumber.
[self createLevelModel:(int)level];
Is casting that NSNumber * to an int.
You also have another bug in that you are setting level as the view controller and calling getLevelNumber but not actually using the returned value. So here is what I would do. Firstly you don't need to define an NSNumber and don't need a custom getter. Just use this:
#property (nonatomic, assign) int levelNumber;
Then this becomes much simple:
- (void)setUpBoards {
LevelSelectViewController* levelSelectViewController = [[LevelSelectViewController alloc] init];
[self createLevelModel: levelSelectViewController.levelNumber]; // Always going to be zero at this point.
}
I am new in programming and I have problem with moving objects from one VC to another.
My second VC have NSArray * objects. Third VC have NSMutableArray *products. I have modal segue from 2 -> 3
Here is my segue method:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([segue.identifier isEqualToString:#"Products segue"]) {
ProductsViewController*pvc = segue.destinationViewController;
pvc.products = [self.objects mutableCopy];
}
}
In ProductsViewController i create few objects:
-(IBAction)addSomeObjects {
products = [NSMutableArray new];
[products addObject:#"Cola"];
[products addObject:#"Pepsi"];
[products addObject:#"Fanta"];
[products addObject:#"Red bull"];
[products addObject:#"Monster"];
If I NSLog my IBAction method products successfully added my objects but when I dissmissViewController my objects array is empty.
Thanks for any help.
M.
Koptak. Welcome to programming.
In this example, I would suggest you create a model to manage products. A model is a class that represents data. In this example, create a class to represent the collection of products. For this I will call it a catalog.
// In Catalog.h
#interface Catalog : NSObject
#property (nonatomic, readonly) NSMutableArray *products;
#end
// In Catalog.m
#implementation Catalog
- (id)init
{
self = [super init];
if (self) {
_products = [NSMutableArray array];
}
}
#end
Now that I have a class which can manage products, I need to have catalog properties (of type Catalog) in both the first view controller and ProductsViewController.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"Products segue"]) {
ProductsViewController *pvc = segue.destinationViewController;
pvc.catalog = self.catalog; // Pass the catalog between the view controllers.
}
}
- (void)viewDidLoad
{
…
if (self.catalog == nil) // Make sure an instance of `Catalog` has been created!
self.catalog = [[Catalog alloc] init];
…
}
Finally, in ProductsViewController
- (IBAction)addSomeObjects
{
[self.catalog.products addObject:#"Cola"];
[self.catalog.products addObject:#"Pepsi"];
[self.catalog.products addObject:#"Fanta"];
[self.catalog.products addObject:#"Red bull"];
[self.catalog.products addObject:#"Monster"];
}
Now when you dismiss ProductsViewController, the first view controller will have all the new products.
Note: This is the very first step in how to share data. It skips proper class names, data protection, and data validation, but it gets you started.
Firstly, I've already tried to search for solutions online but none works for me and I'm thinking since I'm using ECSlidingViewController to navigate around the app, I can't utilise the prepareForSegue method thus, my problem may need a different approach.
I have a class called viewInits which holds properties in the .h file that I want allow other classes to set and get it's values. In this case, the property is an NSString *navBarTitle.
In ClassA, I have a tableView:didSelectRowAtIndexPath: method, where I
Create an ViewInits class object - *viewInits.
I then set the setNavBarTitle: to the value of [self.MenuRowsArray objectAtIndex:indexPath.row].
In the next line, I did an NSLog to check and yes, viewInits.navBarTitle now holds the value I desire.
In ClassB's viewDidloadMethod, similarly, I created a ViewInits object - *viewInits and did an NSLog check for viewInits.navBarTitle. But it returns (null). What seems to be the problem here?
Here is the code for how I'm trying to pass the NSString. What am I doing wrong?
viewInit .h
#interface ViewInits : NSObject
#property (strong, nonatomic) NSString *navBarTitle;
#end
ClassA.m tableView:didSelectRowAtIndexPath: method:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *identifier = [self.MenuRowsArray objectAtIndex:indexPath.row];
UIViewController *newTopViewController = [self.storyboard instantiateViewControllerWithIdentifier:identifier];
// *---------- Assign identifier to NSString viewInits ----------*
ViewInits *viewInits = [[ViewInits alloc] init];
[viewInits setNavBarTitle:identifier];
NSLog(#"%#", viewInits.navBarTitle);
// *---------- Assign identifier to NSString viewInits ----------*
[self.slidingViewController anchorTopViewOffScreenTo:ECRight animations:nil onComplete:^
{
CGRect frame = self.slidingViewController.topViewController.view.frame;
self.slidingViewController.topViewController = newTopViewController;
self.slidingViewController.topViewController.view.frame = frame;
[self.slidingViewController resetTopView];
}];
}
ClassB.m
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
// *========== ECSlidingViewController ==========*
self.view.layer.shadowOpacity = 0.75f;
self.view.layer.shadowRadius = 10.0f;
self.view.layer.shadowColor = [UIColor blackColor].CGColor;
if (![self.slidingViewController.underLeftViewController isKindOfClass:[MenuViewController class]])
{
self.slidingViewController.underLeftViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"Menu"];
}
[self.view addGestureRecognizer:self.slidingViewController.panGesture];
// *========== ECSlidingViewController ==========*
ViewInits *viewInits = [[ViewInits alloc] init]; // Create ViewInit class object
self.navBar.topItem.title = viewInits.navBarTitle;
NSLog(#"%#", viewInits.navBarTitle); // <<--- This always ends up null. What's wrong?
}
Your help are much appreciated. Thank you.
If you want to use ViewInit as a common store of settings it should be a singleton so that all other instances in the app can get it. Currently you're creating a new instance each time you want to use it, so the new instance doesn't have any of your previous settings.
Aside, I know what the sliding view controller is, I ask about it because you may be using it incorrectly. If you have a view controller which is the current top view controller and it changes the top view controller (class A might be doing this, not sure) then the reference self.slidingViewController will stop working part way through your code.
In the iOS application, I need to pass an object from one view controller to another and then switch to the new view controller. The goal is to simply relay the information. However, the object becomes NULL and is devoid of any information when the next view showed up. what? could be the reason?
Below is part of my code.
#pragma mark - Table view delegate
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
BIDCourse *course = self.courses[indexPath.row];
NSString *sub = course.subject;
BIDSubjectMainTwoViewController *subjectController=[[BIDSubjectMainTwoViewController alloc] init];
subjectController.title = sub;
subjectController.course=course;
[self.navigationController pushViewController:subjectController animated:YES];
}
BIDCourse *course is my custom subclass to NSObject, which has some NSStrings, and it's not nil, but when I pass it onto the next view controller, it becomes NULL.
I have met a problem like you , when I pass a object to another viewController , it has been released.
I think this is a defect of ARC , ARC think your subjectController may be no use , so it release the subjectController. Make your subjectController as a property , so ARC will not release when you pass it to another viewController.
such as :
#pragma mark - Table view delegate
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
BIDCourse *course = self.courses[indexPath.row];
NSString *sub = course.subject;
_subjectController=[[BIDSubjectMainTwoViewController alloc] init];
_subjectController.title = sub;
_subjectController.course=course;
[self.navigationController pushViewController:_subjectController animated:YES];
}
Could the problem lie in BIDSubjectMainTwoViewController.m?
subjectController.title = sub;
subjectController.course = course;
in your BIDSubjectMainTwoViewController.m file, did you #synthesize your properties?
#synthesize title = _title;
#synthesize course = _course;
You could also pass the variables along with an init function ala:
BIDSubjectMainTwoViewController.h
- (id)initWithTitle:(NSString *)title
andCourse:(BIDCourse *)course;
BIDSubjectMainTwoViewController.m
- (id)initWithTitle:(NSString *)title
andCourse:(BIDCourse *)course
{
self = [super init];
if (self) {
// Custom initialization
_title=title;
_course = course;
}
return self;
}
I encountered this similar behavior. My solution was to make a copy of the object. For your case:
BIDCourse *course = [self.courses[indexPath.row] copy];
And make sure you implement copyWithZone in the BIDCourse .m fike.
-(id)copyWithZone:(NSZone*) zone
Try this:
BIDSubjectMainTwoViewController.h
NSString *strTemp;
#property(nonatomic,retain) NSString *strTemp;
-(void) setValuestrTemp : (NSString *) str;
.m file
#synthesize strTemp;
// Write setter method
-(void) setValuestrTemp : (NSString *) str {
self.strTemp = str;
}
and use strTemp in viewWillAppear.
//Now use it
BIDSubjectMainTwoViewController *subjectController=[[BIDSubjectMainTwoViewController alloc] init];
[subjectController setValuestrTemp:sub];
[self.navigationController pushViewController:_subjectController animated:YES];
Use
NSString *sub = [course.subject copy];
subjectController.course=[course copy];
implement delegate in BIDCourse
-(id)copyWithZone:(NSZone*) zone
I'm implementing a uinavigationcontroller. The first view is a uitableview (imagine the Contacts app) with a list of names.
The second view is the person profile.
So, when I click a person in the uitable, it's suppose to load his profile.
How do I pass the person data to the second view?
In didSelectRowAtIndexPath I do:
ContactView * varContactView = [[ContactView alloc] initWithNibName:nil bundle:nil];
varContactView.title = [[contactsArray objectAtIndex:indexPath.row] name];
[varContactView initWithPerson:[contactsArray objectAtIndex:indexPath.row]];
[navigationController pushViewController:varContactView animated:YES];
In the interface of ContactView I've got:
Person * person;
And then:
#property (nonatomic, retain) Person * person;
-(void) initWithPerson:(Person *)newperson;
And in .m:
#synthesize person
-(void) initWithPerson:(Person *)newperson{
person = [[Person alloc] init];
person = newperson;
}
However, when I try to access person in ContactView, it says always EXC_BAD_ACCESS.
What is wrong here?
Instead of:
[varContactView initWithPerson:[contactsArray objectAtIndex:indexPath.row]];
you can simply use:
varContactView.person = [contactsArray objectAtIndex:indexPath.row];
That will make use of person property setter and assign the given object as varContactView's data. The default implementation of that setter is (in case of retain property):
- (void)setPerson:(Person *)newPerson
{
if (person != newPerson) {
[person release];
person = [newPerson retain];
}
}
That's what you're trying to achieve in -initWithPerson: method. That method is not needed, as its functionality is covered by person property setter. BTW, remember to release person property in -dealloc method of your view controller:
- (void)dealloc
{
[person release];
[super dealloc];
}
The bad access exception might be caused by something else in your code, though...
In .m file, change the code as given below.
-(void) initWithPerson:(Person *)newperson
{
self.person = newperson;
}