I have newer version app to update to app store, but since there were some obsolete data: such as database and some files that I don't need in the newer build or some databases which are use same name as newer build but the DB structure is different, so is there a way to remove all personal data of last version when user do the update? Then after user updated my app the app feels like a brand-new one.
Deleting the entire contents of your application's sandbox in one fell swoop is really something that only can be accomplished by the user removing and re-installing your application.
There are two things to consider here:
If you know where the database/files reside, you can use the NSFileManager methods to remove them explicitly. Especially removeItemAtPath:error:. The Class Reference is quite helpful.
You should proceed with caution when clearing out the entire application's state. Do your users know this is going to happen? Are they going to lose sensitive data?
There is a pattern that you can follow.
Let's say you db is called MySqliteDatabase.sqlite. There is some other data in Documents directory but you dont want to use it and delete it on first install of next version.
With the new version, rename the db to MySqliteDatabasev2.sqlite. On appstartup, use NSFileManager to look for MySqliteDatabasev2.sqlite in the documents directory. If it doesn't exist, blow away the documents directory and copy My SqliteDatabasev2.sqlite there.
For any subsequent launches, the above logic will find the v2 data and will do nothing with the documents directory.
For any subsequent version upgrades, you can follow this scheme to update database name.
Have caution: make sure to change rest of the code to use the new name as well. Best practice for that is to define it in some commons header file (i have a commons.h in every project)
#define DB_NAME #"MySqliteDbv2.sqlite"
and then make sure everywhere you are just using DB_NAME and nothing anything else. So whenever version changes:
1. rename the db file in xcode navigator.
2. rename the DB_NAME define.
That's it.
On app start up you can simply remove the entire store file with something like this.
NSString *path = <path to your file>
[[NSFileManager defaultManager] removeItemAtPath:[self persistentStorePath] error:nil];
You can use this code to get the path to the documents directory, if that is where you store the data.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [[[paths lastObject] stringByAppendingPathComponent:#"subdirectoryIfAny"]
stringByAppendingPathComponent:#"name of your .sql or whatever"];
If you do not want to blow away all the data, then you have read in the records you want to delete and then manually remove them.
Related
I have been working on an app where the user inputs data stored in core data everyday (two attributes an NSNumber and one as NSDate) and I wanted to improve that by allowing the user to import data from a external file such as csv or any other supported format through a button click. Any suggestions on how to proceed efficiently to do this?
Thank you.
Edit: Just adding a screenshot of the csv file as well as the output of the csv parser as NSArray. Basicly need to fetch the attribute separately and store them in core data on button click.
- The input file as csv:
- Sample csv parser output(NSarray):
I needed to achieve something similar recently.
A couple of members of my project team wanted to take our app prototype out to show potential clients, but wanted to show different data to each client. We solved this by allowing members of our project team to create their own test data before meeting with the client.
I achieved this by creating an example .csv file and distributing it to the other guys in the project team. They populate it with their own test data and use iTunes File Sharing to drop the .csv test data file on to the device.
On load, the app scans its Documents directory for a the test data file. If it exists, it parses the .csv file and persists to the database.
For the CSV parsing, I used Dave DeLong's CHCSVParser: https://github.com/davedelong/CHCSVParser
Plenty of help is available on setting up iTunes file sharing for your app. A quick Google finds this tutorial (http://www.raywenderlich.com/1948/how-integrate-itunes-file-sharing-with-your-ios-app) which should help you out, if you need it.
Edit- added help on storing data from .csv in Core Data
You stated in your original post that you store an NSNumber and NSDate. Taking that as a starting point, you might have a .csv file in the following form:
+----------------+--------------+
+ NSNumberColumn | NSDateColumn |
+----------------+--------------+
+ 1 | 2013-05-15 |
+ 2 | 2013-06-15 |
+ 3 | 2013-07-15 |
+----------------+--------------+
Assuming the output from the CSV parser is an NSArray of NSArrays, you could create the Core Data objects as follows:
I would create a couple of macros for the column numbers:
#define NSNumberColumn 0
#define NSDateColumn 1
Then iterate over the rows in the .csv file:
NSArray *rows = [NSArray arrayWithContentsOfCSVFile:pathToFile]; //CHCSVParser specific parsing method
for (NSArray *row in rows)
{
NSString *numberString = [parsedCsvRow objectAtIndex:NSNumberColumn];
NSString *dateString = [parsedCsvRow objectAtIndex:NSDateColumn];
NSNumber *number = // parse numberString to an NSNumber. Plenty of other posts on achieving this.
NSDate *date = // parse NSDate from dateString. Plenty of other posts on achieving this.
NSManagedObjectContext *context = [self managedObjectContext];
NSManagedObject *myCoreDataObject = [NSEntityDescription insertNewObjectForEntityForName:#"MyCoreDataObject" inManagedObjectContext:context];
[myCoreDataObject setValue:number forKey:#"NSNumberColumn"];
[myCoreDataObject setValue:date forKey:#"NSDateColumn"];
NSError *error;
if (![context save:&error]) {
NSLog(#"%#", [error localizedDescription]);
}
}
Note: Input validation and null checks have been ommited for brevity. I have also taken the liberty of making up your NSManagedObject property names, this will need updating. The above code should be separated in to a more suitable class structure.
I'm not at a Mac right now, so unfortunately I can't check if this works.
Hope that helps.
Try to use plist or json, they are already supported on iOS instead of CSV. CSV would require an third party parser. Using json or plist you will only need to loop throught the elemnts of the collections to create you persistent store. If you have just the CSV you can do a mid conversion using different free tools that you can find for free on the internet and later add to your bundle or publish to your site.
Here's what you do when you already have your CSV file parsed and data is ready to use in Objective-C.
Create a separate context for the import. You don't know how big the data can be, so you probably don't want to block one of your existing contexts while importing.
Iterate through the entries in the parsed data and insert new managed objects configured from each entry.
Every 200, 500, or 1000 entries (different for everybody, you'll need to test what's working best for you) save the context and, if needed, post a notification that a batch has been imported.
To keep the memory low, reset the context and forget all the objects that you created in this import context.
After the loop is finished, don't forget to save the last time.
Now how do you bring the data into another context, say, UI context?
This depends on the way you organized your Core Data stack. For example, import context can be configured as a child of the UI context. In this case, after each save to the import context the changes will be pushed to the UI context (and don't forget to save the UI context as well to push changes further).
But this is not the most efficient approach, because UI context, which is a context on the main thread, is involved in the import, and additional work is done on the UI thread that blocks it. I recommend creating the import context not as a child, but connected to the persistent store coordinator directly. To bring changes to the UI context in this case you either need to call mergeChangesFromContextDidSaveNotification: method after each save or you just refetch in the UI context after each save and in the end. The latter is easier on the UI context and particularly on NSFetchedResultsController, if you use it, because it doesn't need to replay changes to the updated objects one-by-one.
I'm having some difficulty with my sqlite prepare statement. I get an error saying my table does not exist, although I've checked in multiple places for it, and it does exist, so I'm confuzzled.
The file is in the correct iPhone Simulator Application folder
The file is added to my project and viewable in the project navigator
It is also in my build phases- Copy Bundle Resources area.
I've cleaned and started running again.
The database exists and running my sql statement gets me just the
results I expected.
- (NSMutableArray *) getMyWorkout{
NSMutableArray *workoutArray = [[NSMutableArray alloc] init];
#try {
NSFileManager *fileMgr = [NSFileManager defaultManager];
NSString *dbPath = [[[NSBundle mainBundle] resourcePath ]stringByAppendingPathComponent:#"IOSDB.sqlite"];
NSLog(#"Db path is %#",dbPath);
BOOL success = [fileMgr fileExistsAtPath:dbPath];
if(!success) {
NSLog(#"Cannot locate database file '%#'.", dbPath);
}
if(!(sqlite3_open([dbPath UTF8String], &db) == SQLITE_OK)){
sqlite3_close(db);
NSLog(#"Failed to open database with message '%s'.", sqlite3_errmsg(db));
}
const char *sql = "SELECT Id, type, difficulty, duration, description FROM workoutTbl";
sqlite3_stmt *sqlStatement;
if(sqlite3_prepare(db, sql, -1, &sqlStatement, NULL) != SQLITE_OK){
NSLog(#"%s Prepare failure '%s' (%1d)", __FUNCTION__, sqlite3_errmsg(db), sqlite3_errcode(db));
} //...
When I run it, I get the file path and the following error
2013-02-01 18:07:08.060 TriShake[9251:c07] -[MyWorkoutList getMyWorkout] Prepare failure 'no such table: workoutTbl' (1)
I've checked out these other questions, but have been unable to find a solution
Sqlite Prepare Failed: no such table<tablename>
Sqlite3 gives "no such table" error on iPhone
I understand sqlite3_open() creates an empty database for you if the database path does not exist, but i know it exists, so frustration ensues. Any help or guidance you could give me would be much appreciated.
In terms of your immediate problem, it's going to be something simple.
You say you've "cleaned and built again", but have you actually removed the old app from your simulator? Either remove the app manually, or, easier, just reset the simulator entirely by choosing "reset content and settings" from the "iOS Simulator" menu. Sometimes Xcode is not good about knowing what files to copy over (particularly in a case like this where your running it on the device may be changing the timestamp of the file in the simulator's bundle!)
Run the app again.
If the app doesn't work as expected, open up the database in the simulator folder from the Mac and check out the database to make sure the table is there and precisely as you expected it to be. So navigate to the app, open the bundle (you may have to choose the "show package contents" option), confirm the existence of the database, but just as importantly, open it up this particular copy of the database in your favorite Mac sqlite3 tool of choice and confirm the existence of the table there.
Let us know what you find. Again, it's got to be something simple such as:
Perhaps the process of rebuilding the app was not reinstalling everything; I've occasionally had problems where Xcode elected to not re-copy something during the install on my simulator;
Perhaps your database in your project was accidentally put in a subdirectory, worse, you might have two copies sitting in different directories;
Perhaps the database in your Xcode project is missing (or has a typo or (esp in the case of the device) has incorrect filename capitalization) in the name of the table or file;
Etc.
For a lot of these errors, you won't notice the problem until you completely reset the simulator itself. There are a million little things it could be, but hopefully completely resetting the simulator and starting over will help you find the issue. It's always something simple when it comes to these sorts of issues.
Some other minor observations:
You probably should not be opening databases from the bundle. Programmatically copy it from the bundle to the Documents folder, and open the database from there. I know it seems unnecessary, but it's important for a myriad of reasons (if db changes during operation of the app, if db accidentally gets created on you, don't let Xcode get confused about things that changed (even if only file timestamps) in the bundle changing behind Xcode's back, etc.)
You should, if you need the database to be there, use sqlite3_open_v2, using either SQLITE_OPEN_READWRITE or SQLITE_OPEN_READONLY for flags (but do not include SQLITE_OPEN_CREATE). It causes headaches to ever give sqlite a chance to create a blank database for you, or otherwise modify it, so never give it an opportunity to so.
I have encounter the same problem as yours. If the IOS can not find the designated database file, defaultly it will create one for you instead of throwing an error. So you must open the database file IOS created for you which is blank so it off course contain the table you expected.
what I deal with it :
1 you have to bundle the resource file named *.sqlite3 into your project
2 Then You have to use [NSBundle mainBundle] pathFordirectory...... function to search your proper database file.
then you can open the database file you expected and can operate it properly
Best regards,
Not enough rep to comment on Jack's post, but that helped me.
In my case, I had mistyped my path for resource extension:
// Wrong
NSString *sqLiteDb = [[NSBundle mainBundle] pathForResource:#"productList"
ofType:#"sqlite3"];
// Should have been (added db ext)
NSString *sqLiteDb = [[NSBundle mainBundle] pathForResource:#"productList"
ofType:#"db"];
I would always get past the:
if (sqlite3_open([sqLiteDb UTF8String], &_database) == SQLITE_OK))
because it was automatically creating a db file for me.
On my iOS application I'm unziping files in "app/temp" folder like this:
NSString *unzipFolder = [[CommonFunctions getCachePath] stringByAppendingPathComponent:#"/temp/"];
and once im done with it im deleting item with:
[[NSFileManager defaultManager] removeItemAtPath:unzipFolder error:&e];
Issue is that bcz im creating mulital copy of unzip files some of the files Images name are same and displaying wrong images, and i dont find error on why my deleting function does not work!
IS there any way that i can do unziping of folder on diffrent path for each message that open by user?
Thanks :)
It sounds like all you're asking is how to generate a unique name for your unzipFolder each time.
Just don't use a hardcoded name. Almost anything will do. For example:
NSString *unzipFolderTemplate = [[CommonFunctions getCachePath] stringByAppendingPathComponent:#"temp.XXXXXX"];
char *template = strdup([template fileSystemRepresentation]);
if (mkdtemp(template)) {
NSString *unzipFolder = [NSString stringWithFileSystemRepresentation:template
length:strlen(template)];
free(template);
// do the work
[[NSFileManager defaultManager] removeItemAtPath:unzipFolder error:&e];
}
The nice thing about mkdtemp is that it creates the directory for you, and guarantees there are no race conditions, somehow-left-over directories, or other problems. It's also more secure against, say, someone writing a crack or other jailbreak hack that exploits your code by predicting the path. The downside is, of course, that you have to drop down to C strings (which means an explicit free). But, as I said, there are many possibilities, and almost anything will do.
Also, note that I'm using #"temp.XXXXXX", not #"/temp.XXXXXX/". That's because -[stringByAppendingPathComponent:] already adds any necessary slashes for you (that is, in fact, the whole point of the method), and directory-creation functions don't need a trailing slash, so both of the slashes are unnecessary.
Meanwhile, I'm still a bit confused by what you're trying to do. If you need to keep a unique folder around for each message, and delete the folder when you're done with that message, and you could have multiple messages open at once, you need some way to remember which folder goes with which message.
For that, create an NSMutableDictionary somewhere, and right after the free(template) you'll want to do something like [tempFolderMap addObject:unzipFolder forKey:messageName]. Then, when closing a message, you'll do [tempFolderMap objectForKey:messageName] and use the result to the removeItemAtPath:error: message (and then you can also remove the key from tempFolderMap).
I noticed that NSHomeDirectory returns a different path each time i restart the App with Xcode, and apparently even if i click the icon manually since it doesn't load the file's contents. I'm stunned that it gives me a different directory each time i restart the app. This happens on both simulator and device and even if i use the "ForUser" method. My calls look like this:
NSString* fullPath = [NSHomeDirectory() stringByAppendingPathComponent:#"test.file"];
or
NSString* fullPath = [NSHomeDirectoryForUser(#"MrX") stringByAppendingPathComponent:#"test.file"];
or even
NSString *docsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *path = [docsDirectory stringByAppendingPathComponent:#"test.file"];
And the log of two app starts looks like this, notice how the GUID part has changed:
loadCharacters: /var/mobile/Applications/ABE7E33E-439B-4258-8FC1-127A3CD00D87/test.file
loadCharacters: /var/mobile/Applications/71C02507-6347-4693-8CC1-537BE223179E/test.file
What am i doing wrong, or what am i missing?
SOLUTION:
At first i was saving to NSHomeDirectory itself, not the "Documents" subdirectory. Since the App directory changes with each deployment but only the files in the "Documents" directory get copied, the saved file was lost after each deployment. Then i used the "Documents" folder but someone suggested i should probably put it in a subfolder of "Documents" i did just that but forgot to create the folder, hence the file wasn't saved. I double-failed on this one. :)
Simply saving to NSHomeDirectory() + "Documents/test.file" works.
You're doing this correctly. Each time an app is installed into the simulator or device, it's placed into a different directory (I'm not sure why, but that's what happens). However, all of its directory structure is moved to the new position. Thus, your RELATIVE paths will remain unchanged.
The GUID portion of an app's home directory appears to be just a random UUID (as indicated by the 3rd group of hex digits starting with a '4' and the 4th group starting with '8','9','A' or 'B'. See the wikipedia entry on 'universally unique identifier' as a good starting place if you want to learn more about UUIDs and unique IDs in general. The reason for using a unique id in the directory path is just to separate one app (and multiple deploments of the same app) from one another as part of sandboxing (IMHO).
I have found several snippets of code describing how to write data to a user's application Documents folder. However, when I try this out in the iPhone simulator, no files get created. I called
[NSFileManager isWritbleAtPath:<my document folder>]
and it returned 0 (false). Do I need to make this folder explicitly writable, and if so, how do I do it?
The iPhone simulator should be able to write to the entire disk. My app routinely dumps test files to the root level of my boot volume (using [NSData's writeToPath:#"/test.jpg" atomically:NO]).
Are you sure that you've correctly determined the path to the documents folder? You need to expand the tilde in the path. Here's the code my app uses to put things in the documents folder. I don't think there's any more setup involved!
brushesDir = [[#"~/Documents/BrushPacks/" stringByExpandingTildeInPath] retain];
// create brush packs folder if it does not exist
if (![[NSFileManager defaultManager] fileExistsAtPath: brushesDir])
[[NSFileManager defaultManager] createDirectoryAtPath:brushesDir withIntermediateDirectories:YES attributes:nil error:nil];
NSLog(#"writable: %d", [[NSFileManager defaultManager] isWritableFileAtPath:NSHomeDirectory()]);
This prints 1 on the console.
Did you mean to call the method isWritableAtPath or isWritableFileAtPath ? And did you mean to call it on the class itself, or on a (default) instance of it?
Thanks for the pointers. So after a toiling through a few documents, I found the thing I was doing wrong: trying to save an NSArray that wasn't composed of basic datatypes such as NSDictionary, NSArray, or NSString. I was trying to save an array of MPMediaItems (from the MediaKit Framework in SDK 3.0+).
I had a trivial issue with the file writing to NSBundle. I had a requirement where a text file needs to be updated with the server as soon as app launches and it worked well with the simulator but not with the device. I later found out that we don't have write permission with NSBundle. Copying the file into Documents directory from NSBundle and using for my purpose solved my problem. I use :
[myPlistData writeToFile:fileName atomically:NO];