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;
}
Related
I'm trying to implement KVOController in my app. I managed to get it working on custom UISliders, but I can't seem to get it working on a custom NSObject. This is the object:
Restaurant.h
#import <UIKit/UIKit.h>
#interface Restaurant : NSObject
#property (nonatomic, copy, readonly) NSString *name, *tagline;
#property (nonatomic, copy, readonly) UIColor *uicolor;
- (id)initWithName:(NSString *)main;
- (void)setName:(NSString *)title;
- (void)changeColor:(UIColor)color;
#end
Restaurant.m
#implementation Restaurant
- (id)initWithName:(NSString *)main {
self = [super init];
if (self)
{
_name = main;
}
return self;
}
- (void)setName:(NSString *)title {
_name = title;
}
- (void)changeColor:(UIColor)color {
_uicolor = color;
}
#end
So, I introduced this listener into my code:
- (void)viewDidLoad {
[super viewDidLoad];
self.restaurant = [[Restaurant alloc] initWithName:#"test"];
[self.restaurant changeColor:[UIColor blueColor]];
FBKVOController *KVOController = [FBKVOController controllerWithObserver:self];
self.KVOController = KVOController;
[self.KVOController observe:self.restaurant keyPath:#"name" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew block:^(id observer, id object, NSDictionary *change) {
NSLog(#"Restaurant changed!");
}];
}
- (void)someOtherMethod:(id)sender {
[self.restaurant setName:#"Another test"];
[self.restaurant changeColor:[UIColor redColor]];
}
However, this isn't logging anything for this, nor when I change the keyPath to #"uicolor". What am I doing wrong?
I am trying to include MWPhotoBrowser in my project
When its used as given in the sample it working fine.
But when a new viewcontroller is subclassed from MWPhotoBrowser, photos are not loaded except empty black theme.
Delegate methods are not getting called. As the controller is subclass of MWPhotoBrowser, I assume there is no need to set it explicitly.
Storyboard is used and the nib class in it is set.
.h file
#interface MDRPhotoViewerController : MWPhotoBrowser
{
NSMutableArray *_selections;
}
#property (nonatomic, strong) NSMutableArray *photos;
#property (nonatomic, strong) NSMutableArray *thumbs;
#property (nonatomic, strong) NSMutableArray *assets;
#property (nonatomic, strong) NSMutableIndexSet *optionIndices;
#property (nonatomic, strong) UITableView *tableView;
#property (nonatomic, strong) ALAssetsLibrary *ALAssetsLibrary;
- (void)loadAssets;
#end
**.m file **
- (void)viewWillAppear:(BOOL)animated
{
NSMutableArray *photos = [[NSMutableArray alloc] init];
NSMutableArray *thumbs = [[NSMutableArray alloc] init];
//mwphotobrowser options setup
BOOL displayActionButton = YES;
BOOL displaySelectionButtons = NO;
BOOL displayNavArrows = NO;
BOOL enableGrid = YES;
BOOL startOnGrid = NO;
BOOL autoPlayOnAppear = NO;
//loading data
NSArray *photosDataArray = [MDRDataController GetPhotos]; //creating array
for (NSString *urlString in photosDataArray) { //Formating the data source for images
NSString *urlFullString = [NSString stringWithFormat:#"%#%#",KBASEURL,urlString];
//Photos
[photos addObject:[MWPhoto photoWithURL:[NSURL URLWithString:urlFullString]]];
//thumbs
[thumbs addObject:[MWPhoto photoWithURL:[NSURL URLWithString:urlFullString]]];
}
// Options
self.photos = photos;
self.thumbs = thumbs;
// Create browser
self.displayActionButton = displayActionButton;
self.displayNavArrows = displayNavArrows;
self.displaySelectionButtons = displaySelectionButtons;
self.alwaysShowControls = displaySelectionButtons;
self.zoomPhotosToFill = YES;
self.enableGrid = enableGrid;
self.startOnGrid = startOnGrid;
self.enableSwipeToDismiss = NO;
self.autoPlayOnAppear = autoPlayOnAppear;
[self setCurrentPhotoIndex:0];
// Test custom selection images
// browser.customImageSelectedIconName = #"ImageSelected.png";
// browser.customImageSelectedSmallIconName = #"ImageSelectedSmall.png";
// Reset selections
if (displaySelectionButtons) {
_selections = [NSMutableArray new];
for (int i = 0; i < photos.count; i++) {
[_selections addObject:[NSNumber numberWithBool:NO]];
}
}
self.title = #"Phots";
//[self reloadData];
}
Debugging performed
Considering the image template of mwphotobrowser, tried reloading the code.
Shifted the code between viewwillappear and viewdidload.
Doesn't MWPhotoBrowser support this way or am i doing it wrong ?
For those who stumble upon this later...
If you look at MWPhotoBrowser.m you'll see various initializers:
- (id)init {
if ((self = [super init])) {
[self _initialisation];
}
return self;
}
- (id)initWithDelegate:(id <MWPhotoBrowserDelegate>)delegate {
if ((self = [self init])) {
_delegate = delegate;
}
return self;
}
- (id)initWithPhotos:(NSArray *)photosArray {
if ((self = [self init])) {
_fixedPhotosArray = photosArray;
}
return self;
}
- (id)initWithCoder:(NSCoder *)decoder {
if ((self = [super initWithCoder:decoder])) {
[self _initialisation];
}
return self;
}
The problem is there's no awakeFromNib initializer. Simplest solution is to fork the project and create the awakeFromNib initializer.
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.
I need to take information submitted by a user, store that information in an NSMutableDictionary, then store that NSMutableDictionary inside another NSMutableDictionary which is then encoded inside another class. For whatever reason, I can't seem to store the first NSMutableDictionary inside of the other.
I had to slim down the code that's in here due to work rules, so sorry if it seems to be missing anything. I only posted the parts that I'm having trouble with.
UserInfo.h:
#import <Foundation/Foundation.h>
#interface MyPlanInfo : NSObject <NSCoding>
#property (nonatomic, strong) NSMutableDictionary *emergencyDictionary;
#end
UserInfo.m:
#import <Foundation/Foundation.h>
#import "MyPlanInfo.h"
static NSString *emergencyDictionaryKey = #"emergencyDictionaryKey";
#implementation MyPlanInfo
#synthesize emergencyDictionary;
- (id) initWithCoder:(NSCoder *)coder
{
self = [super init];
self.emergencyDictionary = [coder decodeObjectForKey:emergencyDictionaryKey];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:self.emergencyDictionary forKey:emergencyDictionaryKey];
}
#end
infoView.h
#import <UIKit/UIKit.h>
#import "MyPlanInfo.h"
#interface infoView : UIViewController <NSCoding>
{
NSMutableDictionary *emergencyContactInfo;
NSArray *userInfo;
NSArray *userKeys;
NSMutableArray *tempArray;
}
#property (nonatomic, strong) MyPlanInfo *myPlanInfoObject;
-(void)saveUserInfo;
-(void)loadUserInfo;
#end
infoView.m:
#import "infoView.h"
#interface infoView ()
#end
#implementation infoView
static NSString *userInfoKey = #"userInfoKey";
static NSString *userName;
-(void)viewDidLoad
{
[super viewDidLoad];
if(!self.myPlanInfoObject)
{
self.myPlanInfoObject = [[MyPlanInfo alloc] init];
}
[self loadUserInfo];
}
-(void)addToDictionary
{
emergencyContactInfo = [NSMutableDictionary dictionaryWithObjects:userInfo forKeys:userKeys];
if([userInfo count] != 0 || userInfo == nil)
{
self.myPlanInfoObject.emergencyDictionary = [NSMutableDictionary dictionaryWithObject:emergencyContactInfo forKey:userName];
}
[self saveUserInfo];
}
- (void)saveUserInfo
{
NSData *userInfoData = [NSKeyedArchiver archivedDataWithRootObject:self.myPlanInfoObject];
[[NSUserDefaults standardUserDefaults] setObject:userInfoData forKey:userInfoKey];
}
- (void)loadUserInfo
{
NSData *userInfoData = [[NSUserDefaults standardUserDefaults] objectForKey:userInfoKey];
if(userInfoData)
{
self.myPlanInfoObject = [NSKeyedUnarchiver unarchiveObjectWithData:userInfoData];
}
}
#end
In infoView.m, in the addToDictionary method, userInfo is an array of user inputted information, and userKey's is an array of key's. The emergencyContactInfo NSMutableDictionary works just fine, everything is in it, but when I try to set that as an object in a new NSMutableDictionary, for a key, it doesn't work. Everything is nil.
Anyone have any ideas on how what I'm doing wrong?
Edit: If you down vote, please leave a reason as to why so that I can avoid doing whatever I did wrong in the future.
In the following line you’re creating an instance of MyPlanInfo using plain alloc/init:
self.myPlanInfoObject = [[MyPlanInfo alloc] init];
However, at least in the code provided, you haven’t overridden init in MyPlanInfo, but instead, initWithCoder::
- (id) initWithCoder:(NSCoder *)coder
{
self = [super init];
self.emergencyDictionary = [coder decodeObjectForKey:emergencyDictionaryKey];
return self;
}
When you use just plain init, the MyPlanInfo’s emergencyDictionary instance variable will be nil. You should likely add something like the following to MyPlanInfo to override init:
- (id) init
{
if ((self = [super init])) {
emergencyDictionary = [[NSMutableDictionary alloc] init];
}
return self;
}
That will assure that the newly created MyPlanInfo instance has a proper NSMutableDictionary that can be manipulated from other classes.
****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];
}