I need to save a NSMutableArray to NSUserDefaults.
I have tried this, but the load method returns a nil NSMutableArray :
// NSMutableArray *listaAenviar = [[NSMutableArray alloc]init];
-(void) saveArray {
NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
[_listaAenviar addObject:#"1"];
[currentDefaults setObject:[NSKeyedArchiver archivedDataWithRootObject:[_listaAenviar mutableCopy]] forKey:#"listaAenviar"];
[currentDefaults synchronize];
}
-(void) loadArray {
NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
_listaAenviar = [currentDefaults objectForKey:#"listaAenviar"];
}
Try using these methods to save an array, a lot easier.
-(void)saveData :(NSMutableArray *)dataArray
{
NSFileManager *filemgr;
NSString *docsDir;
NSArray *dirPaths;
filemgr = [NSFileManager defaultManager];
// Get the documents directory
dirPaths = NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = [dirPaths objectAtIndex:0];
// Build the path to the data file
NSString *dataFilePath = [[NSString alloc] initWithString: [docsDir
stringByAppendingPathComponent: #"data.archive"]];
[NSKeyedArchiver archiveRootObject:
dataArray toFile:dataFilePath];
}
-(NSMutableArray *)loadData
{
NSFileManager *filemgr;
NSString *docsDir;
NSArray *dirPaths;
filemgr = [NSFileManager defaultManager];
// Get the documents directory
dirPaths = NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = [dirPaths objectAtIndex:0];
// Build the path to the data file
NSString *dataFilePath = [[NSString alloc] initWithString: [docsDir
stringByAppendingPathComponent: #"data.archive"]];
// Check if the file already exists
if ([filemgr fileExistsAtPath: dataFilePath])
{
NSMutableArray *dataArray;
dataArray = [NSKeyedUnarchiver
unarchiveObjectWithFile: dataFilePath];
return dataArray;
}
return NULL;
}
Related
I have created a simple plist file with some user preferences for a card game I'm writing.
I have also created a controller that reads and writes to this plist file which is a singelton.
everything works fine, but then after a couple of tries it stops working.
Logging the values to the console it shows the list returning a value of 0 which causes my app to crash
I have deleted the plist and created a new one and then the same story, works fine for 2 or three time and then boom zero.
here is a copy of the controller singelton code:
#implementation userOptionsController
static userOptionsController* _sharedOptionsController = nil;
#synthesize backgroundSound=_backgroundSound;
#synthesize soundEffects = _soundEffects;
#synthesize coach = _coach;
#synthesize numberOfDecks = _numberOfDecks ;
+(userOptionsController*)sharedOptionsController{
#synchronized([userOptionsController class])
{
if(!_sharedOptionsController)
[[self alloc]init];
return _sharedOptionsController;
}
return nil;
}
+(id)alloc
{
#synchronized ([userOptionsController class])
{
NSAssert(_sharedOptionsController == nil, #"Attempted to allocate a second instance of userOptionsController singleton");
_sharedOptionsController = [super alloc];
return _sharedOptionsController;
}
return nil;
}
- (id) init {
self = [super init];
if (self) {
}
return self;
}
-(void)readPlistFile
{
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"playerPrefOptions.plist"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath: path])
{
NSString *bundle = [[NSBundle mainBundle] pathForResource:#"playerPrefOptions" ofType:#"plist"];
[fileManager copyItemAtPath:bundle toPath: path error:&error];
}
NSMutableDictionary *temp = [[NSMutableDictionary alloc] initWithContentsOfFile: path];
self.backgroundSound = [[temp objectForKey:#"backgroundSounds"]boolValue];
self.soundEffects = [[temp objectForKey:#"soundEffects"]boolValue];
self.coach =[[temp objectForKey:#"coach"]boolValue];
self.numberOfDecks = [[temp objectForKey:#"numberOfDecks"]intValue];
}
-(void)writeOptionsToFile
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"playerPrefOptions.plist"];
NSMutableDictionary *infoDict = [[NSMutableDictionary alloc] initWithContentsOfFile: path];
NSNumber *moshe = [NSNumber numberWithInt:self.numberOfDecks];
[infoDict setObject: moshe forKey:#"numberOfDecks"];
[infoDict setObject:[NSNumber numberWithBool:self.coach] forKey:#"coach"];
[infoDict setObject:[NSNumber numberWithBool:self.backgroundSound] forKey:#"backgroundSounds"];
[infoDict setObject:[NSNumber numberWithBool:self.soundEffects] forKey:#"soundEffects"];
[infoDict writeToFile:path atomically:YES];
}
#end
so the property :
int numberOfDecks =[userOptionsController sharedOptionsController].numberOfDecks;
will return zero.
any ideas?
thanks.
Rather than use a plist for this content, it looks like NSUserDefaults is a more appropriate location.
Instead of shipping the app with a default plist file, instead just registerDefaults: with NSUserDefaults (often done in your app delegate application:didFinishLaunchingWithOptions:).
Then, whenever any changes are made just update NSUserDefaults and call synchronize to save the changes.
Try this and see what it does (what logs are output):
#implementation userOptionsController
+ (userOptionsController*)sharedOptionsController
{
static dispatch_once_t pred = 0;
__strong static id _sharedObject = nil;
dispatch_once(&pred, ^{
_sharedObject = [[self alloc] init];
});
return _sharedObject;
}
- (id) init {
self = [super init];
if (self) {
}
return self;
}
-(void)readPlistFile
{
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"playerPrefOptions.plist"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath: path])
{
NSString *bundle = [[NSBundle mainBundle] pathForResource:#"playerPrefOptions" ofType:#"plist"];
if (![fileManager copyItemAtPath:bundle toPath: path error:&error]) {
NSLog(#"ERROR - file couldn't be copied: %#", error);
}
}
NSMutableDictionary *temp = [[NSMutableDictionary alloc] initWithContentsOfFile: path];
if (temp == nil) {
NSLog(#"ERROR - file couldn't be read");
}
self.backgroundSound = [[temp objectForKey:#"backgroundSounds"]boolValue];
self.soundEffects = [[temp objectForKey:#"soundEffects"]boolValue];
self.coach =[[temp objectForKey:#"coach"]boolValue];
self.numberOfDecks = [[temp objectForKey:#"numberOfDecks"]intValue];
}
-(void)writeOptionsToFile
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"playerPrefOptions.plist"];
NSMutableDictionary *infoDict = [[NSMutableDictionary alloc] initWithContentsOfFile: path];
NSNumber *moshe = [NSNumber numberWithInt:self.numberOfDecks];
[infoDict setObject: moshe forKey:#"numberOfDecks"];
[infoDict setObject:[NSNumber numberWithBool:self.coach] forKey:#"coach"];
[infoDict setObject:[NSNumber numberWithBool:self.backgroundSound] forKey:#"backgroundSounds"];
[infoDict setObject:[NSNumber numberWithBool:self.soundEffects] forKey:#"soundEffects"];
if (![infoDict writeToFile:path atomically:YES]) {
NSLog(#"ERROR - failed to write the new file (%#)", path);
} else {
NSLog(#"Completed write of:\n%#", infoDict);
}
}
#end
I can't save my new data to plist file for some reason. This is code I have been using for saving data:
-(void)saveData:(NSMutableDictionary *)dictionaryData toFile:(NSString *)filename {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [paths objectAtIndex:0];
NSString *path = [docDir stringByAppendingPathComponent:filename];
NSMutableArray *data = [[NSMutableArray alloc] initWithContentsOfFile:path];
[data addObject:dictionaryData];
[data writeToFile:filename atomically:YES];
}
this is code I used to copy file from bundle to app directory in case if it is not there :
-(NSMutableArray *)loadFromFile:(NSString *)filename {
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [paths objectAtIndex:0];
NSString *path = [docDir stringByAppendingPathComponent:filename];
NSFileManager *fileMgr = [NSFileManager defaultManager];
if(![fileMgr fileExistsAtPath:path]) {
NSArray *fileArray = [filename componentsSeparatedByString:#"."];
NSString *name = [fileArray objectAtIndex:0];
NSString *ext = [fileArray objectAtIndex:1];
NSString *bundle = [[NSBundle mainBundle] pathForResource:name ofType:ext];
[fileMgr copyItemAtPath:bundle toPath:path error:&error];
}
NSMutableArray *data = [[NSMutableArray alloc] initWithContentsOfFile:path];
return data;
}
For some reason I can't save new data to plist file. When I try to add new NSMutableDictionary object to my plist file (with saveData:toFile: method) and than reload my array variable with the plist file data - new object is not there. Am I doing something wrong?
this is how I load plist file :
#property (nonatomic, strong) NSMutableArray *modules;
#property (nonatomic, strong) NSMutableDictionary *module;
- (void)viewDidLoad {
[super viewDidLoad];
self.modules = [self loadFromFile:#"ModulesList.plist"];
self.module = [self.modules objectAtIndex:0];
for (int i = 0; i < self.modules.count; i++ ) {
NSLog(#"Modules array from plist file, module at index %i : %#",i, [self.modules objectAtIndex:i]);
}
than for testing purpose I have this code to add new module object:
- (IBAction)leftButton:(id)sender {
NSString *mytitle = [[NSString alloc] initWithFormat:#"my title"];
NSString *myauthor = [[NSString alloc] initWithFormat:#"my author2"];
NSUInteger myean = 22023423;
NSString *mytitle2 = [[NSString alloc] initWithFormat:#"my title 2"];
NSString *myauthor2 = [[NSString alloc] initWithFormat:#"my author2"];
NSUInteger myean2 = 29032432;
NSString *mytitle3 = [[NSString alloc] initWithFormat:#"my title 3"];
NSString *myauthor3 = [[NSString alloc] initWithFormat:#"my author 3"];
NSUInteger myean3 = 21023423;
NSMutableDictionary *mybook = [[NSMutableDictionary alloc] init];
NSMutableDictionary *mybook2 = [[NSMutableDictionary alloc] init];
NSMutableDictionary *mybook3 = [[NSMutableDictionary alloc] init];
[mybook setObject:mytitle forKey:#"title"];
[mybook setObject:myauthor forKey:#"author"];
[mybook setObject:[NSNumber numberWithInteger:myean] forKey:#"ean"];
[mybook2 setObject:mytitle2 forKey:#"title"];
[mybook2 setObject:myauthor2 forKey:#"author"];
[mybook2 setObject:[NSNumber numberWithInteger:myean2] forKey:#"ean"];
[mybook3 setObject:mytitle3 forKey:#"title"];
[mybook3 setObject:myauthor3 forKey:#"author"];
[mybook3 setObject:[NSNumber numberWithInteger:myean3] forKey:#"ean"];
NSMutableArray *mybooks = [[NSMutableArray alloc] init];
[mybooks addObject:mybook];
[mybooks addObject:mybook2];
[mybooks addObject:mybook3];
[self.module setObject:mybooks forKey:#"books"];
[self.modules addObject:self.module];
for (int i = 0; i < self.modules.count; i++ ) {
NSLog(#"Modules array after add operation, module at index: %i: %#",i, [self.modules objectAtIndex:i]);
}
[self saveData:self.module toFile:#"ModulesList.plist"];
}
than when I will reload my self.modules array from plist with button action, my new data is not there:
- (IBAction)reload:(id)sender {
self.modules = [self loadFromFile:#"ModulesList.plist"];
for (int i = 0; i < self.modules.count; i++ ) {
NSLog(#"RELOAD: Modules array from plist file, module at index %i : %#",i,[self.modules objectAtIndex:i]);
}
}
this is screenshot of my plist file : http://dl.dropbox.com/u/49076351/Screen%20Shot%202013-02-27%20at%2016.27.45.png
Hello I am trying to load data from a file
This function save the data to a file in documents folder
- (IBAction)saveUser:(id)sender{
NSString *name = [nameField stringValue];
NSString *weight = [weightField stringValue];
NSDate *date = [datePick dateValue];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setValue:[nameField stringValue] forKey:#"name"];
[dict setValue:[weightField stringValue] forKey:#"weight"];
[dict setValue:date forKey:#"date"];
[dict writeToFile:name atomically:YES];
}
However when I try to load the file and get the data from it I cant, could someone tell me how this is done done.
Edit to show loadFunction
-(IBAction)loadUser:(id)sender{
NSString *weight = [weightField stringValue];
NSDate *date = [datePick dateValue];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *loadPath = [documentsDirectory stringByAppendingPathComponent:name];
NSMutableDictionary *savedData = [[NSMutableDictionary alloc] initWithContentsOfFile: name];
NSLog(#"%#", weight);
}
To fix your answer. here is a code that works, you pretty much did not give a filename.
NSString *filename = [documentsDirectory stringByAppendingPathComponent:#"file.txt"];
And in your line where you write the file :
[dict writeToFile:name atomically:YES];
you must change name to filename, like this
[dict writeToFile:filename atomically:YES];
And in your load data method :
NSString *filename = [documentsDirectory stringByAppendingPathComponent:#"file.txt"];
NSMutableDictionary *savedData = [[NSMutableDictionary alloc] initWithContentsOfFile:filename];
NSLog(#"The weight is : %#", [savedData valueForKey:#"weight"]);
The following seems to do what you intend (minus the UI element dependencies in your code):
#import <Foundation/Foundation.h>
static NSString *pathToDocuments(void) {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
return [paths objectAtIndex:0];
}
int main(int argc, char *argv[]) {
NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init];
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setValue:#"SomeName" forKey:#"name"];
[dict setValue:#"SomeWeight" forKey:#"weight"];
[dict setValue:[NSDate date] forKey:#"date"];
NSString *filePath = [pathToDocuments() stringByAppendingPathComponent:[dict objectForKey:#"name"]];
[dict writeToFile:filePath atomically:YES];
[dict release]; dict = nil;
NSLog(#"%s - Confirming dict is nil: %#",__FUNCTION__,dict);
dict = [[NSDictionary dictionaryWithContentsOfFile:filePath] mutableCopy];
NSLog(#"%s - weight = %#",__FUNCTION__,[dict objectForKey:#"weight"] );
[p release];
}
This prints the following to the console:
2012-10-19 06:40:38.691 Untitled[10633:707] main - Confirming dict is nil: (null)
2012-10-19 06:40:38.693 Untitled[10633:707] main - weight = SomeWeight
EDIT:
That said, I think the problem may be the file path in loadUser...
Plist is copyed to documents directory if it doesn't exist.
If it already exists, I want to use the "Name" key from NSDictionary in bundleArray to find the matching NSDictionary in documentsArray.
When the match is found, I want to check for changes in the strings and replace them if there is a change.
If a match is not found it means this dictionary must be added to documents plist.
This is my code:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[self managePlist];
return YES;
}
- (void)managePlist {
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"Objects.plist"];
NSString *bundle = [[NSBundle mainBundle] pathForResource:#"Objects" ofType:#"plist"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath: path]) {
[fileManager copyItemAtPath:bundle toPath:path error:&error];
} else {
NSArray *bundleArray = [[NSArray alloc] initWithContentsOfFile:bundle];
NSMutableArray *documentArray = [[NSMutableArray alloc] initWithContentsOfFile:path];
BOOL updateDictionary = NO;
for(int i=0;i<bundleArray.count;i++) {
NSDictionary *bundleDict=[bundleArray objectAtIndex:i];
BOOL matchInDocuments = NO;
for(int ii=0;ii<documentArray.count;ii++)
{
NSMutableDictionary *documentDict = [documentArray objectAtIndex:ii];
NSString *bundleObjectName = [bundleDict valueForKey:#"Name"];
NSString *documentsObjectName = [documentDict valueForKey:#"Name"];
NSRange range = [documentsObjectName rangeOfString:bundleObjectName options:NSCaseInsensitiveSearch];
if (range.location != NSNotFound) {
matchInDocuments = YES;
}
if (matchInDocuments) {
if ([bundleDict objectForKey:#"District"] != [documentDict objectForKey:#"District"]) {
[documentDict setObject:[bundleDict objectForKey:#"District"] forKey:#"District"];
updateDictionary=YES;
}
}
else {
[documentArray addObject:bundleDict];
updateDictionary=YES;
}
}
}
if(updateDictionary){
[documentArray writeToFile:path atomically:YES];
}
}
}
If I run my app now I get this message: '-[__NSCFDictionary setObject:forKey:]: mutating method sent to immutable object'
How can I fix this?
When this is fixed, do you think my code will work?
If not, I would be happy for some suggestions on how to do this. I have struggled for a while and really need to publish the update with the corrections! Thanks a lot for your help.
Presumably the documentDict is actually immutable. Even though you assign it to an NSMutableDictionary, this doesn't accurately describe the underlying data.
NSMutableDictionary *documentDict = [documentArray objectAtIndex:ii];
should be:
NSMutableDictionary *documentDict = [NSMutableDictionary dictionaryWithDictionary:[documentArray objectAtIndex:ii]];
and wherever you edit the documentDict, add:
[documentArray replaceObjectAtIndex:ii withObject:documentDict];
When you read a plist from file, an immutable dictionary/array with immutable children is created, so your
NSMutableDictionary *documentDict = [documentArray objectAtIndex:ii];
line is erfectively a lie - you don't have a mutable dictionary. To create a mutable object from a plist, have a look at CFPropertyListCreateWithData and specify kCFPropertyListMutableContainersAndLeaves as the mutability option.
This is my final and working code using the answer from James Webster. Thanks to H2CO3 as well for contribution.
- (void)managePlist {
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"Object.plist"];
NSString *bundle = [[NSBundle mainBundle] pathForResource:#"Object" ofType:#"plist"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:path]) {
[fileManager copyItemAtPath:bundle toPath:path error:&error];
}
else
{
NSArray *bundleArray = [[NSArray alloc] initWithContentsOfFile:bundle];
NSMutableArray *documentArray = [[NSMutableArray alloc] initWithContentsOfFile:path];
BOOL updateDictionary = NO;
for(int i=0;i<bundleArray.count;i++) {
NSDictionary *bundleDict=[bundleArray objectAtIndex:i];
for(int ii=0;ii<documentArray.count;ii++)
{
NSMutableDictionary *documentDict = [NSMutableDictionary dictionaryWithDictionary:[documentArray objectAtIndex:ii]];
NSString *bundleObjectName = [bundleDict valueForKey:#"Name"];
NSString *documentsObjectName = [documentDict valueForKey:#"Name"];
NSRange range = [documentsObjectName rangeOfString:bundleObjectName options:NSCaseInsensitiveSearch];
if (range.location != NSNotFound) {
NSString *districtString = [bundleDict objectForKey:#"District"];
if ([documentDict objectForKey:#"District"] != districtString) {
[documentDict setObject:districtString forKey:#"District"];
[documentArray replaceObjectAtIndex:ii withObject:documentDict];
updateDictionary=YES;
}
}
}
}
if(updateDictionary){
[documentArray writeToFile:path atomically:YES];
}
}
}
This code works beautifully to save my plist to the documents directory.
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.rightBarButtonItem = self.addButtonItem;
NSString *path = [[NSBundle mainBundle] pathForResource:#"TitleArray" ofType:#"plist"];
NSMutableArray *tmpArray = [[NSMutableArray alloc]initWithContentsOfFile:path];
self.titles = tmpArray;
[tmpArray release];
//TESTING NEW CODE FOR SAVING TO DOC DIR
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *docDirPath = [documentsDirectory stringByAppendingPathComponent:#"TitleArray.plist"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if(![fileManager fileExistsAtPath: docDirPath])
{
NSString *bundle = [[NSBundle mainBundle] pathForResource:#"TitleArray" ofType:#"plist"];
[fileManager copyItemAtPath:bundle toPath:docDirPath error:&error];
NSLog(#"plist is copied to Directory");
}
I have not been able to figure out how to load the plist back to the app!
Can anyone help? Thanks.
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"TitleArray.plist"];
NSArray * myList = [NSArray arrayWithContentsOfFile:path];
NSMutableArray *tmpArray = [myList mutableCopy];
self.titles = tmpArray;
[tmpArray release];
didn't work?
If you are asking how to overwrite a plist included in your app's main bundle, then you should know that it cannot be done. Take a look at this question for more details: Can you update a file in the application bundle?
NSError *error; NSArray *paths =
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *docDirPath = [documentsDirectory stringByAppendingPathComponent:#"TitleArray.plist"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if(![fileManager fileExistsAtPath: docDirPath])
{
docDirPath = [[NSBundle mainBundle] pathForResource:#"TitleArray" ofType:#"plist"];
}
NSMutableArray *tmpArray = [[[NSMutableArray alloc] initWithContentsOfFile: docDirPath] mutableCopy];