Correcting a plist with dictionaries - objective-c

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];
}
}
}

Related

Creating and writing/reading from plist file not working

I am trying to save multiple games and whenever I want to save one the first method is called. However, no matter what I do, [NSFileManager defaultManager] keeps telling me that there is no file at [SaveGameManager filePath] and [SaveGameManager savedGames] keeps giving me nil objects. Any advice/help?
#implementation SaveGameManager
+(void)saveGameWithDate:(NSDate*)date type:(NSInteger)type allMoves:(NSArray*)allMoves players:(NSArray*)players playerTimes:(NSArray*)playerTimes delayTime:(double)delayTime useTimer:(BOOL)useTimer delayOption:(NSInteger)delayOption drawOfferState:(NSInteger)drawOfferState resignationState:(NSInteger)resignationState current:(BOOL)current {
NSLog(#"saving game");
NSMutableArray* savedGames = [SaveGameManager savedGames];
if (!savedGames) {
savedGames = [NSMutableArray array];
}
NSMutableDictionary* dict = [#{#"date" : date, #"boardType" : #(type), #"allMoves" : allMoves, #"players" : players, #"delayTime" : #(delayTime), #"useTimer" : #(useTimer), #"delayOption" : #(delayOption), #"drawOfferState" : #(drawOfferState), #"resignationState" : #(resignationState), #"current" : #(current)} mutableCopy];
if (playerTimes) {
dict[#"playerTimes"] = playerTimes;
}
[savedGames addObject:dict];
[savedGames writeToFile:[SaveGameManager filePath] atomically:YES];
}
+(NSString*)filePath {
static NSString* path;
if (!path) {
path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:#"SavedGames.plist"];
}
return path;
}
+(NSMutableArray*)savedGames {
return [NSMutableArray arrayWithContentsOfFile:[SaveGameManager filePath]];
}
#end
Use this code to save a NSDictionary in Plist format in Application document.
+ (void)saveSessionToDisk:(NSDictionary *)session {
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] initWithDictionary:session];
NSError *error;
NSData *archiveData = [NSKeyedArchiver archivedDataWithRootObject:dictionary];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [paths objectAtIndex:0];
NSString *fullPath = [documentsDir stringByAppendingPathComponent:#"SavedSession.plist"];
[archiveData writeToFile:fullPath options:NSDataWritingAtomic error:&error];
}
Use this code to Load NSDictionary from Plist that you saved earlier.
+ (NSDictionary *)loadSessionFromDisk
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [paths objectAtIndex:0];
NSString *fullPath = [documentsDir stringByAppendingPathComponent:#"SavedSession.plist"];
DLog(#"%#",fullPath);
NSFileManager *fileManager =[NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:fullPath]) {
NSMutableDictionary *dict = nil;
#try {
NSData *archiveData = [NSData dataWithContentsOfFile:fullPath];
dict = (NSMutableDictionary*)[NSKeyedUnarchiver unarchiveObjectWithData:archiveData];
if ([dict count] > 0) {
return dict;
} else {
return nil;
}
} #catch (NSException *e) {
}
} else {
return nil;
}
}
Always use this condition before you try to load NSDictionary :
if ([[UtilityFunctions loadSessionFromDisk] isKindOfClass:[NSDictionary class]]) {
NSDictionary *myDictionary = [UtilityFunctions loadSessionFromDisk];
}

issues reading from plist file

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

Save NSMutableArray in NSUserDefaults

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;
}

Saving data from Twitter feed parsed with JSON into Plist

I have a UITableView populated by a feed from searchtwitter.com. I also have a details table with an image and UILabel. I have all this working, but I want to add a UIButton on the detail page that will save the UIImage and the label into a property list. I'm very new to this; here is what I have so far (not working).
- (IBAction)SaveFriend:(id)sender
{
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [path objectAtIndex:0];
NSString *plistPath = [documentDirectory stringByAppendingPathComponent:#"TwitterFriends.plist"];
NSDictionary * tweet = [[NSDictionary alloc]init];
NSString *text = [tweet objectForKey:#"from_user"];
NSString *user = [tweet objectForKey:#"from_user_name"];
personName.text = text;
personInfo.text = user;
// create dictionary with values in UITextFields
NSDictionary *plistDict = [NSDictionary dictionaryWithObjects: [NSArray arrayWithObjects: personName, personInfo, nil] forKeys:[NSArray arrayWithObjects: #"Name", #"info", nil]];
NSString *error = nil;
// create NSData from dictionary
NSData *plistData = [NSPropertyListSerialization dataFromPropertyList:plistDict format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
// check is plistData exists
if(plistData)
{
// write plistData to our Data.plist file
[tweet writeToFile:plistPath atomically:YES];
}
else
{
NSLog(#"Error in saveData: %#", error);
[error release];
}
}
Instead of using a .plist, try using an NSArray or NSMutableArray and then the writeToFile method. For example:
NSMutableArray *temp = [NSMutableArray new];
[temp addObject:image];
[temp addObject:label];
[temp writeToFile:[self saveFilePath:#"filename"] atomically:YES];
//Returns the saved information path
- (NSString*) saveFilePath: (NSString *) add
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString *path = [paths objectAtIndex:0];
NSString *filename = [path stringByAppendingPathComponent:add];
return filename;
}

Retrieve all images from NSDocumentDirectory and storing into an array

Currently i'm using these codes to save my images into NSDocumentDirectory. I use this counter as the naming convention for them.
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)selectedImage editingInfo:(NSDictionary *)editingInfo
{
[self.popoverController dismissPopoverAnimated:YES];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [paths objectAtIndex:0];
NSString *savedImagePath = [documentsDir stringByAppendingPathComponent:[NSString stringWithFormat:#"%d.png", counter]];
UIImage *image = imageView.image;
NSData *imageData = UIImagePNGRepresentation(image);
[imageData writeToFile:savedImagePath atomically:NO];
}
I use this method because it's easier for me to retrieve all of them by using a loop. I want to retrieve all the images from the NSDocumentDirectory so that i can display them in another view. The following codes show how i retrieve them.
-(NSMutableArray *)GetImage:(NSMutableArray *)arrayImgNames
{
NSMutableArray *tempArray;
for(int i=0;i<[arrayImgNames count]; i++)
{
NSArray *paths1 = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths1 objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent: [arrayImgNames objectAtIndex:i]];
[tempArray addObject:[[UIImage alloc] initWithContentsOfFile:filePath]];
return tempArray;
}
}
However, i do not wish to use the counter as a naming convention for my images. I want to use proper names for them but if i do so, i will have to change my method of retrieving all the images.
Is there any other way that i can retrieve all images other than this method i mentioned?
You can retrieve files using next approach:
NSURL *url = [[AppDelegate sharedAppDelegate] applicationDocumentsDirectory];
NSError *error = nil;
NSArray *properties = [NSArray arrayWithObjects: NSURLLocalizedNameKey, NSURLLocalizedTypeDescriptionKey, nil];
NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:url
includingPropertiesForKeys:properties options:(NSDirectoryEnumerationSkipsPackageDescendants)
error:&error];
In files paths to all files of documents directory will be stored. Next code will help you to get there names:
NSURL *url = [files objectAtIndex:index];
NSString *localizedName = [url lastPathComponent];