Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
The Goal and Problem:
After every passing second, the value of 'regularBubbleCount' - a variable of RWGameData - increases by 1. I am attempting to display this change in value by passing the new value of 'regularBubbleCount' to the 'regularBubLabel' UILabel in the PrimaryViewController. I am attempting to do this by using the following line of code,
_regularBubLabel.text = [NSString stringWithFormat:#"%li", [RWGameData sharedGameData].regularBubbleCount];
Obviously this does not work because 'regularBubLabel' is not an object of the RWGameData class where the 'timerCalled' method resides. How can I change the value of the 'regularBubLabel' from inside the RWGameData class?
RWGameData.h
#import <Foundation/Foundation.h>
#interface RWGameData : NSObject <NSCoding>
#property (assign, nonatomic) long regularBubbleCount;
#property (assign, nonatomic) BOOL dataIsInitialized;
+(instancetype)sharedGameData;
-(void)reset;
-(void)save;
-(void)timerSetup;
-(void)timerCalled;
#end
RWGameData.m
#import "RWGameData.h"
#implementation RWGameData
static NSString* const SSGameDataRegularBubbleCountKey = #"regularBubbleCount";
static NSString* const SSGameDataIsInitializedKey = #"dataIsInitializedKey";
+ (instancetype)sharedGameData {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [self loadInstance];
});
return sharedInstance;
}
-(void)reset {
self.regularBubbleCount = 0;
self.dataIsInitialized = true;
}
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeDouble:self.regularBubbleCount forKey: SSGameDataRegularBubbleCountKey];
[encoder encodeBool:self.dataIsInitialized forKey: SSGameDataIsInitializedKey];
}
- (instancetype)initWithCoder:(NSCoder *)decoder
{
self = [self init];
if (self) {
_regularBubbleCount = [decoder decodeDoubleForKey: SSGameDataRegularBubbleCountKey];
_dataIsInitialized = [decoder decodeBoolForKey: SSGameDataIsInitializedKey];
}
return self;
}
+(NSString*)filePath
{
static NSString* filePath = nil;
if (!filePath) {
filePath =
[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]
stringByAppendingPathComponent:#"gamedata"];
}
return filePath;
}
+(instancetype)loadInstance
{
NSData* decodedData = [NSData dataWithContentsOfFile: [RWGameData filePath]];
if (decodedData) {
RWGameData* gameData = [NSKeyedUnarchiver unarchiveObjectWithData:decodedData];
return gameData;
}
return [[RWGameData alloc] init];
}
-(void)save
{
NSData* encodedData = [NSKeyedArchiver archivedDataWithRootObject: self];
[encodedData writeToFile:[RWGameData filePath] atomically:YES];
}
- (void)timerSetup { // to be called from delegate didFinishLaunching….
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(timerCalled) userInfo:nil repeats:YES];
}
-(void)timerCalled
{
[RWGameData sharedGameData].regularBubbleCount++;
/* THE ISSUE IS HERE */
_regularBubLabel.text = [NSString stringWithFormat:#"%li", [RWGameData sharedGameData].regularBubbleCount];
[[RWGameData sharedGameData] save];
} NSLog(#"Regular Bubble Count: %li", [RWGameData sharedGameData].regularBubbleCount);
}
#end
PrimaryViewController.h
#import <UIKit/UIKit.h>
#import "RWGameData.h"
#interface PrimaryViewController : UIViewController
#property (strong, nonatomic) IBOutlet UILabel *regularBubLabel;
#end
PrimaryViewController.m
#import "PrimaryViewController.h"
#interface PrimaryViewController ()
#end
#implementation PrimaryViewController
{
NSString *bubbleImage;
UIImage *backgroundImage;
UIImageView *backgroundImageView;
int r;
int i;
}
- (void)viewDidLoad {
[super viewDidLoad];
backgroundImage = [UIImage imageNamed:#"background_new.png"];
backgroundImageView=[[UIImageView alloc]initWithFrame:self.view.frame];
backgroundImageView.image=backgroundImage;
[self.view insertSubview:backgroundImageView atIndex:0];
}
- (void)viewDidAppear:(BOOL)animated {
_regularBubLabel.text = [NSString stringWithFormat:#"%li", [RWGameData sharedGameData].regularBubbleCount];
_premiumBubLabel.text = [NSString stringWithFormat:#"%li", [RWGameData sharedGameData].premiumBubbleCount];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)increment {
if ([RWGameData sharedGameData].megaBubblePopValue == 0) {
[RWGameData sharedGameData].megaBubblePopValue++;
[[RWGameData sharedGameData] save];
}
if ([#"mysterybubble.png" isEqual:bubbleImage]) {
[RWGameData sharedGameData].premiumBubbleCount += 2;
_premiumBubLabel.text = [NSString stringWithFormat:#"%li", [RWGameData sharedGameData].premiumBubbleCount];
} else if ([#"megaBubbleLarge30.png" isEqual:bubbleImage]) {
[RWGameData sharedGameData].regularBubbleCount += [RWGameData sharedGameData].megaBubblePopValue;
_regularBubLabel.text = [NSString stringWithFormat:#"%li", [RWGameData sharedGameData].regularBubbleCount];
} i++;
}
- (IBAction)save {
[[RWGameData sharedGameData] save];
}
- (IBAction)setBubbleStatus {
r = arc4random_uniform(400);
if (r <= 1) {
bubbleImage = #"mysterybubble.png";
[_megaBubbleButton setImage:[UIImage imageNamed:bubbleImage] forState:UIControlStateNormal];
NSLog(#"Roll SUCCESS. [%i] %i", i, r);
} else {
bubbleImage = #"megaBubbleLarge30.png";
[_megaBubbleButton setImage:[UIImage imageNamed:bubbleImage] forState:UIControlStateNormal];
NSLog(#"Roll FAIL. [%i] %i", i, r);
}
}
#end
It makes no sense to have an IBOutlet in your RWGameData class, since it's a subclass of NSObject. That class has no instance (and no view) in the storyboard, so you can't hook up an IBOutlet. One way to accomplish your goal would be to make PrimaryViewController a delegate of RWGameData, and call a delegate method inside the timer's action method that would pass whatever data you need so PrimaryViewController can update its own label.
I am going to make the following assumptions:
Assumption #1 -RWGameData is instantiated inside the ApplicationDelegate when the application is loaded and can be referenced in the application delegate as self.gameData
Assumption #2 - PrimaryViewController is, as you have named it, the primary view controller in your storyboard.
Assumption #3 - You're using an NSTimer to update this count every 1 second.
Assumption #4 - Your Storyboard file is called Mainstorybaord
RWGameData
Add this to your RWGameData.h before your class definition.
#class RWGameData;
#protocol RWGameStateProtocol <NSObject>
-(void)StateUpdateForGameData:(RWGameData*)data;
#end
Inside RWGameData.h add a delegate in your class definition.
#interface RWGameData : NSObject
#property(weak)id<RWGameStateProtocol> delegate;
#end
In your selector timerCalled add a call to your delegate informing it the data has changed.
-(void)timerCalled
{
[RWGameData sharedGameData].regularBubbleCount++;
/* THE ISSUE IS HERE */
_regularBubLabel.text = [NSString stringWithFormat:#"%li", [RWGameData sharedGameData].regularBubbleCount];
[[RWGameData sharedGameData] save];
} NSLog(#"Regular Bubble Count: %li", [RWGameData sharedGameData].regularBubbleCount);
/* Inform the delegate */
[self.delegate StateUpdateForGameData:self];
}
PrimaryViewController
Once you have your protocol defined have your PrimaryViewcontroller conform to this protocol with something similar to the following.
-(void)StateUpateforGameData:(RWGameData*)data
{
self.regularBubLabel.text = [[NSString alloc]initWithFormat:#"%i",data.regularBubbleCount];
}
Application Delegate
All that remains is to set the delegate on your RWGameData instance. You can do this by asking storyboard to provide you an instance of your primary view controller. If you're using storyboard then by default this method is empty. However, since we are going to modify the primary view controller before it is shown then we need to make a few changes and perform some of the work Storyboard already does for you.
In your ApplicationDelegate's header file create a property for a UIWindow and RWGameData.
#interface YourAppDelegate : UIResponder <UIApplicationDelegate>
#property(strong,nonatomic) UIWindow *window;
#property(strong,nonatomic) RWGameData *gameData;
#end
Now that we have the properties setup you need to perform some additional work. Note that normally you don't have to perform this step when using storyboard.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
/// Screen Information
CGFloat scale = [[UIScreen mainScreen]scale];
CGRect frame = [[UIScreen mainScreen]bounds];
UIWindow* window = [[UIWindow alloc]initWithFrame:frame];
self.window = window;
self.window.contentScaleFactor = scale;
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
PrimaryViewController *vc = (PrimaryViewController*)[sb instantiateInitialViewController];
RWGameData *gameData = [[RWGameData alloc]init];
gameData.delegate = vc;
self.gameData = gameData;
[window setRootViewController:vc];
[window makeKeyAndVisible];
return YES;
}
This approach doesn't require you to tightly couple your label and game data. Instead any update to your game data can now inform your view and allows your view to select which labels need to be updated.
Related
I know one way to share data is segue. But in my application I have multiple tabs which contain number of VCs. For instance userName and address. I want to show in some of the VCs these infos.
Every time I query the cloud is not right way. I am following this answer first part: answer. But as a newbie I am not sure how MyDataModel is defined. Is it a NSObject class? I appreciate if anyone can define this class as example with two NSString fields. And how to access these fields in VC and AppDelegate.
Inside AppDelegate
#interface MyAppDelegate : NSObject <UIApplicationDelegate, UITabBarControllerDelegate>
{
MyDataModel *model;
AViewController *aViewController;
BViewController *bViewController;
...
}
#property (retain) IBOutlet AViewController *aViewController;
#property (retain) IBOutlet BViewController *aViewController;
#end
#implementation MyAppDelegate
...
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
...
aViewController.model = model;
bViewController.model = model;
[window addSubview:tabBarController.view];
[window makeKeyAndVisible];
}
Inside VC:
#interface AViewController : UIViewController {
MyDataModel *model;
}
#property (nonatomic, retain) MyDataModel *model;
#end
#interface BViewController : UIViewController {
MyDataModel *model;
}
#property (nonatomic, retain) MyDataModel *model;
#end
The only thing I need is where to define MyDataMode and how to access its fields?
You can use singleton class for that,
----------
SharedManages.h
----------
#import <Foundation/Foundation.h>
#import "Reachability.h"
#import "Reachability.h"
#interface SharedManager : NSObject
{
}
+(SharedManager *)sharedInstance;
// Create property of your object which you want to access from whole over project.
#property (retain, nonatomic) User *loginUser;
#property (assign, readwrite) BOOL isNetAvailable;
#end
----------
----------
SharedManages.m
----------
#import "SharedManager.h"
static SharedManager *objSharedManager;
#implementation SharedManager
#synthesize
isNetAvailable = _isNetAvailable,
loginUser = _ loginUser;
+(SharedManager *)sharedInstance
{
if(objSharedManager == nil)
{
objSharedManager = [[SharedManager alloc] init];
objSharedManager. loginUser = [User alloc]] init];
Reachability *r = [Reachability reachabilityForInternetConnection];
NetworkStatus internetStatus = [r currentReachabilityStatus];
// Bool
if(internetStatus == NotReachable)
{
NSLog(#"Internet Disconnected");
objSharedManager.isNetAvailable = NO; // Internet not Connected
}
else if (internetStatus == ReachableViaWiFi)
{
NSLog(#"Connected via WIFI");
objSharedManager.isNetAvailable = YES; // Connected via WIFI
}
else if (internetStatus == ReachableViaWWAN)
{
NSLog(#"Connected via WWAN");
objSharedManager.isNetAvailable = YES; // Connected via WWAN
}
}
return objSharedManager;
}
#end
Access from other Class...
[SharedManager sharedInstance].isNetAvailable ;
[SharedManager sharedInstance].loginUser ;
Hope, This will help you..
I don't have "local" copies of the values. I set them in the delegate and fetch them from there. That way you don't have to hard code it for all UIViewController's in the delegate.
Assigning values is best done on the first view, or with default values. I personally use viewDidLoad for those kind of things. Since it is only called once on the first view once and pertains until the app is terminated.
Then I get the delegate from inside the VC, call the instance and from there the values.
Swift
Inside AppDelegate:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var globals : GlobalValueClass?
First VC:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let delegate = UIApplication.sharedApplication().delegate as! AppDelegate
delegate.globals = GlobalValueClass()
delegate.globals!.numbers = [1,2,3]
}
}
Other VC's:
class ViewControllerTwo: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let delegate = UIApplication.sharedApplication().delegate as! AppDelegate
print(delegate.globals!.numbers)
// Do any additional setup after loading the view.
}
}
Objective C ( don't have the full method in obj-c, but easy to find)
MainClass *appDelegate = (MainClass *)[[UIApplication sharedApplication] delegate];
how to get the delegate in obj-c
Easiest way i could think of is using NSUserDefaults. Save your name and address string in NSUserDefaults like
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setValue:YourNameString forKey:#"NameString"];
[defaults setValue:YourAddressString forKey:#"AddressString"];
[defaults synchronize];
and access it in any ViewController as
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *name = [plistContent valueForKey:#"NameString"];
NSString *address= [plistContent valueForKey:#"AddressString"];
Hope this helps.
You can create a class and use it in your tab controller.
#import <Foundation/Foundation.h>
#interface UserModel : NSObject <NSCoding>
#property (nonatomic, strong) NSString *lastName;
#property (nonatomic, strong) NSString *firstName;
+ (instancetype)modelObjectWithDictionary:(NSDictionary *)dict;
- (instancetype)initWithDictionary:(NSDictionary *)dict;
- (NSDictionary *)dictionaryRepresentation;
#end
implementation file
#import "UserModel.h"
NSString *const kUserModelLastName = #"LastName";
NSString *const kUserModelFirstName = #"FirstName";
#interface UserModel ()
- (id)objectOrNilForKey:(id)aKey fromDictionary:(NSDictionary *)dict;
#end
#implementation UserModel
#synthesize lastName = _lastName;
#synthesize firstName = _firstName;
+ (instancetype)modelObjectWithDictionary:(NSDictionary *)dict
{
return [[self alloc] initWithDictionary:dict];
}
- (instancetype)initWithDictionary:(NSDictionary *)dict
{
self = [super init];
// This check serves to make sure that a non-NSDictionary object
// passed into the model class doesn't break the parsing.
if(self && [dict isKindOfClass:[NSDictionary class]]) {
self.lastName = [self objectOrNilForKey:kUserModelLastName fromDictionary:dict];
self.firstName = [self objectOrNilForKey:kUserModelFirstName fromDictionary:dict];
}
return self;
}
- (NSDictionary *)dictionaryRepresentation
{
NSMutableDictionary *mutableDict = [NSMutableDictionary dictionary];
[mutableDict setValue:self.lastName forKey:kUserModelLastName];
[mutableDict setValue:self.firstName forKey:kUserModelFirstName];
return [NSDictionary dictionaryWithDictionary:mutableDict];
}
- (NSString *)description
{
return [NSString stringWithFormat:#"%#", [self dictionaryRepresentation]];
}
#pragma mark - Helper Method
- (id)objectOrNilForKey:(id)aKey fromDictionary:(NSDictionary *)dict
{
id object = [dict objectForKey:aKey];
return [object isEqual:[NSNull null]] ? nil : object;
}
#pragma mark - NSCoding Methods
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
self.lastName = [aDecoder decodeObjectForKey:kUserModelLastName];
self.firstName = [aDecoder decodeObjectForKey:kUserModelFirstName];
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:_lastName forKey:kUserModelLastName];
[aCoder encodeObject:_firstName forKey:kUserModelFirstName];
}
Import this class where you are setting or initializing data or values. and do below code.
NSDictionary *dicUserModel = [[NSDictionary alloc]initWithObjectsAndKeys:#"Moure",#"LastName",#"Jackson",#"FirstName", nil];
UserModel *userModel = [[UserModel alloc]initWithDictionary:dicUserModel];
//NSUserDefault save your class with all property. and you can simply retrieve your UserModel from NSUserDefault.
//Below code save this model into nsuserdefault.
[[NSUserDefaults standardUserDefaults]setObject:[NSKeyedArchiver archivedDataWithRootObject:userModel] forKey:#"UserModel"];
[[NSUserDefaults standardUserDefaults]synchronize];
You can retrieve you class object using below code.
UserModel *savedUserModel = (UserModel *)[NSKeyedUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults]objectForKey:#"UserModel"]];
NSLog(#"%#",savedUserModel.firstName);
NSLog(#"%#",savedUserModel.lastName);
I simplify the code for ask question.
I want to save more class and load class model file.
But now I simplify parameters to one part (lim) in the model(FlightSettingModel.h).
In the FlightSettingModel.h I have set some parameters in here.
FlightSettingModel.h code below:
#import <Foundation/Foundation.h>
#interface FlightSettingModel : NSObject<NSCoding>
// lim property part
#property float limCurrentVal;
#property float limDefaultVal;
#property float limMaxVal;
#property float limMinVal;
// .... other property part
#end
FlightSettingModel.m code below
#import "FlightSettingModel.h"
#interface FlightSettingModel()
{
}
#end
#implementation FlightSettingModel
-(instancetype)init
{
self = [super init];
if(self)
{
self.limDefaultVal = 3.0;
self.limCurrentVal = 4.0;
self.limMaxVal = 10;
self.limMinVal = 0;
// ... other property part value init .....
}
return self;
}
- (void)setFlightSettingToDefaultValue
{
self.limCurrentVal = self.limDefaultVal;
}
- (void) encodeWithCoder: (NSCoder *)encoder
{
[encoder encodeFloat:self.limCurrentVal forKey:#"limCurrentVal"];
}
- (id)initWithCoder:(NSCoder *)decoder {
if((self = [super init])) {
self.limCurrentVal = [decoder decodeFloatForKey:#"limCurrentVal"];
}
return self;
}
#end
Then I have set the singleton SettingData file to initial the FlightSettingModel and other model class.
The SettingData model header like below:
#import <Foundation/Foundation.h>
#import "FlightSettingModel.h"
#interface SettingData : NSObject
#property (nonatomic,strong) FlightSettingModel *flightSettingModel;
+(SettingData*) sharedInstance;
#end
SettingData.m code below:
#import "SettingData.h"
#implementation SettingData
SettingData *sharedInstance;
+(SettingData*) sharedInstance{
if( sharedInstance == nil )
{
sharedInstance = [SettingData new];
}
return sharedInstance;
}
-(id) init{
self = [super init];
if( self )
{
self.flightSettingModel = [FlightSettingModel new];
}
return self;
}
#end
In my storyboard (UI) is like below:
When I click the save button , I want to save the custom class model(FlightSettingModel.h) in the NSKeyedArchiver. When I click the load, I want to load the model from the archiver using NSKeyedUnarchiver and resetting to the slider.
But now,when I drag the slider to other value(ex:10), then I click the save, then I close the app restart the app. I click the load, the slider value will become 0.
I don't know why when I load the value the all value will become 0;
My view controller code .m below:
#import "ViewController.h"
#import "SettingData.h"
#interface ViewController ()
{
NSString *path;
NSString *fileName;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// file manage
path =[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES) objectAtIndex:0];
fileName = [path stringByAppendingPathComponent:#"flightFile"];
[self setUIValue];
NSLog(#"---");
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (IBAction)saveAction:(UIButton *)sender {
[NSKeyedArchiver archiveRootObject:[SettingData sharedInstance].flightSettingModel toFile:fileName];
}
- (IBAction)loadAction:(UIButton *)sender {
[SettingData sharedInstance].flightSettingModel = (FlightSettingModel*) [NSKeyedUnarchiver unarchiveObjectWithFile: fileName];
[self setUIValue];
NSLog(#"current Value:%.f",[SettingData sharedInstance].flightSettingModel.limCurrentVal);
}
- (IBAction)sliderChangedAction:(UISlider *)sender {
[SettingData sharedInstance].flightSettingModel.limCurrentVal = sender.value;
self.theTextField.text = [NSString stringWithFormat:#"%.f",self.theSlider.value];
}
-(void) setUIValue
{
// setting slider property
self.theSlider.maximumValue = [SettingData sharedInstance].flightSettingModel.limMaxVal;
self.theSlider.minimumValue = [SettingData sharedInstance].flightSettingModel.limMinVal;
self.theSlider.value = [SettingData sharedInstance].flightSettingModel.limCurrentVal;
self.theTextField.text = [NSString stringWithFormat:#"%.f",self.theSlider.value];
}
#end
Have anyone know where my problem in my code?
thank you very much.
If you want to download the complete code(the question code) , I have upload in github
This behavior happens because the [SettingData sharedInstance].flightSettingModel.limMaxVal and the [SettingData sharedInstance].flightSettingModel.limMinVal are zero:
-(void) setUIValue
{
// setting slider property
self.theSlider.maximumValue = [SettingData sharedInstance].flightSettingModel.limMaxVal;
// self.theSlider.maximumValue = 0
self.theSlider.minimumValue = [SettingData sharedInstance].flightSettingModel.limMinVal;
// self.theSlider.minimumValue = 0
self.theSlider.value = [SettingData sharedInstance].flightSettingModel.limCurrentVal;
// [SettingData sharedInstance].flightSettingModel.limCurrentVal = 10
self.theTextField.text = [NSString stringWithFormat:#"%.f",self.theSlider.value];
}
EDIT: You can fix it by adding this:
- (void) encodeWithCoder: (NSCoder *)encoder {
[encoder encodeFloat:self.limCurrentVal forKey:#"limCurrentVal"];
[encoder encodeFloat:self.limMaxVal forKey:#"limMaxVal"];
[encoder encodeFloat:self.limMinVal forKey:#"limMinVal"];
}
- (id)initWithCoder:(NSCoder *)decoder {
if((self = [super init])) {
self.limCurrentVal = [decoder decodeFloatForKey:#"limCurrentVal"];
self.limMaxVal = [decoder decodeFloatForKey:#"limMaxVal"];
self.limMinVal = [decoder decodeFloatForKey:#"limMinVal"];
}
return self;
}
The variable 'bubbleBankCapacity' is expected to return as 500 and be compared to the 'regularBubbleCount' variable in the comparison below, but instead returns as 0 which effectively makes the condition false given that the regularBubbleCount is greater than 0. Why is this returning a 0 when it has been set to initialize as 500?
AppDelegate.m
#import "AppDelegate.h"
#interface AppDelegate ()
#end
#implementation AppDelegate
-(void)timerCalled
{
NSLog(#"Timer Called");
if ([RWGameData sharedGameData].bubbleFactoryUpgradeTier > 0) {
if ([RWGameData sharedGameData].regularBubbleCount < [RWGameData sharedGameData].bubbleBankCapacity) {
NSLog(#"bubbleFactoryUpgradeTier: %li", [RWGameData sharedGameData].bubbleFactoryUpgradeTier);
[RWGameData sharedGameData].regularBubbleCount += [RWGameData sharedGameData].bubbleFactoryTickValue;
NSLog(#"bubbleFactoryTickValue: %i", [RWGameData sharedGameData].bubbleFactoryTickValue);
[[RWGameData sharedGameData] save];
} else {
NSLog(#"Capacity Reached! Capacity: %li", [RWGameData sharedGameData].bubbleBankCapacity);
}
} NSLog(#"Regular Bubble Count: %li", [RWGameData sharedGameData].regularBubbleCount);
}
RWGameData.h
#import <Foundation/Foundation.h>
#interface RWGameData : NSObject <NSCoding>
#property (assign, nonatomic) long regularBubbleCount;
#property (assign, nonatomic) long premiumBubbleCount;
#property (assign, nonatomic) long megaBubbleUpgradeTier;
#property (assign, nonatomic) long bubbleFactoryUpgradeTier;
#property (assign, nonatomic) long bubblersUpgradeTier;
#property (assign, nonatomic) long mysteryBubbleUpgradeTier;
#property (assign, nonatomic) long bubbleBankUpgradeTier;
#property (assign, nonatomic) int megaBubblePopValue;
#property (assign, nonatomic) int bubbleFactoryTickValue;
#property (assign, nonatomic) long bubbleBankCapacity;
+(instancetype)sharedGameData;
-(void)reset;
-(void)save;
#end
RWGameData.m
#import "RWGameData.h"
#implementation RWGameData
static NSString* const SSGameDataRegularBubbleCountKey = #"regularBubbleCount";
static NSString* const SSGameDataPremiumBubbleCountKey = #"premiumBubbleCount";
static NSString* const SSGameDataMegaBubbleUpgradeTierKey = #"megaBubbleUpgradeTier";
static NSString* const SSGameDataBubbleFactoryUpgradeTierKey = #"bubbleFactoryUpgradeTier";
static NSString* const SSGameDataBubblersUpgradeTierKey = #"bubblersUpgradeTier";
static NSString* const SSGameDataMysteryBubbleUpgradeTierKey = #"mysteryBubbleUpgradeTier";
static NSString* const SSGameDataBubbleBankUpgradeTierKey = #"bubbleBankUpgradeTier";
static NSString* const SSGameDataMegaBubblePopValueKey = #"megaBubblePopValueKey";
static NSString* const SSGameDataBubbleFactoryTickValueKey = #"bubbleFactoryTickValueKey";
static NSString* const SSGameDataBubbleBankCapacityKey = #"bubbleBankCapacityKey";
+ (instancetype)sharedGameData {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [self loadInstance];
});
return sharedInstance;
}
-(void)reset {
self.regularBubbleCount = 0;
self.premiumBubbleCount = 0;
self.megaBubbleUpgradeTier = 0;
self.bubbleFactoryUpgradeTier = 0;
self.bubblersUpgradeTier = 0;
self.mysteryBubbleUpgradeTier = 0;
self.bubbleBankUpgradeTier = 0;
self.megaBubblePopValue = 1;
self.bubbleFactoryTickValue = 1;
self.bubbleBankCapacity = 500;
}
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeDouble:self.regularBubbleCount forKey: SSGameDataRegularBubbleCountKey];
[encoder encodeDouble:self.premiumBubbleCount forKey: SSGameDataPremiumBubbleCountKey];
[encoder encodeDouble:self.megaBubbleUpgradeTier forKey: SSGameDataMegaBubbleUpgradeTierKey];
[encoder encodeDouble:self.bubbleFactoryUpgradeTier forKey: SSGameDataBubbleFactoryUpgradeTierKey];
[encoder encodeDouble:self.bubblersUpgradeTier forKey: SSGameDataBubblersUpgradeTierKey];
[encoder encodeDouble:self.mysteryBubbleUpgradeTier forKey: SSGameDataMysteryBubbleUpgradeTierKey];
[encoder encodeDouble:self.bubbleBankUpgradeTier forKey: SSGameDataBubbleBankUpgradeTierKey];
[encoder encodeDouble:self.megaBubblePopValue forKey: SSGameDataMegaBubblePopValueKey];
[encoder encodeDouble:self.bubbleFactoryTickValue forKey: SSGameDataBubbleFactoryTickValueKey];
[encoder encodeDouble:self.bubbleBankCapacity forKey: SSGameDataBubbleBankCapacityKey];
}
- (instancetype)initWithCoder:(NSCoder *)decoder
{
self = [self init];
if (self) {
_regularBubbleCount = [decoder decodeDoubleForKey: SSGameDataRegularBubbleCountKey];
_premiumBubbleCount = [decoder decodeDoubleForKey: SSGameDataPremiumBubbleCountKey];
_megaBubbleUpgradeTier = [decoder decodeDoubleForKey: SSGameDataMegaBubbleUpgradeTierKey];
_bubbleFactoryUpgradeTier = [decoder decodeDoubleForKey: SSGameDataBubbleFactoryUpgradeTierKey];
_bubblersUpgradeTier = [decoder decodeDoubleForKey: SSGameDataBubblersUpgradeTierKey];
_mysteryBubbleUpgradeTier = [decoder decodeDoubleForKey: SSGameDataMysteryBubbleUpgradeTierKey];
_bubbleBankUpgradeTier = [decoder decodeDoubleForKey: SSGameDataBubbleBankUpgradeTierKey];
_megaBubblePopValue = [decoder decodeDoubleForKey: SSGameDataMegaBubblePopValueKey];
_bubbleFactoryTickValue = [decoder decodeDoubleForKey: SSGameDataBubbleFactoryTickValueKey];
_bubbleBankCapacity = [decoder decodeDoubleForKey: SSGameDataBubbleBankCapacityKey];
}
return self;
}
+(NSString*)filePath
{
static NSString* filePath = nil;
if (!filePath) {
filePath =
[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]
stringByAppendingPathComponent:#"gamedata"];
}
return filePath;
}
+(instancetype)loadInstance
{
NSData* decodedData = [NSData dataWithContentsOfFile: [RWGameData filePath]];
if (decodedData) {
RWGameData* gameData = [NSKeyedUnarchiver unarchiveObjectWithData:decodedData];
return gameData;
}
return [[RWGameData alloc] init];
}
-(void)save
{
NSData* encodedData = [NSKeyedArchiver archivedDataWithRootObject: self];
[encodedData writeToFile:[RWGameData filePath] atomically:YES];
}
#end
You need to initialize the bubbleBankCapacity variable in AppDelegate.m inside your method.
Solved.
I went ahead and declared a boolean property to RWGameData and tasked the reset method to set it to true when the reset method had been called. Then I went ahead and adjusted - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions like so:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
if (![RWGameData sharedGameData].dataIsInitialized) {
[[RWGameData sharedGameData] reset];
}
NSTimer *timer;
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(timerCalled) userInfo:nil repeats:YES];
return YES;
}
Thank you to everyone who offered a solution to this problem. I greatly appreciate it!
****What I am trying to do:****
I am attempting to increment the value of [RWGameData sharedGameData].regularBubbleCount by 1 for every second that passes. I have done this successfully. The problem is that I cannot call the following from within AppDelegate#timerCalled. How would I accomplish this?
_regularBubLabel.text = [NSString stringWithFormat:#"%li", [RWGameData sharedGameData].regularBubbleCount];
AppDelegate.h
#import <UIKit/UIKit.h>
#import "RWGameData.h"
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
-(void)timerCalled;
#end
AppDelegate.m
#import "AppDelegate.h"
#interface AppDelegate ()
#end
#implementation AppDelegate
-(void)timerCalled
{
NSLog(#"Timer Called");
[RWGameData sharedGameData].regularBubbleCount++;
NSLog(#"%li", [RWGameData sharedGameData].regularBubbleCount);
[[RWGameData sharedGameData] save];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(timerCalled) userInfo:nil repeats:YES];
return YES;
}
#end
PrimaryViewController.h
#import <UIKit/UIKit.h>
#import "RWGameData.h"
#interface PrimaryViewController : UIViewController
#property (strong, nonatomic) IBOutlet UILabel *regularBubLabel;
#end
PrimaryViewController.m
#import "PrimaryViewController.h"
#interface PrimaryViewController ()
#end
#implementation PrimaryViewController
{
NSString *bubbleImage;
int r;
int i;
}
- (void)viewDidLoad {
[super viewDidLoad];
_regularBubLabel.text = [NSString stringWithFormat:#"%li", [RWGameData sharedGameData].regularBubbleCount];
_premiumBubLabel.text = [NSString stringWithFormat:#"%li", [RWGameData sharedGameData].premiumBubbleCount];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)increment {
if ([#"mysterybubble.png" isEqual:bubbleImage]) {
[RWGameData sharedGameData].premiumBubbleCount += 2;
_premiumBubLabel.text = [NSString stringWithFormat:#"%li", [RWGameData sharedGameData].premiumBubbleCount];
} else if ([#"megaBubbleLarge30.png" isEqual:bubbleImage]) {
[RWGameData sharedGameData].regularBubbleCount += 50000;
_regularBubLabel.text = [NSString stringWithFormat:#"%li", [RWGameData sharedGameData].regularBubbleCount];
} i++;
}
- (IBAction)save {
[[RWGameData sharedGameData] save];
}
- (IBAction)setBubbleStatus {
r = arc4random_uniform(300);
if (r <= 12) {
bubbleImage = #"mysterybubble.png";
[_megaBubbleButton setImage:[UIImage imageNamed:bubbleImage] forState:UIControlStateNormal];
NSLog(#"Roll SUCCESS. [%i] %i", i, r);
} else {
bubbleImage = #"megaBubbleLarge30.png";
[_megaBubbleButton setImage:[UIImage imageNamed:bubbleImage] forState:UIControlStateNormal];
NSLog(#"Roll FAIL. [%i] %i", i, r);
}
}
#end
RWGameData.h
#import <Foundation/Foundation.h>
#interface RWGameData : NSObject <NSCoding>
#property (assign, nonatomic) long regularBubbleCount;
+(instancetype)sharedGameData;
-(void)reset;
-(void)save;
#end
RWGameData.m
#import "RWGameData.h"
#implementation RWGameData
static NSString* const SSGameDataRegularBubbleCountKey = #"regularBubbleCount";
+ (instancetype)sharedGameData {
static id sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [self loadInstance];
});
return sharedInstance;
}
-(void)reset {
self.regularBubbleCount = 0;
}
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeDouble:self.regularBubbleCount forKey: SSGameDataRegularBubbleCountKey];
}
- (instancetype)initWithCoder:(NSCoder *)decoder
{
self = [self init];
if (self) {
_regularBubbleCount = [decoder decodeDoubleForKey: SSGameDataRegularBubbleCountKey];
}
return self;
}
+(NSString*)filePath
{
static NSString* filePath = nil;
if (!filePath) {
filePath =
[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]
stringByAppendingPathComponent:#"gamedata"];
}
return filePath;
}
+(instancetype)loadInstance
{
NSData* decodedData = [NSData dataWithContentsOfFile: [RWGameData filePath]];
if (decodedData) {
RWGameData* gameData = [NSKeyedUnarchiver unarchiveObjectWithData:decodedData];
return gameData;
}
return [[RWGameData alloc] init];
}
-(void)save
{
NSData* encodedData = [NSKeyedArchiver archivedDataWithRootObject: self];
[encodedData writeToFile:[RWGameData filePath] atomically:YES];
}
#end
Either you need the timer to invoke a selector on the PrimaryViewController instead of the AppDelegate, or else the AppDelegate needs to store a reference to the PrimaryViewController in order to call a method on the View Controller that updates the label.
A third (less recommended) method is to send out an NSNotification when the timer fires and have multiple VC's observe and respond to the notification, but that's harder to debug later.
You didn't show the code where the instance of PrimaryViewController gets created, but normally you'd store the reference in a property so you can access it later.
If there are many such view controllers, then the code which creates the ViewControllers (is this the AppDelegate?) should have a property which is an NSMutableArray of view controllers, say myTimerViewControllers. When the timer fires, inside timerCalled, iterate through them and tell each one to update its label.
To really do this with the best pattern, you'd create a simple #protocol with one method (say, handleTimer or updateTimerLabel). Each of the VC's would be declared as supporting this protocol, and the AppDelegate would have code something like:
for(UIViewController<TimerProtocol> *vc in myTimerViewControllers) {
[vc handleTimer];
}
I was trying to solve assignment 2 from Stanford iOS7 development (Matchismo card game)
The game works fine. Now I have to add the Restart function. If the user press on the restart button, the game restarts (it deals new cards and it resets the score)
my game model is the #property (nonatomic, strong) CardMatchingGame *game;
this is the code for the CardMatchingGame.m:
#import "CardMatchingGame.h"
#import "PlayingCardDeck.h"
#interface CardMatchingGame()
#property (nonatomic, readwrite) NSInteger score;
#property (nonatomic, strong) NSMutableArray *cards;
#end
#implementation CardMatchingGame
static const int MATCH_BONUS = 4;
static const int MATCH_PENALTY = 2;
static const int COST_TO_CHOOSE = 1;
-(NSMutableArray *)cards{
if(!_cards) _cards = [[NSMutableArray alloc]init];
return _cards;
}
-(instancetype)initWithCardCount:(NSUInteger)count usingDeck:(Deck *)deck{
self = [super init];
if(self){
for(int i=0; i < count; i++){
Card *card = [deck drawRandomCard];
if(card){
[self.cards addObject:card];
} else{
self = nil;
break;
}
}
}
return self;
}
-(void)chooseCardAtIndex:(NSUInteger)index{
Card *card = [self cardAtIndex:index];
if(!card.isMatched){
if(card.isChosen){
card.chosen = NO;
} else{
for(Card *otherCard in self.cards){
if(otherCard.isChosen && !otherCard.isMatched){
int matchScore = [card match:#[otherCard]];
if(matchScore){
self.score += matchScore * MATCH_BONUS;
card.matched = YES;
otherCard.matched = YES;
} else{
self.score -= MATCH_PENALTY;
otherCard.chosen = NO;
}
break;
}
}
self.score -= COST_TO_CHOOSE;
card.chosen = YES;
}
}
}
-(Card *)cardAtIndex:(NSUInteger)index{
return (index < [self.cards count]) ? self.cards[index] : nil;
}
#end
here is my CardGameViewController.m:
#import "CardGameViewController.h"
#import "PlayingCardDeck.h"
#import "CardMatchingGame.h"
#interface CardGameViewController ()
#property (nonatomic, strong) Deck *deck;
#property (nonatomic, strong) CardMatchingGame *game;
#property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *cardsCollection;
#property (weak, nonatomic) IBOutlet UILabel *scoreLabel;
#end
#implementation CardGameViewController
#synthesize game = _game;
-(CardMatchingGame *)game{
if(!_game) _game = [[CardMatchingGame alloc] initWithCardCount:[self.cardsCollection count]
usingDeck:self.deck];
return _game;
}
-(Deck *)deck{
if(!_deck) _deck = [[PlayingCardDeck alloc] init];
return _deck;
}
- (IBAction)touchRestartButton:(id)sender {
self.game = nil;
[self updateUI];
}
- (IBAction)touchCardButton:(UIButton *)sender {
int chosenButtonIndex = [self.cardsCollection indexOfObject:sender];
[self.game chooseCardAtIndex:chosenButtonIndex];
[self updateUI];
}
-(void)updateUI{
for(UIButton *cardButton in self.cardsCollection){
int buttonIndex = [self.cardsCollection indexOfObject:cardButton];
Card *card = [self.game cardAtIndex:buttonIndex];
[cardButton setTitle:[self titleForCard:card] forState:UIControlStateNormal];
[cardButton setBackgroundImage:[self backgroundImageForCard:card] forState:UIControlStateNormal];
cardButton.enabled = !card.isMatched;
}
self.scoreLabel.text = [NSString stringWithFormat:#"Score: %d", self.game.score];
}
-(NSString *)titleForCard:(Card *)card{
return card.isChosen ? card.contents : #"";
}
-(UIImage *)backgroundImageForCard:(Card *)card{
return [UIImage imageNamed: card.isChosen ? #"cardfront" : #"cardback"];
}
#end
In order to restart the game, I think I should simply re-initialize the property CardMatchingGame *game.
This is what I tried to do, by setting self.game = nil; Then it should automatically be re-initialized in the getter of game.
This is, indeed, the solution that I found on the internet. However, in my program it doesn't work. *game is set to nil and never restored, so the game ends when you click restart.
Could you please help me to figure out why self.game = nil doesn't work in my case?
- (IBAction)startover:(UIButton *)sender {
self.game= [[CardMatchingGame alloc] initWithCardCount:[self.cardButtons count] usingDeck:[self createDeck]];
[self updateUI];
}
If your program has the lazy init style recommended, there is no need for a new method such as start over. You are correct to set self.game to nil, now a call the the getter will start a new instance of the game. I did it by making a call to UIUpdate right after the self.game = nil. That has a call to the getter and lazily inits a new instance of the game.