IOS and .plists - ios

I am working on an app for a Senior Capstone and I am working with .plist for the first time. What I have so far is in my .h:
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
-(NSString *)dataFilePath;
-(IBAction) readPlist:(id)sender;
-(IBAction) writePlist:(id)sender;
#property (weak, nonatomic) IBOutlet UITextField *textBox;
#end
and in my .m I have:
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(NSString *) dataFilePath
{
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES );
NSString *documentDirectory = [path objectAtIndex:0];
return [documentDirectory stringByAppendingPathComponent:#"JoesData.plist"];
}
- (IBAction)readPlist:(id)sender
{
NSString *filePath = [self dataFilePath];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath])
{
NSArray *array = [[NSArray alloc] initWithContentsOfFile:filePath];
NSLog(#"%#\n",array);
NSLog(#"%#\n", filePath);
}
}
- (IBAction)writePlist:(id)sender {
NSString *string = _textBox.text;
NSMutableArray *anArray = [[NSMutableArray alloc] init];
[anArray addObject:string];
[anArray writeToFile:[self dataFilePath] atomically:YES];
}
#end
so what this does is creates a .plist based upon what is in the text box that I have set up in my storyboard. My problem with this is that it will read and write it just fine, but it won't keep a running list of the things that are entered into the text box. Instead, it simply overwrites the previous .plist. Any thoughts on how to fix the overwriting problem?

Read the plist into memory, make a mutable copy of the array, and add the object to that array, instead of creating a new NSMutableArray every time.

An alternative option is to do the following.
To read the property-list data back into your program, first initialize an allocated NSData object by invoking initWithContentsOfFile: or initWithContentsOfURL: or call a corresponding class factory method such as dataWithContentsOfFile:. Then call the propertyListFromData:mutabilityOption:format:errorDescription: class method of NSPropertyListSerialization, passing in the data object.
Property List Programming Guide

Related

string = textView.text, yet string is (nul)

I've literally tried everything. I'm sure my mistake is going to be something as stupid and small as a missed self.string, but I can't seem to get it working. What my mini program does is checks if a file named Code.txt already exists. If it does exist, then it will load the file in a UITextView (and it does this just fine). If it doesn't exist, it will create a file name Code.txt. All of this works just fine.
However, when I try to save the current text inside the UITextView to the file, I am unable to save it to the string first. This is what I am trying to achieve:
Upon exiting the app: textView.text --> codeString --> Code.txt [saved]
Except, again, I am unable to save the textView.text to the string (codeString). The NSLog is returning it as a nut (see NSLog(#"String: %#", self.codeString); in the saveFile method).
ViewController.h
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "BuildViewController.h"
#interface ViewController : UIViewController <UITextViewDelegate>
#property (nonatomic, strong) NSString *codeString;
#property (nonatomic, retain) IBOutlet UITextView *textView;
#property (nonatomic, retain) BuildViewController *buildViewController;
- (void)saveFile;
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize textView;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
textView.delegate = self;
// Check if Code.txt exists
NSString* documentsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *codeFile = [NSString stringWithFormat:#"%#/Code.txt", documentsDir];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:codeFile];
NSLog(#"viewWillAppear");
if (fileExists == true) {
// Code.txt does exist, continue to viewDidLoad
NSLog(#"Code.txt does exist");
}
else if (fileExists == false) {
// Code.txt does not exist, create the file
NSLog(#"Code.txt does not exist");
ViewController *viewController = [ViewController alloc];
[viewController createFile];
}
self.textView.delegate = self;
}
- (void)viewWillAppear:(BOOL)animated {
// Load Code.txt
NSString* documentsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString* codeFile = [documentsDir stringByAppendingPathComponent:#"Code.txt"];
NSString *codeString = [NSString stringWithContentsOfFile:codeFile encoding:NSUTF8StringEncoding error:NULL];
textView.text = codeString;
NSLog(#"Loaded Code.txt");
}
- (void)saveFile {
// Save text to Code.txt
self.codeString = textView.text;
NSString *documentsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *codeFile = [NSString stringWithFormat:#"%#/Code.txt", documentsDir];
[self.codeString writeToFile:codeFile
atomically:NO
encoding:NSStringEncodingConversionAllowLossy
error:nil];
NSLog(#"String: %#", self.codeString);
NSLog(#"Saved text to Code.txt");
}
- (void)createFile {
NSString *documentsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *codeFile = [NSString stringWithFormat:#"%#/Code.txt", documentsDir];
NSString *codeString = #"print(\"Hello, world\")";
[codeString writeToFile:codeFile
atomically:NO
encoding:NSStringEncodingConversionAllowLossy
error:nil];
NSLog(#"Created Code.txt");
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Your error is you create a new instance of ViewController like #Avi said.
You should get the existing instance in memory
If you want to save file when in background,you may register notification
In viewDidLoad,add this line
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(enterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
Then add this function to ViewController
-(void)enterBackground:(NSNotification *)notification{
[self saveFile];
}
Then in dealloc
-(void)dealloc{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
}

Recreating NSMutableArray

I have a NSMutableArray called _objects stored in NSUserDefaults. I access _objects upon app launch and put them in a CollectionView with custom cell (see below). I let the user edit each item by first entering edit mode and then tapping the desired cell. The cell class then alters the visual appearance correspondingly and reveal a UITextField. Whenever the user presses done on the keyboard while entering data here, the ProjectsViewController (CollectionView viewcontroller) gets a notification from the custom class to resave the items. How can I save the new _objects then?
Specifically I need to edit a cell (done), edit the corresponding item in _objects and resave _objects. How can I achieve this?
My custom class:
ProjectCell.h:
#import <UIKit/UIKit.h>
#interface ProjectCell : UICollectionViewCell
#property (weak, nonatomic) IBOutlet UILabel *projectLabel;
#property (weak, nonatomic) IBOutlet UILabel *projectCount;
#property (weak, nonatomic) IBOutlet UITextField *projectTextField;
#property (weak, nonatomic) IBOutlet UILabel *editModeLabel;
#property (nonatomic, assign) BOOL editMode;;
-(void)editProject;
#end
ProjectCell.m:
#import "ProjectCell.h"
#import "QuartzCore/CALayer.h"
#implementation ProjectCell
#synthesize editMode;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
-(void)awakeFromNib {
// Custom initial code
[_projectTextField setDelegate:self];
[_projectTextField setReturnKeyType:UIReturnKeyDone];
[_projectTextField addTarget:self
action:#selector(editingFinished:)
forControlEvents:UIControlEventEditingDidEndOnExit];
}
- (IBAction)editingFinished:(id)sender
{
// Update the project cell visually
_projectLabel.text = _projectTextField.text;
// disable editMode and update the project cell
editMode = NO;
_projectTextField.hidden = YES;
_editModeLabel.hidden = YES;
// Reload the MasterViewController
[[NSNotificationCenter defaultCenter] postNotificationName:#"saveNewProjects" object:self];
}
-(void)editProject {
if (editMode == YES) {
// disable editMode and update the project cell
editMode = NO;
_projectTextField.hidden = YES;
_editModeLabel.hidden = YES;
} else if (editMode == NO) {
// Enable editMode and update the project cell
editMode = YES;
_projectTextField.hidden = NO;
_editModeLabel.hidden = NO;
}
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
#end
ProjectsViewController.m
This method is called whenever the user presses "done" while editing a cell.
I tried to cycle through every item and "recreate" the NSMutableArray but I
don't think this is a very good approach.
- (void)resaveProjects:(NSNotification *)notification {
// Edit the edited projects and save all again
editedProjects = editedProjects - 1;
NSMutableArray *newObjects;
int objectsCount = _objects.count;
for (int i = 0; i < objectsCount; i++) {
// Initialize objects
if (!newObjects) {
newObjects = [[NSMutableArray alloc] init];
}
}
}
What do you think is the best approach for this and how do I proceed with that?
Thanks!
Edit:
Reading the _objects:
NSMutableArray *_objects;
_objects = [NSMutableArray arrayWithArray:[[NSUserDefaults standardUserDefaults] objectForKey:#"myProjects"]];
In iOS or Mac OS, when you read a collection object like a dictionary or array (from user defaults or a plist) the object is read in as immutable, even if the original object was mutable.
Thus, after reading in a dictionary from user defaults, you have to convert it to a mutable object.
I would suggest you read your object from user defaults and convert it to a mutable version:
In your header:
#property (nonatomic, strong) NSMutableDictionary *aDict;
And in your .m file, when you read the dictionary:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *aDict = [[defaults objectForKey: #"aDictionaryKey"] mutableCopy];
Then, when the user does something that requires you to change the data:
aDict[key] = newValueFromUser;
[defaults setObject: aDict forKey: #"aDictionaryKey"];
Note that if your dictionary contains other collection objects (arrays or other dictionaries), those also become immutable when read back from a file. That means that you may have to walk through the "object graph" of your dictionary, converting each collection inside to a mutable version and saving it in it's enclosing collection.
I just found some posts online that claim that NSCoding preserves the mutability of objects. If that's the case then you might want to save your dictionary to an archive file rather than saving it to user defaults:
Writing your dictionary to a file:
NSString *documentsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES) lastObject]
NSString *path = [documentsDir stringByAppendingPathComponent: #"dictFile"];
[NSKeyedArchiver archiveRootObject: myDict toFile: path];
Reading it back:
NSString *documentsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES) lastObject]
NSString *path = [documentsDir stringByAppendingPathComponent: #"dictFile"];
myDict = [NSKeyedUnarchiver unarchiveObjectWithFile: path];

How to load/save data in NSMutableArray

Here is my mainAppDelegate.h:
#import <UIKit/UIKit.h>
#interface mainAppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#property NSMutableArray *toDoItems;
#end
and my mainAppDelegate.m:
#import "mainAppDelegate.h"
#implementation mainAppDelegate
- (void)applicationWillTerminate:(UIApplication *)application
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:#"toDoItems.plist"];
[self.toDoItems writeToFile:filePath atomically:TRUE];
}
#end
I have another file, XYZToDoListViewController.m with the code:
#import "XYZToDoListViewController.h"
#import "XYZToDoItem.h"
#import "XYZAddItemViewController.h"
#interface XYZToDoListViewController ()
#property NSMutableArray *toDoItems;
#end
#implementation XYZToDoListViewController
- (IBAction)unwindToList:(UIStoryboardSegue *)segue
{
XYZAddItemViewController *source = [segue sourceViewController];
XYZToDoItem *item = source.toDoItem;
if (item != nil) {
[self.toDoItems addObject:item];
[self.tableView reloadData];
}
}
- (IBAction)clearData:(UIBarButtonItem *)sender;
{
[self.toDoItems removeAllObjects];
[self.tableView reloadData];
}
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.toDoItems = [[NSMutableArray alloc] init];
self.navigationController.view.backgroundColor =
[UIColor colorWithPatternImage:[UIImage imageNamed:#"bg_full.png"]];
self.tableView.backgroundColor = [UIColor clearColor];
self.tableView.contentInset = UIEdgeInsetsMake(-35, 0, -35, 0);
}
#end
This is at least what I think is relevant. My basic framework is something I followed from this tutorial. So as you can see I have an NSMutableArray named .toDoItems. and I want it to remember the list when I exit the application (not just minimizing it; it does that already. I think I have the code which is saving the data, but what do I use to check if the file exists, and if so, display it?
And would this affect my clearData method when the app resumes as well?
As if i understood true you want to save your NSMutableArray even after restart your app.
So you may use this codes to save your NSMutableArray with NSUserDefaults.
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
[defaultsPhoneId setObject:yourArray forKey:#"yourKey"];
[defaultsPhoneId synchronize];
And it is how you can take it back
yourArray=[[NSMutableArray alloc]initWithArray:[[NSUserDefaults standardUserDefaults]objectForKey:#"yourKey"]];
But dont forget to initialize it before save with NSUserDefaults.
By the way you may check yourArray anytime if it is loaded or not with
if([yourArray count]==0){
//Your array is empty so you have to call it with #"yourKey". (second codes part)
}
"what do I use to check if the file exists"
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:your_file_path])
"..and if so, display it?"
Please read about UITableView and UITableViewDataSource
https://developer.apple.com/library/ios/documentation/uikit/reference/UITableView_Class/Reference/Reference.html
"And would this affect my clearData method when the app resumes as well?"
clearData is a button action. It will be called only if user will tap
a button. It is not connected with application launch/stopping.

iOS 7 - Using MLPAutoCompleteTextField with a plain NSArray

I am relatively new to iOS Development and I wanted to implement an autocomplete textfield in my application. Upon doing research, I have come across this library, MLPAutoCompleteTextField. I downloaded it, ran the Demo, and tried to understand how it works.
From what I got, the demo uses a custom class for the Array and a custom cell view that's why the autocomplete in the demo contains the flag of the country.
However, what I want to implement is a much simpler version, one that would only use an Array, no more custom classes for the data and the cell layout.
Here is what I have so far:
My FirstViewController.h File
#import <UIKit/UIKit.h>
#import "MLPAutoCompleteTextFieldDataSource.h"
#import "MLPAutoCompleteTextFieldDelegate.h"
#interface FirstViewController : UIViewController <UITextFieldDelegate, MLPAutoCompleteTextFieldDataSource, MLPAutoCompleteTextFieldDelegate>
#property (strong, nonatomic) NSArray *groupID;
#property (strong, nonatomic) NSMutableArray *part;
#property (strong, nonatomic) NSMutableArray *brand;
#property (strong, nonatomic) NSMutableArray *barcode;
#property (strong, nonatomic) NSMutableArray *itemName;
#property (weak) IBOutlet MLPAutoCompleteTextField *groupIDInput;
#property (weak) IBOutlet MLPAutoCompleteTextField *partInput;
#property (weak) IBOutlet MLPAutoCompleteTextField *brandInput;
#property (weak) IBOutlet MLPAutoCompleteTextField *barcodeInput;
#property (weak) IBOutlet MLPAutoCompleteTextField *itemNameInput;
#property (strong, nonatomic, retain) IBOutlet UIButton *searchButton;
#property (assign) BOOL testWithAutoCompleteObjectsInsteadOfStrings;
#end
As you can see, I have 5 AutoCompleteTextViews and I intend to use the 5 Arrays to supply the data for the autoCompleteTextViews.
This is my FirstViewController.m File:
#import "FirstViewController.h"
#import "FMDatabase.h"
#import "FMDatabaseAdditions.h"
#import "MLPAutoCompleteTextFieldDataSource.h"
#import "MLPAutoCompleteTextFieldDelegate.h"
#import "MLPAutoCompleteTextField.h"
#interface FirstViewController ()
#end
#implementation FirstViewController
#synthesize groupID;
#synthesize part;
#synthesize brand;
#synthesize barcode;
#synthesize itemName;
#synthesize groupIDInput;
#synthesize partInput;
#synthesize brandInput;
#synthesize barcodeInput;
#synthesize itemNameInput;
#synthesize searchButton;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self setType];
}
- (void)didReceiveMemoryWarning{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - MLPAutoCompleteTextField DataSource
- (void)groupIDInput:(MLPAutoCompleteTextField *)textField
possibleCompletionsForString:(NSString *)string
completionHandler:(void (^)(NSArray *))handler{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(queue, ^{
NSArray *completions;
if(self.testWithAutoCompleteObjectsInsteadOfStrings){
completions = [self allCountryObjects];
} else {
completions = [self allCountries];
}
handler(completions);
});
}
-(void) setType{
[self.groupIDInput setAutoCompleteTableAppearsAsKeyboardAccessory:NO];
}
- (NSArray *)allCountryObjects{
if(!self.groupID){
NSArray *countryNames = [self allCountries];
NSMutableArray *mutableCountries = [NSMutableArray new];
for(NSString *countryName in countryNames){
[mutableCountries addObject:countryName];
}
[self setGroupID:[NSArray arrayWithArray:mutableCountries]];
}
return self.groupID;
}
- (NSArray *)allCountries{
NSArray *countries =
#[/* Insert Long List of Countries Here */];
return countries;
}
#end
However, my problem now is that in the demo, there is a line that goes [self.autocompleteTextField registerAutoCompleteCellClass:[DEMOCustomAutoCompleteCell class] wherein the custom cell class is used. I get the feeling that I'm also supposed to create my own custom cell class even though I'm not implementing anything fancy.
So, question is:
Do I have to implement my own CustomAutoCompleteObject and CustomAutoCompleteCell? If not, how can I implement this library just by using simple Arrays?
Any help is appreciated. I have been working on this for the past 4-5 hours and my lack of iOS Dev knowledge is taking it's toll on me.
UPDATE 1:
I tried to use a predeclared array instead of a mutable one populated by a database query, I also made some changes as follows:
- (void)autoCompleteTextField:(MLPAutoCompleteTextField *)textField
possibleCompletionsForString:(NSString *)string
completionHandler:(void (^)(NSArray *))handler{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(queue, ^{
NSArray *completions;
//completions = [self allCountries];
completions = [self initializeGroupIDArray];
handler(completions);
});
}
This function is attached to the storyboard.
And my initializeGroupIDArray is as follows:
-(NSArray *)initializeGroupIDArray{
// Getting the database path.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsPath = [paths objectAtIndex:0];
NSString *dbPath = [docsPath stringByAppendingPathComponent:#"itemList.db"];
NSMutableArray *groupArray = #[/* Insert List of Countries Here */];
FMDatabase *database = [FMDatabase databaseWithPath:dbPath];
[database open];
NSString *sqlSelectQuery = #"SELECT DISTINCT GROUPID FROM ItemList";
// Query result
FMResultSet *resultsWithNameLocation = [database executeQuery:sqlSelectQuery];
while([resultsWithNameLocation next]) {
NSString *queryResult = [NSString stringWithFormat:#"%#",[resultsWithNameLocation stringForColumn:#"GROUPID"]];
// loading your data into the array, dictionaries.
NSLog(#"GroupID = %#", queryResult);
[groupArray addObject:queryResult];
}
[database close];
NSArray *groupID;
[groupID = groupArray copy];
return groupID;
}
However, it seems to me that I am not adding my results from the database query properly. Does anyone have ideas?
I didn't initialize my mutable array. Now goes goes as such:
#pragma mark - MLPAutoCompleteTextField DataSource
- (void)autoCompleteTextField:(MLPAutoCompleteTextField *)textField
possibleCompletionsForString:(NSString *)string
completionHandler:(void (^)(NSArray *))handler{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(queue, ^{
NSLog(#"autoCompleteTextField Entered");
NSArray *completions;
completions = [self allGroups];
handler(completions);
});
}
allGroups function:
-(NSArray *)allGroups{
NSLog(#"allGroups Entered");
// Getting the database path.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsPath = [paths objectAtIndex:0];
NSString *dbPath = [docsPath stringByAppendingPathComponent:#"itemList.db"];
NSMutableArray *groupArray = [[NSMutableArray alloc] init]; //This bad body right here.
FMDatabase *database = [FMDatabase databaseWithPath:dbPath];
[database open];
NSString *sqlSelectQuery = #"SELECT DISTINCT GROUPID FROM ItemList";
// Query result
FMResultSet *resultsWithNameLocation = [database executeQuery:sqlSelectQuery];
while([resultsWithNameLocation next]) {
NSString *groupIDName = [NSString stringWithFormat:#"%#",[resultsWithNameLocation stringForColumn:#"GROUPID"]];
// loading your data into the array, dictionaries.
NSLog(#"Group ID = %#", groupIDName);
[groupArray addObject:groupIDName];
}
[database close];
NSLog(#"size of groupArray (mutbale): %d", [groupArray count]);
for (NSUInteger i = 0; i < [groupArray count]; i++){
NSLog(#"Group Array (mutable) :%#", groupArray[i]);
}
NSArray *groupID;
[groupID = groupArray copy];
NSLog(#"size of groupID (immutbale): %d", [groupID count]);
for (NSUInteger i = 0; i < [groupID count]; i++){
NSLog(#"Group ID (non mutable) :%#", groupID[i]);
}
NSLog(#"allGroups before return statement");
return groupID;
}

Can't restore archived data

Ok, I've been over this a million times in the last week and I just am not getting it. (And yes, I've read Apple's docs.)
I am archiving my object and it appears to be archiving correctly (I can see the file written to the file system and if I examine it I can see my data within). However, when I relaunch my app my data is not being restored. Every example I read tells me how easy this is but I'm just not getting it. One unique thing is that my object is a singleton, it's used for passing data between view controllers.
I'd really appreciate some sage advice. Thanks in advance.
Here's my header:
#import <Foundation/Foundation.h>
#interface SharedAppDataObject : NSObject <NSCoding>
{
NSMutableDictionary *apiKeyDictionary;
NSString *skuFieldText;
NSIndexPath *checkmarkIndex;
}
+ (SharedAppDataObject *)sharedStore;
#property (nonatomic, copy) NSString *skuFieldText;
#property (nonatomic, copy) NSIndexPath *checkmarkIndex;
#property (nonatomic, copy) NSMutableDictionary *apiKeyDictionary;
-(void)setValue:(NSString *)apiKey forKey:(NSString *)name;
-(void)setSkuField:(NSString *)s;
-(void)setCheckmarkIndex:(NSIndexPath *)p;
-(NSMutableDictionary *)apiKeyDictionary;
-(BOOL)saveChanges;
#end
Here's my implementation:
#import "SharedAppDataObject.h"
#implementation SharedAppDataObject
#synthesize skuFieldText;
#synthesize checkmarkIndex;
#synthesize apiKeyDictionary;
//create our shared singleton store
+(SharedAppDataObject *)sharedStore {
static SharedAppDataObject *sharedStore = nil;
if (!sharedStore) {
sharedStore = [NSKeyedUnarchiver unarchiveObjectWithFile:[SharedAppDataObject archivePath]];
if(!sharedStore)
sharedStore = [[super allocWithZone:NULL] init];
}
return sharedStore;
}
-(id) init {
self = [super init];
if (self) {
}
return self;
}
-(void)setValue:(id)apiKey forKey:(NSString *)name {
[apiKeyDictionary setObject:apiKey forKey:name];
}
-(void)setSkuField:(NSString *)s {
skuFieldText = s;
}
-(NSMutableDictionary *)apiKeyDictionary {
return apiKeyDictionary;
}
-(void)setCheckmarkIndex:(NSIndexPath *)p {
checkmarkIndex = p;
}
-(void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:skuFieldText forKey:#"skuFieldText"];
[aCoder encodeObject:checkmarkIndex forKey:#"checkmarkIndex"];
[aCoder encodeObject:apiKeyDictionary forKey:#"apiKeyDictionary"];
}
-(id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
[self setSkuFieldText:[aDecoder decodeObjectForKey:#"skuFieldText"]];
[self setCheckmarkIndex:[aDecoder decodeObjectForKey:#"checkmarkIndex"]];
[self setApiKeyDictionary:[aDecoder decodeObjectForKey:#"apiKeyDictionary"]];
}
return self;
}
+(NSString *)archivePath {
NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [documentDirectories objectAtIndex:0];
return [documentDirectory stringByAppendingPathComponent:#"bbyo.archive"];
}
-(BOOL)saveChanges {
return [NSKeyedArchiver archiveRootObject:self toFile:[SharedAppDataObject archivePath]];
}
#end
Save method from App Delegate:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
BOOL success = [[SharedAppDataObject sharedStore] saveChanges];
if (success) {
NSLog(#"Saved all the data");
} else {
NSLog(#"Didn't save any of the data");
}
}
Initialize sharedStore = [NSKeyedUnarchiver unarchiveObjectWithFile:[SharedAppDataObject archivePath]]; in application:didFinishLaunchingWithOptions:. This method is used to initialize data structures and restore previous app state.
Also, take out static SharedAppDataObject *sharedStore = nil; from sharedStore. If the save file exists, [ShareAppDataObject sharedStore] will always unarchive the file which is not necessary. It can be unarchived once during initialization.
Here's a post that can answer your problem: http://bit.ly/PJO8fM
I cannot give you the answer but some ideas to figure this out. Taking this line:
sharedStore = [NSKeyedUnarchiver unarchiveObjectWithFile:[SharedAppDataObject archivePath]];
So if the sharedStore is nil, something is wrong - so test for it. If nothing then log the path, and use NSFileManager methods to see if the file is there, its size etc. If you find the file is there and has size, but you cannot unarchive it, that's a problem of course. In that case, add special debug code just after you create the file:
-(BOOL)saveChanges {
BOO ret = [NSKeyedArchiver archiveRootObject:self toFile:[SharedAppDataObject archivePath]];
id foo = [NSKeyedUnarchiver unarchiveObjectWithFile:[SharedAppDataObject archivePath]];
// check if foo is not nil, if its the proper class, etc.
}
If when you save the file you can unarchive it just fine, but cannot on restart of the app, then something is wrong with the file. All this info should point the way to a solution.
Another thought - when you encode the data, log it, just to be sure its not nil - but even if so the unarchive should work.

Resources