How to save data from multiple views of an iPhone app? - iphone

I'm making an app where I need to save the text in multiple views in the app when the app quits. I also need to be able to remove all of the data from just one of those views and when the app quits, it's possible not all of those views will have been created yet.
After reading this post I thought perhaps it would be good to use a singleton that manages my app data which loads in the data when it is first requested and saved it when the app quits. Then in each view where I need to save data I can just set it on the singleton.
I gave it a go but have run into some issues. At first I didn't synthesize the properties (as in the post I was using as a guide) but the compiler told me I needed to make getters and setters, so I did. Now when my applicationWIllTerminate: gets call the app crashes and the console says "Program received signal: “EXC_BAD_ACCESS”. kill quit".
Is anyone able to tell me what I'm doing wrong, or suggest a better approach to saving the data?
//SavedData.h
#import <Foundation/Foundation.h>
#define kFileName #"appData.plist"
#interface SavedData : NSObject {
NSString *information;
NSString *name;
NSString *email;
NSString *phone;
NSString *mobile;
}
#property(assign) NSString *information;
#property(assign) NSString *name;
#property(assign) NSString *email;
#property(assign) NSString *phone;
#property(assign) NSString *mobile;
+ (SavedData *)singleton;
+ (NSString *)dataFilePath;
+ (void)applicationWillTerminate:(NSNotification *)notification;
#end
//SavedData.m
#import "SavedData.h"
#implementation SavedData
#synthesize information;
#synthesize name;
#synthesize email;
#synthesize phone;
#synthesize mobile;
static SavedData * SavedData_Singleton = nil;
+ (SavedData *)singleton{
if (nil == SavedData_Singleton){
SavedData_Singleton = [[SavedData_Singleton alloc] init];
NSString *filePath = [self dataFilePath];
if([[NSFileManager defaultManager] fileExistsAtPath:filePath]){
NSMutableArray * array = [[NSMutableArray alloc] initWithContentsOfFile:filePath];
information = [array objectAtIndex:0];
name = [array objectAtIndex:1];
email = [array objectAtIndex:2];
phone = [array objectAtIndex:3];
mobile = [array objectAtIndex:4];
[array release];
}
UIApplication *app = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationWillTerminate:) name:UIApplicationWillTerminateNotification object:app];
}
return SavedData_Singleton;
}
+ (NSString *)dataFilePath{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *DocumentsDirectory = [paths objectAtIndex:0];
return [DocumentsDirectory stringByAppendingPathComponent:kFileName];
}
+ (void)applicationWillTerminate:(NSNotification *)notification{
NSLog(#"Application will terminate received");
NSMutableArray *array = [[NSMutableArray alloc] init];
[array addObject:information];
[array addObject:name];
[array addObject:email];
[array addObject:phone];
[array addObject:mobile];
[array writeToFile:[self dataFilePath] atomically:YES];
[array release];
}
#end
Then when I want to use it I do
myLabel.text = [SavedData singleton].information;
And when I change the field
[SavedData singleton].information = #"my string";
Any help will be very much appreciated!

You might want to change your properties to be (retain) instead of assign.
You might want to get the singleton header file I use. It is quite nice for me.
What is likely happening is that you load up your array from a file, and assign it to the property. But it is being autoreleased, and thus it doesn't exist anymore. So when you try to access the memory later, it crashes.
Further reading on memory management: http://www.cocoadev.com/index.pl?MemoryManagement

Related

old values in pList file get overwritten

Hello there I'm trying to create a dictionary with with phone numbers and I can populate the dictionary and add it to a plist but when I try to add to the plist again I overwrite the file.
#import "FirstViewController.h"
#interface FirstViewController ()
{
NSMutableDictionary *NameNumberDict;
NSDictionary *plistDict;
NSMutableArray *numberArray;
NSString *filePath;
}
#end
#implementation FirstViewController
#synthesize NameTxtField;
#synthesize FirstNumField;
#synthesize SecNumField;
#synthesize personName;
#synthesize phoneNumbers;
- (void)viewDidLoad
{
[super viewDidLoad];
//get some memory
plistDict = [[NSDictionary alloc]init];
NameNumberDict = [[NSMutableDictionary alloc]init];
//make a file path string
NSArray *pathArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [pathArray objectAtIndex:0];
filePath = [path stringByAppendingPathComponent:#"data.plist"];
NSFileManager *manager = [NSFileManager defaultManager];
// here i set the Dictionary to the file
//give the file to my dictonary
NameNumberDict = [[NSMutableDictionary alloc] initWithContentsOfFile:filePath];
NSLog(#"The count: %i", [NameNumberDict count]);
//make sure the file is there
NSString *err = nil;
NSData *plist;
plist = [NSPropertyListSerialization dataFromPropertyList:NameNumberDict format:NSPropertyListBinaryFormat_v1_0 errorDescription:&err];
if([manager fileExistsAtPath:#"data.plist"] == NO)
{
[manager createFileAtPath:[NSString stringWithFormat:#"data.plist"] contents:plist attributes:nil];
}
}
- (void)viewDidUnload
{
[self setNameTxtField:nil];
[self setFirstNumField:nil];
[self setSecNumField:nil];
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
// Populate the dictionary when they dismiss the keyboard if all the data is filled out
-(IBAction)TextFieldReturn:(id)TextField
{
if(!NameTxtField.text && !FirstNumField.text && !SecNumField.text);
{
NSString *name = NameTxtField.text;
numberArray = [[NSMutableArray alloc]init];
[numberArray addObject:FirstNumField.text];
[numberArray addObject:SecNumField.text];
[NameNumberDict setObject:name forKey:#"Name"];
[NameNumberDict setObject:numberArray forKey:#"Number"];
NSLog(#"dicSave: %#",NameNumberDict);
}
[TextField resignFirstResponder];
}
// and down here is where im lost Im not sure how to append the data to the Dictionary and write it to file
- (IBAction)AddBtn:(UIButton *)sender
{
plistDict = [[NSMutableDictionary alloc]initWithDictionary:NameNumberDict];
[plistDict writeToFile:filePath atomically:YES];
NSLog (#"File %# exists on iPhone",filePath);
}
#end
In your "TextFieldReturn" method, I see you're always resetting the "Name" and "Number" objects of your "NameNumberDict" mutable dictionary each time it's being called.
I think what you really want to do is add a new dictionary (for each new name & number) to a mutable array and then write that mutable array out to a file (which would end up being your property list file).
Also, just as a style note: Method names and variable names should always start with a lowercase letter (e.g. "textFieldReturn", "addBtn", "nameNumberDict", etc.) while class names (e.g. "FirstViewController") are what start capitalized.

how to maintain a plist so you dont lose your values

I have a plist which I read from my bundle into a new plist object that I put in the root directory for reading and writing. My question is what do I do with this or what dose the application do when its quit, and better yet what happens when the app is killed from the "multi task" menu from ios.
Also is there a way to save this plist to memory/the bundle for future use when the application is used again.
My code is as follows for refrence.
Here is my .h
#import <Foundation/Foundation.h>
#interface EngineProperties : NSObject {
NSString *signature;
NSNumber *version;
NSNumber *request;
NSNumber *dataVersion;
NSMutableDictionary *cacheValue;
//cachevalue Items
NSNumber *man;
NSNumber *mod;
NSNumber *sub;
}
#property (copy, nonatomic) NSString *signature;
#property (copy, nonatomic) NSNumber *version;
#property (copy, nonatomic) NSNumber *request;
#property (copy, nonatomic) NSNumber *dataVersion;
#property (copy, nonatomic) NSMutableDictionary *cacheValue;
//cachevalue Items
#property (copy, nonatomic) NSNumber *man;
#property (copy, nonatomic) NSNumber *mod;
#property (copy, nonatomic) NSNumber *sub;
//Singletton
+ (id)sharedManager;
- (void) saveData:(NSString *)methodName signature:(NSString *)pSignature Version:(NSNumber *)pVersion request:(NSNumber *)rNumber dataVersion:(NSNumber *)dvReturned cacheValue:(NSNumber *)cValue;
#end
and Here is my .m
#import "EngineProperties.h"
static EnginePropertiesController *sharedMyManager = nil;
#implementation EngineProperties
#synthesize signature;
#synthesize version;
#synthesize request;
#synthesize dataVersion;
#synthesize cacheValue;
#synthesize man;
#synthesize mod;
#synthesize sub;
#pragma mark Singleton Methods
+ (id)sharedManager {
#synchronized(self) {
if (sharedMyManager == nil)
sharedMyManager = [[self alloc] init];
}
return sharedMyManager;
}
- (id)init {
if (self = [super init]) {
// Data.plist code
// get paths from root direcory
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
// get documents path
NSString *documentsPath = [paths objectAtIndex:0];
// get the path to our Data/plist file
NSString *plistPath = [documentsPath stringByAppendingPathComponent:#"EngineProperties.plist"];
// check to see if Data.plist exists in documents
if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath])
{
// if not in documents, get property list from main bundle
plistPath = [[NSBundle mainBundle] pathForResource:#"EngineProperties" ofType:#"plist"];
}
// read property list into memory as an NSData object
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
NSString *errorDesc = nil;
NSPropertyListFormat format;
// convert static property liost into dictionary object
NSDictionary *tempRoot = (NSMutableDictionary *)[NSPropertyListSerialization propertyListFromData:plistXML mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&format errorDescription:&errorDesc];
if (!tempRoot)
{
NSLog(#"Error reading plist: %#, format: %d", errorDesc, format);
}
// assign values
self.signature = [tempRoot objectForKey:#"Signature"];
self.version = [tempRoot objectForKey:#"Version"];
self.request = [tempRoot objectForKey:#"Request"];
self.dataVersion = [tempRoot objectForKey:#"Data Version"];
man = [cacheValue objectForKey:#"Man"];
mod = [cacheValue objectForKey:#"Mod"];
sub = [cacheValue objectForKey:#"SubMod"];
cacheValue = [tempRoot objectForKey:#"Cache Value"];
}
- (void) saveData:(NSString *)methodName signature:(NSString *)pSignature Version:(NSNumber *)pVersion request:(NSNumber *)rNumber dataVersion:(NSNumber *)dvReturned cacheValue:(NSNumber *)cValue;
{
// get paths from root direcory
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
// get documents path
NSString *documentsPath = [paths objectAtIndex:0];
// get the path to our Data/plist file
NSString *plistPath = [documentsPath stringByAppendingPathComponent:#"EngineProperties.plist"];
// set the variables to the values in the text fields
self.signature = pSignature;
self.version = pVersion;
self.request = rNumber;
self.dataVersion = dvReturned;
//do some if statment stuff here to put the cache in the right place or what have you.
if (methodName == #"manufacturers")
{
self.man = cValue;
}
else if (methodName == #"models")
{
self.mod = cValue;
}
else if (methodName == #"subMod")
{
self.sub = cValue;
}
self.cacheValue = [NSDictionary dictionaryWithObjectsAndKeys:
man, #"Manufacturers",
mod, #"Models",
sub, #"SubModels", nil];
NSDictionary *plistDict = [NSDictionary dictionaryWithObjectsAndKeys:
signature, #"Signature",
version, #"Version",
request, #"Request",
dataVersion, #"Data Version",
cacheValue, #"Cache Value", 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
[plistData writeToFile:plistPath atomically:YES];
NSString *myString = [[NSString alloc] initWithData:plistData encoding:NSUTF8StringEncoding];
// NSLog(#"%#", myString);
}
else
{
NSLog(#"Error in saveData: %#", error);
// [error release];
}
}
#end
It honestly depends on how frequently you'll be asking for and changing the plist's values. For instance, my app only needed to retrieve it once, then write it a few times (nothing much), so all of my saving code was at the end of said particular method.
However, if your plist is live (constant value changes), keeping a reference to the data you wish to save that is accessible from the AppDelegate is recommended. That way, you can simply call: beginBackgroundTaskWithExpirationHandler: on -applicationDidResignActive and save your plist data.
(Note, if the user is fast enough to kill your app before it saves completely (big if), there are no guarantees as to the integrity of your plist).
follow the blow link in that link they provided the code also. How to Write/Read data to .plist file.
Plist
You can quickly and easily save plists to NSUserDefaults
Check out this quick tutorial:
http://www.cocoadev.com/index.pl?NSUserDefaults

Leaky Custom Object for storing data from a plist

I have made a very simple custom object pictureData.
Here is the .h file
#import <Foundation/Foundation.h>
#interface pictureData : NSObject {
NSString *fileName;
NSString *photographer;
NSString *title;
NSString *license;
}
#property (nonatomic, retain) NSString *fileName;
#property (nonatomic, retain) NSString *photographer;
#property (nonatomic, retain) NSString *title;
#property (nonatomic, retain) NSString *license;
+(pictureData*)picDataWith:(NSDictionary*)dictionary;
#end
The .m file
#import "pictureData.h"
#implementation pictureData
#synthesize fileName;
#synthesize photographer;
#synthesize title;
#synthesize license;
+ (pictureData*)picDataWith:(NSDictionary *)dictionary {
pictureData *tmp = [[[pictureData alloc] init] autorelease];
tmp.fileName = [dictionary objectForKey:#"fileName"];
tmp.photographer = [dictionary objectForKey:#"photographer"];
tmp.title = [dictionary objectForKey:#"title"];
tmp.license = [dictionary objectForKey:#"license"];
return tmp;
}
-(void)dealloc {
[fileName release];
[photographer release];
[title release];
[license release];
}
#end
I then set up these objects in an array, like so:
NSString *path = [[NSBundle mainBundle] pathForResource:#"pictureLicenses" ofType:#"plist"];
NSArray *tmpDataSource = [NSArray arrayWithContentsOfFile:path];
NSMutableArray *tmp = [[NSMutableArray alloc] init];
self.dataSource = tmp;
[tmp release];
for (NSDictionary *dict in tmpDataSource) {
pictureData *pic = [pictureData picDataWith:dict];
NSLog(#"%#", pic.title);
[self.dataSource addObject:pic];
}
Everything works smashingly. I have a table view which loads the proper picture images, and information, no problem. Upon running Instruments for leaks, I see that my pictureData object is leaks with every allocation.
I would assume that with having my object autoreleased I would not have to worry about manually allocating and deallocating them.
Perhaps is my issue that I use autorelease, which the autoReleasePool keeps a retain count of +1 and then when I add a pictureData object to my array, that also retains it? Thank you all for your time!
edit: Don't forget to call super! Thank you Sam!
Change dealloc to:
-(void)dealloc {
[fileName release];
[photographer release];
[title release];
[license release];
[super dealloc];
}
(call [super dealloc])
In your function, change the return value to include autorelease, like
+ (pictureData*)picDataWith:(NSDictionary *)dictionary
{
...
...
return [tmp autorelease];
}
When you add pictureData object to dataSource, you increase the retain count, so you should autorelease it while returning.
Hope it helps.

EXC_BAD_ACCESS on a NSMutableArray that contains a custom class

I receive I EXC_BAD_ACCESS but i can't understand why.
this is the code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
TDBadgedCell *cell = [[[TDBadgedCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
SubscriptionArray * element =[reader.subscriptions objectAtIndex:[indexPath row]];
[cell.textLabel setText:element.title]; // <--- error at this line of code
return cell;
}
reader.subscritions is NSMutableArray that contains 81 elements ( I'm sure verificated with a NSLog) but when I try to get set the title I have this error... why?
SubscritionArray is a custom class that contains 3 string and 1 int.
Custom class:
header
#import <Foundation/Foundation.h>
#interface SubscriptionArray : NSObject{
NSString *title;
NSString *source;
NSString *htmlUrl;
NSInteger count;
}
#property (nonatomic,retain) NSString *title;
#property (nonatomic,retain) NSString *source;
#property (nonatomic,retain) NSString *htmlUrl;
#property (nonatomic) NSInteger count;
#end
implementation:
#import "SubscriptionArray.h"
#implementation SubscriptionArray
#synthesize title,source,htmlUrl,count;
-(void)dealloc{
[title release];
[source release];
[htmlUrl release];
}
- (id)initWithCoder:(NSCoder *)decoder {
if (self = [super init]) {
title = [[decoder decodeObjectForKey:#"title"] retain];
source = [[decoder decodeObjectForKey:#"source"] retain];
htmlUrl = [[decoder decodeObjectForKey:#"htmlUrl"] retain];
//count = [decoder decodeIntForKey:#"count"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
if (title) [encoder encodeObject:title forKey:#"title"];
if (source) [encoder encodeObject:source forKey:#"source"];
if (htmlUrl) [encoder encodeObject:htmlUrl forKey:#"htmlUrl"];
//if (count) [encoder encodeInteger:count forKey:#"count"];
}
#end
EDIT
Tha's what I get
* -[__NSArrayM objectAtIndex:]: message sent to deallocated instance 0xe5a8260
And that's how I set the NSMutableArray
subscriptions = [[NSMutableArray alloc]init];
NSArray *paths = NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
//make a file name to write the data to using the
//documents directory:
NSString *fullFileName = [NSString stringWithFormat:#"%#/subscriptions", documentsDirectory];
[subscriptions removeAllObjects];
subscriptions = [NSKeyedUnarchiver unarchiveObjectWithFile:fullFileName];
I'd venture to say that your array of subscription elements isn't matching up with your row indexes (just a guess). Suggest that you set a debugger breakpoint immediately after you've declared 'element' and before you attempt to set the label. You should be able to see if you have actually obtained one of your SubscriptionArray objets...
The mutable array in reader.subscriptions is getting deallocated before you attempt to reference it in tableView:cellForRowAtIndexPath:. Make sure that the subscriptions array is properly retained or copied by the reader, and that you're not overreleasing it by sending an extra release or autorelease message somewhere. It might help to post the relevant code from the class of the reader instance.

Getting error request for member 'indexPath' in something not a structure or union when compiling

I have an app made from tableview example, and I managed to get the files of documents directory listed on the table, and also got the way for seeing it's content on the detailview, now the problem comes when I edited one file and I want to save it.
I get the error: request for member 'indexPath' in something not a structure or union when I want to access indexPath.row of the table view which is on RootViewController. This is the header for DetaiViewController:
#import <UIKit/UIKit.h>
#import "RootViewController.h"
#class RootViewController;
#interface DetailViewController : UIViewController
{
IBOutlet UITextView *labelName;
NSString *strName;
}
#property (nonatomic, retain) NSString *strName;
#property (nonatomic, retain) UITextView *labelName;
- (IBAction)saveText:(id)sender;
- (IBAction)hideKeyb:(id)sender;
#end
And here is the IBOutlet in where I'm having trouble:
- (IBAction)saveText:(id)sender
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *data = labelName.text;
//RootViewController *rootViewController = [[RootViewController alloc] initWithNibName:#"RootViewController" bundle:nil];
RootViewController *rootViewController = [RootViewController alloc]
NSString *filename = [NSString stringWithFormat:#"%d.txt",rootViewController.indexPath.row+1];
NSString *wheresave = [documentsDirectory stringByAppendingPathComponent:filename];
NSData *aData = [data dataUsingEncoding:NSUTF8StringEncoding];
[aData writeToFile:wheresave atomically:YES];
}
The error is at NSString *filename = [NSString stringWithFormat:#"%d.txt",rootViewController.indexPath.row+1];
I think I have all the imports ok, but i'm not sure :) Thanks in advance!
I'm not sure why you're allocating RootViewController since it should be already allocated for what you're saying.
If you want to access a row (let's say that's the selected one, you can retrive any specific one if you want) in the RootViewController (assuming it's extending from UITableViewController) it should be something like this:
NSIndexPath *indexPath =
[rootViewController.tableView
indexPathForSelectedRow];
NSString
*filename = [NSString stringWithFormat:#"%d.txt",indexPath.row+1];