Saving NSMutableArrays
Good day lads and lasses of the internet, I decided that saving data files for your applications is an important one to know and saving objects in an NSMutableArray wants you to occasionally make you gouge your eyes out until you get the system lined up pretty well. Let us start with the order of events here for saving and loading. Create and NSMutableArray with your object -> Locate the directory and the file to save to -> Encode the array into an NSMutableData file -> Store the NSMD in the given directory Simple enough. For loading, its really the same system just backward I guess you could say. We’ll go into that a little bit later. For now, Open Xcode -> New Project -> “View Based Application” -> “New File” -> “Objective-C Class” -> Call it theFile and check the box for a header file. At this point, I am going to expect you all to know how to do some of these remedial tasks. Go to your header for this new file and declare two NSStrings along with their @property/@synthesize declarations at the end of the header and start of the @implementation. This is the object that we are going to toss into our NSSMutableArray later, which means we are going to have to conform to some protocols designated by the foundation framework. Let us assume you called your two NSStrings textField and numberField (mainly because that is how I named them in the sample code, there are a few treats in there that use styles and all for text fields etc that weren’t mentioned before). We will need to implement both the encoder and decoder methods here and thats it. Add the following code to the implementation of theFile:
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:textField forKey:@"text"];
[encoder encodeObject:numberField forKey:@"number"];
}
-(void)initWithCoder:(NSCoder *)coder {
textField = [[coder decodeObjectForKey:@"text"] retain];
numberField = [[coder decodeObjectForKey:@"number"] retain];
}
First off is the encode protocol, all we are doing here is encoding the two strings inside of the theFile and giving them a reference string so that we can locate them later. The other method does the opposite of that, it decodes both objects for the given keys and stores them in those strings. With that, we can move on to the quick user interface I wrote for this. We are going to want two UITextFields to control the content of the strings in our custom object, two UIButtons to save and load the data, and a UILabel to display the loaded contents. Same thing as we did in our previous tutorial that introduced our world of iDevKit to the UITextField, UILabel, and UIButton, which can be found here. I will give you the set up code here for all the UI today but this will probably be the last time until something new is tutorialized. Add the UITextFields to the header of your viewController class (labelField, numberFieldUIButtons (saveItems, loadItemstheFile *value;.
- (void)loadView {
CGRect frame = [UIScreen mainScreen].applicationFrame;
UIView *view = [[UIView alloc] initWithFrame:frame];
self.view = view;
displayLoad = [[UILabel alloc] initWithFrame:CGRectMake(10,140,300,28)];
displayLoad.font = [UIFont fontWithName:@"Arial" size:16];
[self saveItems];
[self loadItems];
[self labelField];
[self numberField];
[self.view addSubview:saveItems];
[self.view addSubview:loadItems];
[self.view addSubview:labelField];
[self.view addSubview:numberField];
[self.view addSubview:displayLoad];
}
-(UIButton *)saveItems {
saveItems = [[UIButton buttonWithType:UIButtonTypeRoundedRect] retain];
saveItems.frame = CGRectMake(50,222,90,45);
[saveItems setTitle:@"Save" forState:UIControlStateNormal];
[saveItems setTitle:@"Save" forState:UIControlStateHighlighted];
[saveItems setTitleColor:[UIColor colorWithRed:0.0 green:0.3 blue:1.0 alpha:1.0] forState:UIControlStateNormal];
[saveItems setTitleColor:[UIColor colorWithRed:0.0 green:0.1 blue:1.0 alpha:1.0] forState:UIControlStateHighlighted];
[saveItems setBackgroundColor:[UIColor clearColor]];
[saveItems addTarget:self action:@selector(saveObjects) forControlEvents:UIControlEventTouchUpInside];
return saveItems;
}
-(UIButton *)loadItems {
loadItems = [[UIButton buttonWithType:UIButtonTypeRoundedRect] retain];
loadItems.frame = CGRectMake(180,222,90,45);
[loadItems setTitle:@"Load" forState:UIControlStateNormal];
[loadItems setTitle:@"Load" forState:UIControlStateHighlighted];
[loadItems setTitleColor:[UIColor colorWithRed:0.0 green:0.3 blue:1.0 alpha:1.0] forState:UIControlStateNormal];
[loadItems setTitleColor:[UIColor colorWithRed:0.0 green:0.1 blue:1.0 alpha:1.0] forState:UIControlStateHighlighted];
[loadItems setBackgroundColor:[UIColor clearColor]];
[loadItems addTarget:self action:@selector(loadObjects) forControlEvents:UIControlEventTouchUpInside];
return loadItems;
}
-(UITextField *)labelField {
if (labelField == nil) {
CGRect frame = CGRectMake(20,100,280,30);
labelField = [[UITextField alloc] initWithFrame:frame];
labelField.borderStyle = UITextBorderStyleRoundedRect;
labelField.textColor = [UIColor blackColor];
labelField.font = [UIFont systemFontOfSize:17.0];
labelField.placeholder = @"Type Something...";
labelField.backgroundColor = [UIColor clearColor];
labelField.autocorrectionType = UITextAutocorrectionTypeNo;
labelField.keyboardType = UIKeyboardTypeDefault;
labelField.returnKeyType = UIReturnKeyDone;
labelField.clearButtonMode = UITextFieldViewModeWhileEditing;
labelField.tag = 1;
labelField.delegate = self;
}
return labelField;
}
-(UITextField *)numberField {
if (numberField == nil) {
CGRect frame = CGRectMake(20,50,280,30);
numberField = [[UITextField alloc] initWithFrame:frame];
numberField.borderStyle = UITextBorderStyleRoundedRect;
numberField.textColor = [UIColor blackColor];
numberField.font = [UIFont systemFontOfSize:17.0];
numberField.placeholder = @"Numbers...";
numberField.backgroundColor = [UIColor clearColor];
numberField.autocorrectionType = UITextAutocorrectionTypeNo;
numberField.keyboardType = UIKeyboardTypeNumberPad;
numberField.returnKeyType = UIReturnKeyDone;
numberField.clearButtonMode = UITextFieldViewModeWhileEditing;
numberField.tag = 1;
numberField.delegate = self;
}
return numberField;
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return YES;
}
Not going to reinvent the wheel here so if you have any questions, consult the project I attach to the end of this or previous tutorials, there is NOTHING NEW. Alrighty then, notice that the UIButtons send us to two separate methods: saveObjects and loadObjects. Let us start with saving:
-(void)saveObjects {
value = [[theFile alloc] init];
value.textField = labelField.text;
value.numberField = numberField.text;
NSMutableArray *array = [NSMutableArray arrayWithObjects:value,nil];
NSMutableData *data = [NSMutableData data];
NSKeyedArchiver *encode = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDirectory = [path objectAtIndex:0];
NSString *file = [docDirectory stringByAppendingPathComponent:@"save.iDK"];
NSLog(file);
[encode encodeObject:array forKey:@"save"];
[encode finishEncoding];
[data writeToFile:file atomically:YES];
[encode release];
}
Okay then… First three lines are going to allocate/initialize an instance of theFile and set both of the strings inside of it to the values set in our UITextFields. After that, we create an NSMutableArray and store value inside of it, where nil denotes the end of the array. We then create a data file that will later house our array, we use NSMutableData for this. Then we declare an NSKeyedArchiver that will be used in encoding our data file. Now comes a really important step: locating our save directory. This part I just kind of pulled as stock code from Apple but I will do my best to tell you what is going on. The first line there finds us our local path to our app, then it finds the contained Documents directory. We then extract the string from the array that will then be used to place our fileNSLog() here so we can find our application directory for the simulator if we are so inclined. Few more steps: we encode our NSMA and to a reference point for later and push the encoding along. After that, we write the data to our file, clean memory, and we are saved! Almost done, now to add in our decoder bit, add our loadObjects method and lets begin.
-(void)loadObjects {
NSFileManager *manager = [NSFileManager defaultManager];
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDirectory = [path objectAtIndex:0];
NSString *file = [docDirectory stringByAppendingPathComponent:@"save.iDK"];
if([manager fileExistsAtPath:file]) {
NSLog(@"file located and reading");
NSMutableData *data;
NSMutableArray *array;
NSKeyedUnarchiver *decode;
NSString *label;
if(!value) value = [[theFile alloc] init];
data = [NSData dataWithContentsOfFile:file];
decode = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
array = [decode decodeObjectForKey:@"save"];
[decode finishDecoding];
[decode release];
value = [array objectAtIndex:0];
label = value.textField;
label = [label stringByAppendingString:value.numberField];
displayLoad.text = label;
} else {
NSLog(@"file not found, save something to create the file");
}
}
First up is we create what is called an NSFileManager and we set the value of this instance to defaultManager; in a nutshell, this is just creating an object that we will later use to look for a file and return a boolean value on whether or not it exists. After that we do like we did before in the saving method and locate our file’s directory and name (not to be confused with the file itself, this just tells the device where the file SHOULD exist and what it SHOULD be called if it does exist). Now we use a simple if-else statement that uses our manager to check to see if our file located at file exists, and if so, it will load it, if not, it sends an error message to the console and ends the method. Let us assume it exists, otherwise its just boring and you won’t learn anything. To start with we will declare the same variables we did while saving, with exception to the NSKeyedArchiver, which we replaced with the inverse: NSKeyedUnarchiver. We also put in an NSString to store the displayed string in later. The next line checks to see whether or not we have already allocated value, and if not, then we allocate it, simple ’nuff. We now pull the NSMutableData file from our Documents directory that we proved existed, and store it in data. We decode the data file from there using our unarchiver and search for an NSMutableArray inside it using the key that we archived with before. We push the decoder along while we store the array in array, clean the memory a little, and pull the first value from the array and store it in value. From here is all cosmetic, we take the two strings in our new object, combine them, and set the UILabel text to it.

Thats about it, if you have any questions just ask, you can find the source code here.


Regards,
Sj