Writing and reading custom object to file IOS - ios

i have this object.
#interface SeccionItem : NSObject <NSCoding>
{
NSString * title;
NSString * texto;
NSArray * images;
}
#property (nonatomic,strong) NSString * title;
#property (nonatomic,strong) NSString * texto;
#property (nonatomic,strong) NSArray * images;
#end
With this implementation
#implementation SeccionItem
#synthesize title,texto,images;
- (void) encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:title forKey:#"title"];
[encoder encodeObject:texto forKey:#"texto"];
[encoder encodeObject:images forKey:#"images"];
}
- (id)initWithCoder:(NSCoder *)decoder {
title = [decoder decodeObjectForKey:#"title"];
texto = [decoder decodeObjectForKey:#"texto"];
images = [decoder decodeObjectForKey:#"images"];
return self;
}
#end
I want to save an array filled with this objects to a file on disk.
Im doing this:
to write
[NSKeyedArchiver archiveRootObject:arr toFile:file];
to read
NSArray *entries = [NSKeyedUnarchiver unarchiveObjectWithFile:name];
return entries;
But the readed array is always empty, i dont know why, i have some questions.
What format should i use for file path? on toFile:?
The NSArray on the object is filled with NSData objects, so i can encode them?
Im really lost on this.

Take a look at the documentation of NSKeyedArchiver, especially the archiveWithRootObject:toFile: method.
The path is basically where the file should be stored including the file name. For example you can store your array in your app Documents folder with file name called Storage. The code snippet below is quite common:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [paths objectAtIndex: 0];
NSString* docFile = [docDir stringByAppendingPathComponent: #"Storage"];
The method NSSearchPathForDirectoriesInDomains is used instead of absolute path because Apple can be changing the Documents folder path as they want it.
You can use the docFile string above to be supplied to the toFile parameter of the archiveWithRootObject:toFile: method.

Use the following method to save data
-(NSString*)saveFilePath {
NSArray *pathArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *pathString = [[pathArray objectAtIndex:0] stringByAppendingPathComponent:#"data"];
//NSString *pathString = [[NSBundle mainBundle]pathForResource:#"Profile" ofType:#"plist"];
return pathString;
}
-(void)saveProfile {
SeccionItem *data = [[SeccionItem alloc]init]
data. title = #"title";
data. texto = #"fdgdf";
data.images = [NSArray arrayWithObjects:#"dfds", nil];
NSMutableData *pData = [[NSMutableData alloc]init];
NSString *path = [self saveFilePath];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc]initForWritingWithMutableData:pData];
[data encodeWithCoder:archiver];
[archiver finishEncoding];
[pData writeToFile:path atomically:YES];
}
Use the following method to load data
-(void)loadData {
NSString* path = [self saveFilePath];
//NSLog(path);
NSMutableData *pData = [[NSMutableData alloc]initWithContentsOfFile:path];
NSKeyedUnarchiver *unArchiver = [[NSKeyedUnarchiver alloc]initForReadingWithData:pData];
data = [[SeccionItem alloc]initWithCoder:unArchiver];
//NSLog(#"%#",data.firstName);
[unArchiver finishDecoding];
}

For those who are seeking the solution in swift, I was able to write and read dictionary to file system as follows :
Write:
let data = NSKeyedArchiver.archivedData(withRootObject: dictionary)
do {
try data.write(to: destinationPath)
} catch let error {
print("\(error.localizedDescription)")
}
Read:
do
{
let data = try Data.init(contentsOf: path)
// path e.g. file:///private/var/ .... /Documents/folder/filename
if let dict = NSKeyedUnarchiver.unarchiveObject(with: data){
return dict
}
}
catch let error
{
print("\(error.localizedDescription)")
}

Related

How can I update a UITableView from a dynamically downloaded text file?

I am filling a TableView from a text file. I want to enable the user to download an updated text file and replace the existing content of the TableView with the content of the downloaded file. I am able to download the file and replace the original file. If I close the application and open it again, it loads the updated file.
But the TableView doesn't change while the app is running. When I execute the method to load data from the file into the TableView, I can see, using NSLog, that the method is getting the original data from the file.
What am I doing incorrectly? How can I get the method to see the updated text file instead of the original text file?
Thanks.
#interface
#property (strong, nonatomic) NSArray *tableViewData;
#end
#implementation
/*
When user presses button, IBAction method
- downloads text file
- saves the downloaded file, replacing the original text file
- loads the text file into the TableView data (this is what doesn't work)
- sends a reload message to the TableView
*/
- (IBAction)buttonUpdateTextFile:(UIBarButtonItem *)sender
{
NSString *contentsOfTextFile = [self downloadTextFileFromURL:#"http://www.apple.com/index.html"];
[self saveContentsOfTextFile:contentsOfTextFile toFile:#"tableViewData.txt"];
[self loadDataFromFileWithFileName:#"tableViewData" fileExtension:#"txt"];
[self.tableView reloadData];
}
- (NSString *)downloadTextFileFromURL:(NSString *)textFileURLstring
{
NSURL *textFileURL = [NSURL URLWithString:textFileURLstring];
NSError *error = nil;
NSString *contentsOfTextFile = [NSString stringWithContentsOfURL:textFileURL encoding:NSUTF8StringEncoding error:&error];
return contentsOfTextFile;
}
- (void)saveContentsOfTextFile:(NSString *)contentsOfTextFile toFile:(NSString *)fileName
{
NSString *pathName = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *fileNameWithPath = [pathName stringByAppendingPathComponent:fileName];
if (![[NSFileManager defaultManager] fileExistsAtPath:fileNameWithPath]) {
[[NSFileManager defaultManager] createFileAtPath:fileNameWithPath contents:nil attributes:nil];
[[contentsOfTextFile dataUsingEncoding:NSUTF8StringEncoding] writeToFile:fileNameWithPath atomically:NO];
}
- (void)loadDataFromFileWithFileName:(NSString *)fileName fileExtension:(NSString *)fileExtension
{
NSString *path = [[NSBundle mainBundle] pathForResource:fileName
ofType:fileExtension];
NSString *content = [NSString stringWithContentsOfFile:path
encoding:NSUTF8StringEncoding
error:NULL];
NSString *remainingText = [content mutableCopy];
NSMutableArray *data = [[NSMutableArray alloc] init];
NSRange *substringRange;
while (![remainingText isEqualToString:#""]) {
substringRange = [remainingText rangeOfString:#"/n"];
if (substringRange.location == NSNotFound)
{
currentLine = remainingText;
remainingText = #"";
} else {
substringRange.length = substringRange.location;
substringRange.location = 0;
currentLine = [[remainingText substringWithRange:substringRange] mutableCopy];
// - strip line from remainingText
substringRange.location = substringRange.length + 1;
substringRange.length = remainingText.length - substringRange.length - 1;
remainingText = [[remainingText substringWithRange:substringRange] mutableCopy];
}
[data addObject:currentLine];
}
self.tableViewData = [data copy];
}
I think
self.tableViewData = [data copy];
may be the problem.
I would make data a "private" property of the class. Only init once and then manually add and remove objects to it. Don't use copy.

Unable to save table data via NSCoding

I have an array of Objects (containing an item name and a creation date) used to display table data. I want to save this array using NSCoding protocol. I call my saveDataToDisk method every time a "submit" button is pressed to submit a new table entry. However, my app crashes upon hitting submit. This is my first time learning how to save app data and I looked a several tutorials and my form seems to be the same. I also looked for several days on the internet and couldn't find a solution. What is wrong?
Here are my methods:
Decoder Method:
- (id)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
if (!self) {
return nil;
}
self.itemArray = [decoder decodeObjectForKey:#"itemArray"];
return self;
}
Encoder Method:
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.itemArray forKey:#"itemArray"];
}
Path Finder Method:
- (NSString*) pathForDataFile
{
NSFileManager * fileManager = [NSFileManager defaultManager];
NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *filePath = [documentsPath stringByAppendingPathComponent:#"appData"];
if([fileManager fileExistsAtPath:filePath] == NO)
{
[fileManager createDirectoryAtPath:filePath withIntermediateDirectories:NO attributes:nil error:nil];
}
NSString *fileName = #"Budgetlist.appData";
return [filePath stringByAppendingPathComponent: fileName];
}
Save Data Method:
- (void) saveDataToDisk
{
NSString * path = [self pathForDataFile];
NSMutableDictionary * rootObject;
rootObject = [NSMutableDictionary dictionary];
[rootObject setValue: [self itemArray] forKey:#"itemArray"];
[NSKeyedArchiver archiveRootObject: rootObject toFile: path];
}
Load Data Method:
- (void) loadDataFromDisk
{
NSString * path = [self pathForDataFile];
NSDictionary * rootObject;
rootObject = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
[self setItemArray: [rootObject valueForKey:#"itemArray"]];
}
My Button:
- (IBAction)submitButton:(id)sender {
if (self.textField.text.length > 0 && [self.textField.text floatValue]>0) {
self.item = [[Data alloc] init];
self.item.itemName = self.textField.text;
[self.itemArray addObject:self.item];
[self.tableView reloadData];
[self saveDataToDisk];
}
}
You are trying to save an NSArray with custom objects.
Your objects must implement NSCoding protocol as well.
Implement those:
- (id)initWithCoder:(NSCoder *)decoder
- (void)encodeWithCoder:(NSCoder *)encoder
in your "Data" class.
NSArray is going through all objects and call those methods for each object inisde an array

NSCoding -decoder returns wrong values -iOS

I have written the following method to encode/decode data..
- (void) encode: (BOOL) encodeBool int: (NSNumber *) integer boolean:(BOOL) boolean key: (NSString *) keyStr {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *gameStatePath = [documentsDirectory stringByAppendingPathComponent:#"gameData"];
if (encodeBool == YES) {
NSMutableData *gameData = [NSMutableData data];
NSKeyedArchiver *encoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:gameData];
if (integer) {
[encoder encodeInt:[integer intValue] forKey:keyStr];
}
else if (boolean) {
[encoder encodeBool:boolean forKey:keyStr];
}
[encoder finishEncoding];
[gameData writeToFile:gameStatePath atomically:YES];
[encoder release];
} else {
NSMutableData *gameData = [NSData dataWithContentsOfFile:gameStatePath];
if (gameData) {
NSKeyedUnarchiver *decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:gameData];
if (integer) {
NSLog(#"%d", [decoder decodeIntForKey:keyStr]);
}
else if (boolean) {
if ([decoder decodeBoolForKey:keyStr]==YES) {
NSLog(#"YES");
} else {
NSLog(#"NO");
}
}
[decoder finishDecoding];
[decoder release];
}
}
}
And some testing
[[GameData sharedData] encode:YES int: [NSNumber numberWithInt:100] boolean:NO key:#"testInt"];
[[GameData sharedData] encode:YES int:nil boolean:YES key:#"bool"];
[[GameData sharedData] encode:YES int:[NSNumber numberWithInt:1030] boolean:nil key:#"test"];
[[GameData sharedData] encode:NO int: [NSNumber numberWithInt:1] boolean:nil key:#"testInt"];
[[GameData sharedData] encode:NO int:nil boolean:YES key:#"bool"];
[[GameData sharedData] encode:NO int:[NSNumber numberWithInt:100] boolean:nil key:#"test"];
and output is
0
NO
1030
only the last one is correct.. Can someone tell me what I am doing wrong? Thanks
Your problem is that every time you call your method, you overwrite the file - erasing the values you encoded in previous calls. You should probably rewrite your method so that you encode all the values in a single call.
One alternative is to create a GameState object and have it implement NSCoding, then read and serialize it with +[NSKeyedArchiver archiveRootObject:toFile:] and deserialize it with +[NSKeyedUnarchiver unarchiveObjectWithFile:]. The code to do so looks a bit like this:
#interface GameState : NSObject <NSCoding>
#property (nonatomic) int someInt;
#property (nonatomic) BOOL someBool;
#property (nonatomic, strong) NSString *someString;
#end
static NSString *const BoolKey = #"BoolKey";
static NSString *const StringKey = #"StringKey";
static NSString *const IntKey = #"IntKey";
#implementation GameState
- (id)initWithCoder:(NSCoder *)coder
{
self = [super init];
if (self) {
_someBool = [coder decodeBoolForKey:BoolKey];
_someInt = [coder decodeIntForKey:IntKey];
_someString = [coder decodeObjectForKey:StringKey];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeBool:self.someBool forKey:BoolKey];
[aCoder encodeInt:self.someInt forKey:IntKey];
[aCoder encodeObject:self.someString forKey:StringKey];
}
#end
// Somewhere in your app where reading and saving game state is needed...
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = nil;
if ([paths count]) {
documentsDirectory = paths[0];
}
NSString *archivePath = [documentsDirectory stringByAppendingPathComponent:#"archive"];
GameState *gameState = [NSKeyedUnarchiver unarchiveObjectWithFile:archivePath];
if (!gameState) {
gameState = [[GameState alloc] init];
gameState.someString = #"a string";
gameState.someInt = 42;
gameState.someBool = YES;
}
// Make changes to gameState here...
[NSKeyedArchiver archiveRootObject:gameState toFile:archivePath];
The first problem is when you test if (boolean) it's the same as saying if (boolean == YES). Bools aren't objects, and can't be nil. When you pass nil in as a bool, it's the same as passing NO in. I don't think this accounts for all of your issues though. I think the file is not saving as well.
From the NSKeyedUnarchiver docs:
If you invoke one of the decode... methods of this class using a key
that does not exist in the archive, a non-positive value is returned.
This value varies by decoded type. For example, if a key does not
exist in an archive, decodeBoolForKey: returns NO, decodeIntForKey:
returns 0, and decodeObjectForKey: returns nil.
These are the erroneous values you're getting. To start, I note that you're not doing any error checking. Try adding some checks to see what's failing, for example, you could try:
[encoder finishEncoding];
NSError *error;
BOOL success = [gameData writeToFile:gameStatePath options:NSDataWritingAtomic error:&error];
if (success == NO) NSLog(#"Error: %#", [error localizedDescription]);
Once you get an error, we can go from there.

How do I find the filepath for my plist?

I am trying to load a plist into a UITableView. I am new to working with pLists and tableViews, but I know i need to use something along these lines. My problem is though that where "filePath" is, i don't actually know how to put in my pList?
list = [NSArray arrayWithContentsOfFile:filePath];
Any other suggestions with code how to to do this other than getting the file path would be greatly appreciated. Such as do i need to put anything in my .h file? Thanks.
Assuming you've already added a .plist to your project, I've created a class you can add to your project that will get and save information to a given .plist. It's a functioning singleton, so you can call it from anywhere.
First, create a new NSObject file called "GetAndSaveData", then post the following code into .h:
#interface GetAndSaveData : NSObject{
NSMutableDictionary *allData;
NSString *path;
}
+(GetAndSaveData *)sharedGetAndSave;
-(NSMutableArray *)arrayForKey:(NSString *)dataList;
-(void)setData:(NSMutableArray *)array ForKey:(NSString *)dataList;
#end
and the following code into .m:
static GetAndSaveData *sharedGetAndSave;
#implementation GetAndSaveData
-(id)init{
self = [super init];
NSError *error;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); //1
NSString *documentsDirectory = [paths objectAtIndex:0];
path = [documentsDirectory stringByAppendingPathComponent:#"data.plist"];
if (![fileManager fileExistsAtPath: path])
{
NSString *bundle = [[NSBundle mainBundle] pathForResource:#"data" ofType:#"plist"];
[fileManager copyItemAtPath:bundle toPath: path error:&error];
}
allData = [[NSMutableDictionary alloc] initWithContentsOfFile: path];
return self;
}
-(NSMutableArray *)arrayForKey:(NSString *)dataList{
NSMutableArray *array = [allData objectForKey:dataList];
return array;
}
-(void)setData:(NSMutableArray *)array ForKey:(NSString *)dataList{
[allData setObject:array forKey:dataList];
[allData writeToFile:path atomically:YES];
if(![allData writeToFile:path atomically:YES])
{
NSLog(#".plist writing was unsuccessful");
}
}
+(GetAndSaveData *)sharedGetAndSave{
if (!sharedGetAndSave) {
sharedGetAndSave = [[GetAndSaveData alloc] init];
}
return sharedGetAndSave;
}
+(id)allocWithZone:(NSZone *)zone{
if (!sharedGetAndSave) {
sharedGetAndSave = [super allocWithZone:zone];
return sharedGetAndSave;
} else {
return nil;
}
}
-(id)copyWithZone:(NSZone *)zone{
return self;
}
#end
You can change the functions up to get and save different types of data. You can use it in view controllers by importing the .h file, and doing the following:
myMutableArray = [[GetAndSaveData sharedGetAndSave]arrayForKey:myKey];

Using a class method to create an NSArray

Once more I come to the Internet, hat in hand. :)
I'm attempting to use a class method to return a populated array containing other arrays as elements:
.h:
#interface NetworkData : NSObject {
}
+(NSString*) getCachePath:(NSString*) filename;
+(void) writeToFile:(NSString*)text withFilename:(NSString*) filePath;
+(NSString*) readFromFile:(NSString*) filePath;
+(void) loadParkData:(NSString*) filename;
+(NSArray*) generateColumnArray:(int) column type:(NSString*) type filename:(NSString*) filename;
#end
.m:
#import "NetworkData.h"
#import "JSON.h"
#import "Utility.h"
#implementation NetworkData
+(NSString*) getCachePath:(NSString*) filename {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *cachePath = [NSString stringWithFormat:#"%#/%#", [paths objectAtIndex:0], filename];
[paths release];
return cachePath;
}
+(void) writeToFile:(NSString*)text withFilename:(NSString*) filename {
NSMutableArray *array = [[NSArray alloc] init];
[array addObject:text];
[array writeToFile:filename atomically:YES];
[array release];
}
+(NSString*) readFromFile:(NSString*) filename {
NSFileManager* filemgr = [[NSFileManager alloc] init];
NSData* buffer = [filemgr contentsAtPath:filename];
NSString* data = [[NSString alloc] initWithData:buffer encoding:NSUTF8StringEncoding];
[buffer release];
[filemgr release];
return data;
}
+(void) loadParkData:(NSString*) filename {
NSString *filePath = [self getCachePath:filename];
NSURL *url = [NSURL URLWithString:#"http://my.appserver.com"];
NSData *urlData = [NSData dataWithContentsOfURL:url];
[urlData writeToFile:filePath atomically:YES];
}
+(NSArray*) generateColumnArray:(int) column type:(NSString*) type filename:(NSString*) filename {
// NSLog(#"generateColumnArray called: %u %# %#", column, type, filename);
// productArray = [[NSMutableArray alloc] init];
// NSString *filePath = [self getCachePath:filename];
// NSString *fileContent = [self readFromFile:filePath];
// NSString *jsonString = [[NSString alloc] initWithString:fileContent];
// NSDictionary *results = [jsonString JSONValue];
// NSArray *eventsArray = [results objectForKey:type];
// NSInteger* eventsArrayCount = [eventsArray count];
// NSInteger* a;
// for (a = 0; a < eventsArrayCount; a++) {
// NSArray *eventsColSrc = [eventsArray objectAtIndex:a];
// NSArray *blockArray = [eventsColSrc objectAtIndex:column];
// [productArray addObject:blockArray];
// [blockArray release];
// }
// [eventsArray release];
// [results release];
// [jsonString release];
// [fileContent release];
// [filePath release];
// [a release];
// [eventsArrayCount release];
// return productArray;
}
-(void)dealloc {
[super dealloc];
}
#end
.. and the call:
NSArray* dataColumn = [NetworkData generateColumnArray:0 type:#"eventtype_a" filename:#"data.json"];
The code within the method works (isn't pretty, I know - noob at work). It's essentially moot because just calling it (with no active code, as shown) causes the app to quit before the splash screen reveals anything else.
I'm betting this is a headslapper - many thanks for any knowledge you can drop.
If your app crashes, there's very likely a message in the console that tells you why. It's always helpful to include that message when seeking help.
One obvious problem is that your +generateColumnArray... method is supposed to return a pointer to an NSArray, but with all the code in the method commented out, it's not returning anything, and who-knows-what is being assigned to dataColumn. Try just adding a return nil; to the end of the method and see if that fixes the crash. Again, though, look at the error message to see specifically why the code is crashing, and that will lead you to the solution.
Well, you're not returning a valid value from your commented out code. What do you use 'dataColumn' for next? Running under the debugger should point you right to the issue, no?

Resources