I'm trying to save simple text data in my app using this code from Beginning iPhone Development:
- (NSString *)dataFilePath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
return [documentsDirectory stringByAppendingFormat:kFileName];
}
- (void)applicationWillTerminate:(NSNotification *)notification {
NSMutableArray *array = [[NSMutableArray alloc] init];
[array addObject:field1.text];
[array addObject:field2.text];
[array addObject:field3.text];
[array addObject:field4.text];
[array writeToFile:[self dataFilePath] atomically:YES];
}
- (void)viewDidLoad { NSString *filePath = [self dataFilePath];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
NSArray *array = [[NSArray alloc] initWithContentsOfFile:filePath];
field1.text = [array objectAtIndex:0];
field2.text = [array objectAtIndex:1];
field3.text = [array objectAtIndex:2];
field4.text = [array objectAtIndex:4];
}
UIApplication *app = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationWillTerminate:)
name:UIApplicationWillTerminateNotification
object:app];
[super viewDidLoad];
}
This code was written and works great for iOS 3, but not with iOS 5. If you simply tap the home button in the simulator to close it, then bring the multitasking bar up and tap on the app icon, the data reloads just fine. But, when you close it via tapping the home button, then bring up the multitasking bar and close it there as well, and relaunch the app, the data is gone. I don't think the app is getting the notification that it is being terminated when you close it from the multitasking bar, but I'm not sure about that I'm still a beginner.
I'm sure that I just need to add an extra line or 2 to the code that I already have. Does anyone know what that might be?
May I suggest using the NSUserDefaults class? It's very good for storing simple data like this.
Setting a value
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:#"value" forKey:#"field"];
[defaults synchronize];
Getting a value
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
NSString * value = [defaults objectForKey:#"field"]
My guess is that when this was written for iOS 3, applicationWillTerminate was called when you hit the home button. With iOS 5, this isn't the case. Try moving your code to -applicationDidEnterBackground.
Related
i have to develop an apple watch application in which i have to show some tabular view in the apple watch . For this operation i have already Core data in my iPhone from which i get retrieved in to a NSArray object.
But now i want to pass it to the watch kit extension so how its possible?
is any one have idea ?
Below is the function which returns the Core date fetched records in the form of array objects.
-(NSMutableArray *) getWatchHomeView
{
NSMutableArray *resultTracks = [[NSMutableArray alloc] init];
self.ongoingMapArray = [NSArray arrayWithArray:[[self fetchResultsForCompletedExpeditions:NO] fetchedObjects]];
NSLog(#"ongoingMapArray-- %lu",(unsigned long)[self.ongoingMapArray count]);
self.completedMapArray = [NSArray arrayWithArray:[[self fetchResultsForCompletedExpeditions:YES] fetchedObjects]];
NSLog(#"completedMapArray-- %lu",(unsigned long)[self.completedMapArray count]);
for (int i=0; i < self.completedMapArray.count; i++)
{
WatchTable *watchTableRow = [[WatchTable alloc] init];
Map *mapObject = [self.completedMapArray objectAtIndex:i];
watchTableRow.trackName = [[NSString stringWithFormat:#"%#", [mapObject name]] uppercaseString];
NSArray *arrPolylines = [NSArray arrayWithArray:[[self fetchPloylineForMaps:[mapObject name]] fetchedObjects]];
if ([arrPolylines count] > 0) {
double totalDis = [self getTotalDistanceFromPolylines:arrPolylines];
watchTableRow.trackedDistance = [NSString stringWithFormat:#"%.2f km", totalDis];
Polyline *firstPolyline = [arrPolylines lastObject];
NSMutableArray *arrTimeData = (NSMutableArray*)firstPolyline.time;
if ([arrTimeData count] > 0) {
watchTableRow.trackedTime = [NSString stringWithFormat:#"%# ago", [self getPausedTimeWithCreationDate:[arrTimeData lastObject]]];
}else{
watchTableRow.trackedTime = [NSString stringWithFormat:#"%# ago", [self getPausedTimeWithCreationDate:firstPolyline.creationDate]];
}
}else{
watchTableRow.trackedTime = [NSString stringWithFormat:#"%# ago", [self getPausedTimeWithCreationDate:mapObject.creationDate]];
watchTableRow.trackedDistance = [NSString stringWithFormat:#"0.00 Km"];
}
NSLog(#"watchTableRow = %#",watchTableRow);
[resultTracks addObject:watchTableRow];
}
[[NSUserDefaults standardUserDefaults] setObject:resultTracks forKey:#"WatchHomeViewTableList"];
return resultTracks;
}
If you use watchOS 1, you can share data between your watch and iOS App with App Groups.
ref.
Share Data in your Swift WatchKit Apps with App Groups
WATCHKIT: BEST PRACTICES FOR SHARING DATA BETWEEN YOUR WATCH AND IOS APP
EDIT:
On the iphone side, serialize your data.
NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:resultTracks];
NSUserDefaults *defaults = [[NSUserDefaults alloc]
initWithSuiteName:#"group.com.example.mygroup"];
[defaults setObject:encodedObject forKey:#"WatchHomeViewTableList"];
[defaults synchronize];
And unserialize your data.
NSUserDefaults *defaults = [[NSUserDefaults alloc]
initWithSuiteName:#"group.com.example.mygroup"];
NSData *encodedObject = [defaults objectForKey:#"WatchHomeViewTableList"];
NSMutableArray *resultTracks = [NSKeyedUnarchiver unarchiveObjectWithData:encodedObject];
There are three ways to exchange data from your iPhone app to Apple Watch app under watchOS 1.0 with App Groups:
NSUserDefaults with suite: you can use NSUserDefaults with a virtual "box" of data that is shared between your app and the extension. Simply create a new NSUserDefaults instance with the method -[NSUserDefaults initWithSuiteName:#"myAppGroupIdentifier"] and use it like the shared user defaults provided by iOS. https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/
Darwin notifications: a low-level implementation of the observer pattern, like the one used in NSNotificationCenter. This is a little bit difficult to use, so I recommend to use MMWormhole instead. https://developer.apple.com/library/mac/documentation/Darwin/Conceptual/MacOSXNotifcationOv/DarwinNotificationConcepts/DarwinNotificationConcepts.html
MMWormhole: a wrapper for Darwin notifications, but with a simple communication model similar to the one used in WKInterfaceController/AppDelegate. You can find this library on GitHub.
Please note that you first need to register an App Group Identifier into your Apple Developer account.
See the documentation for updates about watchOS 2.0 and 3.0.
This question already has answers here:
File write with [NSBundle mainBundle] fails
(5 answers)
Closed 8 years ago.
I'm reading and writing variables to a text file in my app.
It works perfectly fine when testing it in the simulator, but somehow when testing it on the iPhone it can only read the file but not write/save anything
Here's my method for saving my variables ("\n" is used for line breaking):
NSString path = [[NSBundle mainBundle] pathForResource:#"settings" ofType:#"txt"];
- (void)saveSettings {
[[NSString stringWithFormat:#"%d\n%d", firstInt, secInt] writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];
}
As I said: works in the simulator but doesn't work on any iDevice.
Does anyone got a idea why it's not working? Changing "atomically" to "NO" does nothing
You can't write to the bundle. Among other reasons because the bundle is part of the signature in the app and that can't be changed. You can write to other directories, en particular the Documents directory
NSArray *paths = NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *write_file = [NSString stringWithFormat:#"%#/settings.txt",
documentsDirectory];
You will be able to write to write_file.
Short answer: You can't write into the main bundle.
Long answer: You should really use the preferences for this:
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
[prefs setObject:[NSString stringWithFormat:#"%d\n%d", firstInt, secInt]
forKey:#"yourPref"];
BOOL saved = [prefs synchronize];
or even better, like that:
NSInteger firstInt = 0;
NSInteger secInt = 0;
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
[prefs setInteger:firstInt forKey:#"firstInt"];
[prefs setInteger:secInt forKey:#"secInt"];
BOOL saved = [prefs synchronize];
Please someone tell me why retaining NSMutabledictionary after some operations is ok, but retain on creation gives a leak.
-(void) Func2
{
NSString *_errorDesc;
NSPropertyListFormat format;
NSString *_plistPath = [[NSBundle mainBundle] pathForResource:#"List" ofType:#"plist"];
NSData *_plistData = [[NSFileManager defaultManager] contentsAtPath:_plistPath];
NSDictionary *dataDict = (NSDictionary *) [NSPropertyListSerialization
propertyListFromData:_plistData
mutabilityOption:NSPropertyListMutableContainersAndLeaves
format:&format
errorDescription:&_errorDesc];
for (int i = 0; i<[dataDict count]; i++)
{
[_ups setObject:[NSArray arrayWithObjects:[NSNumber numberWithInt:0],[NSNumber numberWithInt:0], nil]forKey:[NSString stringWithFormat:#"%d",i]];
}
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:_ups forKey:#"Ups"];
[defaults synchronize];
}
-(void) Func1
{
_ups = [NSMutableDictionary dictionary];
//_ups = [[NSMutableDictionary dictionary]retain]; = leak
//_ups = [[NSMutableDictionary alloc]init]; = leak
if(![[NSUserDefaults standardUserDefaults] objectForKey:#"Ups"])
{
[self Func2];
}else{
_ups = [[NSUserDefaults standardUserDefaults] objectForKey:#"Ups"];
}
[_ups retain]; // - ok
}
Instruments->Leaks shows that leak detected only when I trying to retain on creation, but if I retain after filling a dictionary all fine.
Thanks.
Looking at just one of the if paths (the second) reduces to this:
_ups = [[NSMutableDictionary alloc]init];
// Returns a retained instance assigned to _ups.
_ups = [[NSUserDefaults standardUserDefaults] objectForKey:#"Ups"];
// Now a new instance is assigned to "_ups` without releasing the first instance.
[_ups retain];
//The above retain is incorrect since the method name does not start with "new" or have "copy" in it. This implies that the returned value should be autoreleased. Review Objective-C naming conventions. See Apple's Objective-C Conventions.
All in all, best practice is to use ARC. With ARC the first instance would have been automatically released.
I've been attempting to get my app to save and load data from a set of arrays in the app, the odd issue is that when the app terminates (completely shut down) the data does not seem to load upon being restarted, i've had a look over a lot of posts, tutorials etc but i can't seem to get it to work, i have two test buttons on the app to trigger the save and load methods and also a button to clear the records, when i use these to test, it works perfectly and the data saves and loads correctly.
My current setup is as follows:
I have a plist file called data.plist in the supporting files directory, inside this file i have the various arrays with the same data in index 0 as the data that is initialised when the global data class creates an instance of itself.
My save code:
- (void)saveData{
// 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:#"data.plist"];
// create dictionary with arrays and their corresponding keys
NSDictionary *plistDict = [NSDictionary dictionaryWithObjects: [NSArray arrayWithObjects: personalitySliderValue, looksSliderValue, humourSliderValue, chemistrySliderValue, emptySlider, notesValues,nameValues, noValues, ratingValues, nil] forKeys:[NSArray arrayWithObjects: #"personality", #"looks", #"humour", #"chemistry",#"empty",#"notes",#"name",#"no",#"rating", nil]];
NSString *error = nil;
// create NSData from dictionary
NSData *plistData = [NSPropertyListSerialization dataFromPropertyList:plistDict format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
// check if plistData exists
if(plistData)
{
// write plistData to our Data.plist file
[plistData writeToFile:plistPath atomically:YES];
}
else
{
NSLog(#"Error in saveData: %#", error);
}
}
My load code:
- (void)loadData{
// 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:#"data.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 CHECK D capitalisation
plistPath = [[NSBundle mainBundle] pathForResource:#"data" 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 list into dictionary object
NSDictionary *dictionaryTemp = (NSDictionary *)[NSPropertyListSerialization propertyListFromData:plistXML mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&format errorDescription:&errorDesc];
if (!dictionaryTemp)
{
NSLog(#"Error reading plist: %#, format: %d", errorDesc, format);
}
// assign values
personalitySliderValue = [NSMutableArray arrayWithArray:[dictionaryTemp objectForKey:#"personality"]];
looksSliderValue = [NSMutableArray arrayWithArray:[dictionaryTemp objectForKey:#"looks"]];
humourSliderValue = [NSMutableArray arrayWithArray:[dictionaryTemp objectForKey:#"humour"]];
chemistrySliderValue = [NSMutableArray arrayWithArray:[dictionaryTemp objectForKey:#"chemistry"]];
emptySlider = [NSMutableArray arrayWithArray:[dictionaryTemp objectForKey:#"empty"]];
notesValues = [NSMutableArray arrayWithArray:[dictionaryTemp objectForKey:#"notes"]];
nameValues = [NSMutableArray arrayWithArray:[dictionaryTemp objectForKey:#"name"]];
noValues = [NSMutableArray arrayWithArray:[dictionaryTemp objectForKey:#"no"]];
ratingValues = [NSMutableArray arrayWithArray:[dictionaryTemp objectForKey:#"rating"]];
}
And finally the app delegate methods:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
// save the app data
[[GlobalData sharedGlobalData]saveData];
NSLog(#"save method run");
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
// load the app data
[[GlobalData sharedGlobalData]loadData];
NSLog(#"load method run");
}
This has literally been making me pull my hair out, so any help would be great!
You can load data at launch time in this app delegate method:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
....
[[GlobalData sharedGlobalData] loadData];
}
I am trying to save data to the plist in my app per person, but it save it to everyone, so for example if I have a field for eye color and I enter Brown in the textfield for John Doe, it save Brown for everyone else as well, is there a way to save this info per person in the app instead using plist? I have had no luck trying:
-(NSString *)pathofFile{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsfolder = [paths objectAtIndex:0];
return [docsfolder stringByAppendingFormat:#"data.plist"];
}
Here is the code I have in my view did load:
- (void)viewDidLoad {
[self loadPerson];
[super viewDidLoad];
NSString *filepath = [self pathofFile];
if ([[NSFileManager defaultManager]fileExistsAtPath:filepath]) {
NSArray *array = [[NSArray alloc]initWithContentsOfFile:filepath];
Drinfo.text = [array objectAtIndex:0];
[array release];
}
UIApplication *app = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(applicationWillTerminate:) name: UIApplicationWillTerminateNotification object:app];
and here is what I have as part of my saveperson method:
NSMutableArray *array = [[NSMutableArray alloc]init];
[array addObject:self.Drinfo.text];
[array writeToFile:[self pathofFile] atomically:YES];
[array release];
You could use a plist just create a NSDictionary for each person inside the plist and add the colour string to that. The steps would be something like
Open up the plist from file, set it to be a NSDictionary and make sure its a mutable copy.
Create a new NSMutableDictionary and add any attributes to that e.g. eye colour.
Add that mutable dictionary to your plist dictionary with the key set to the persons name
Save the plist