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.
Related
I'm creating a simple iOS application consisting of a few UITableViewControllers. The information displayed in the view controllers will come from a xml file (that I'll include in the project's Resources or direct from dropbox or iCloud). The xml file's contents will be based on user input .
A few notes:
The data is based on the user input means not static. Ideally the app will load the data into "Core Data" from xml file.
Each additional run of the app will just pull data from some Core Data source (that I'm not completely familiar w/ yet) instead of re-loading it from the textfile.
right now I am using XMLwriter to generate simple xml file
Please guide me
thank you
The best pattern here seems to be to use the XML file to "seed" your Core Data database. This only happens the first time. After that you will never again use your XML file but simply update and sync your core data store.
This is far better than generating XML. The problem with XML files (like property lists) is that you have to write the entire file for each little incremental change. If you sync to a store somewhere online, this can take much too much time to be practical.
Assuming you can get a foundation object from the XML file, simply iterate through the object and insert a Core Data one by one.
for (NSDictionary *dict in xmlArray) {
Entity *newObject = [NSEntityDescription
insertNewObjectForEntityForName:#"Entity"
inManagedObjectContext:self.managedObjectContext];
newObject.attribute1 = [dict objectForKey:#"attribute1"];
newObject.attribute2 = [dict objectForKey:#"attribute2"];
// etc...
}
[self.managedObjectContext save:nil];
I am making an Iphone drinking card game app.
All the card mean something different and i want the user to be able to press an info button and then show a new screen with information about the current card. How can i make a document to load text from instead of using a bunch og long strings?
Thanks
You could look into plist files - they can be loaded quite easily into the various collection objects and edited with the plist editor in Xcode.
For instance, if you organize your data as a dictionary, the convenience constructor
+ (id)dictionaryWithContentsOfURL:(NSURL *)aURL
from NSDictionary would provide you with as many easily accessible strings as you need.
This method is useful if you consider your strings primarily data as opposed to UI elements.
Update:
As #Alex Nichol suggested, here is how you can do it in practice:
To create a plist file:
In your Xcode project, for instance in the Supporting Files group, select New File > Resource > Property List
You can save the file in en.lproj, to aid in localization
In the Property list editing pane, select Add Row (or just hit return)
Enter a key name (for instance user1) and a value (for instance "Joe")
To read the contents:
NSURL *plistURL = [[NSBundle mainBundle] URLForResource:#"Property List" withExtension:#"plist"];
NSLog(#"URL: %#", plistURL);
NSDictionary *strings = [NSDictionary dictionaryWithContentsOfURL:plistURL];
NSString *user1 = [strings objectForKey:#"user1"];
NSLog(#"User 1: %#", user1);
A plist, a JSON string, and an SQLite database walked into a bar ...
Oops!! I mean those are the three most obvious alternatives. The JSON string is probably the easiest to create and "transport", though it's most practical to load the entire thing into an NSDictionary and/or NSArray, vs read from the file as each string is accessed.
The SQLite DB is the most general, and most speed/storage efficient for a very large number (thousands) of strings, but it takes some effort to set it up.
In my other answer, I suggest the use of a dictionary if your texts are mostly to be considered as data. However, if your strings are UI elements (alert texts, window titles, etc.) you might want to look into strings files and NSBundle's support for them.
Strings files are ideally suited for localization, the format is explained here.
To read them into you app, use something like this:
NSString *text1 = NSLocalizedStringFromTable(#"TEXT1", #"myStringsFile", #"Comment");
If you call your file Localizable.strings, you can even use a simpler form:
NSString *str1 = NSLocalizedString(#"String1", #"Comment on String1");
A useful discussion here - a bit old, but still useful.
I'm currently using a singleton as a data store for my app. I essentially store a number of events that are pulled and parsed from a web service and then added as needed. Each time I make a request from the web service, I parse the results and see if the items already exist. If they do, I delete them and add the updated version provided by the web service.
Everything appeared to be working properly until I fired up the Instruments panel to find out that my system is leaking the objects every time it loads them from the web service (from the second time on). The core method where things appear to be messing up is this one, which is located in my HollerStore singleton class:
- (void)addHoller: (Holler *)h
{
//Take a holler, check to see if one like it already exists
int i = 0;
NSArray *theHollers = [NSArray arrayWithArray:allHollers];
for( Holler *th in theHollers )
{
if( [[th hollerId]isEqualToString:[h hollerId]] )
{
NSLog(#"Removing holler at index %i", i);
[allHollers removeObjectAtIndex:i];
}
i++;
}
[allHollers addObject:h];
}
Quick explanation: I decided to copy the allHollers NSMutableArray into theHollers because it's being updated asynchronously by NSURLConnection. If I update it directly, it results in a crash. As such, I switched to this model hoping to solve the problem, however the Instruments panel is telling me that my objects are leaking. All the counts are exactly the # of items I have in my data set.
From what I can tell removeObjectAtIndex isn't effectively removing the items. Would love to get the thoughts of anybody else out there on three things:
Is my analysis correct that something else must be retaining the individual hollers being added?
Should I be using CoreData or SQLite for storing information pulled from the web service?
Do you know how long data stored in a Singleton should be available for? Until the app is killed?
Update
I think I've found the source, however perhaps someone can provide some clarity on the proper way to do this. I've created a method called parseHoller which takes a dictionary object created through SBJSON and returns my own model (Holler). Here are the last couple lines:
Holler *h = [[[Holler alloc] initFromApiResponse:hollerId
creatorId:creatorId
creatorName:creatorName
creatorImageUrl:creatorImage
comments:comments
attendees:attendees
wishes:wishes
invitees:invites
createdAt:createdAt
text:text
title:title
when:when]autorelease];
//(some other autorelease stuff is here to clean up the internal method)
return h;
I figured that since I'm returning an autoreleased object, this should be fine. Do you see anything wrong with this?
Have you tried to do a retain count on the objects that is leaking? Maybe that could clear up when or where it is being retained.
The code should be
[putObjectHere retainCount];
and then write to an NSLog
Hope it gives you something
Peter
I'm trying to create a user editable plist where the user can store a custom created workout routine including strings referenced from another data.plist in the app bundle.
I'm assuming NSCoding is the best way to go about this. So far I have the interface setup as a nav bar and table view.
I want it to be blank be default and the user has to press the "+" that is in the top right of the nav bar. Then he could enter a name for an entry in an array, for example chest day, or bicep day. And within that array, will be a dictionary or another array of strings of the particular exercises for that day, for example bench press, or bicep curl.
This plist needs to be editable so it will be going in the users document folder and not in the app bundle.
Example:
Top array consists of Chest Day, Back Day, Leg Day. Within Chest Day dictionary, include bench press, chest pull, pushup, etc.
Update:
Adding this method to search for routine file;
-(void)loadData
{
if(YES)
{
NSString* documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString* routineFile = [documentsPath stringByAppendingPathComponent:#"routine.plist"];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:routineFile];
}
else
{
//load file
}
}
NSCoding is the protocol used by NSArchiver, NSKeyedArchiver, etc., not for serializing an array into a property list.
Forget about the idea that the user is going to edit a property list. The user is going to edit data in your app -- the fact that it's stored as a property list is just an implementation detail. When your app starts, you read the data stored in the data file. The user edits it, looks at it, whatever. At some point later, perhaps after each edit, perhaps just before the app quits, you write the data back out to the file. Since it's a property list, don't worry about updating the file; you already have all the data, so write a whole new property list and then use that file to replace the old one.
Perhaps I'm wrong and you really do intend for the user to edit the property list by hand, with a text editor. This would be a mistake. It's great the property lists are human readable, but asking your users to edit your raw data files by hand is a strong indication that your app is broken. The whole purpose of your app is to keep track of this information for the user; if they wanted to use a text editor to manage it, they wouldn't need your app. So, with that said, I hope I'm not wrong. ;-)
I don't think I'd use NSCoding for this - if all you're working with is standard plist objects like NSArray, NSDictionary, and NSString, the top array's -writeToFile:atomically: method is an easy way to do the job.
I have an application that downloads an xml file, parses the file, and creates core data objects while doing so. In the parse code I have a function called 'emptydatacontext' that removes all items from Core Data before creating replacements items from the xml data. This method looks like this:
-(void) emptyDataContext
{
NSFetchRequest * allCon = [[NSFetchRequest alloc] init];
[allCon setEntity:[NSEntityDescription entityForName:#"Condition" inManagedObjectContext:managedObjectContext]];
NSError * error = nil;
NSArray * conditions = [managedObjectContext executeFetchRequest:allCon error:&error];
DebugLog(#"ERROR: %#",error);
DebugLog(#"RETRIEVED: %#", conditions);
[allCon release];
for (NSManagedObject * condition in conditions) {
[managedObjectContext deleteObject:condition];
}
// Update the data model effectivly removing the objects we removed above.
//NSError *error;
if (![managedObjectContext save:&error]) {
DebugLog(#"%#", [error domain]);
}
}
The first time this runs it deletes all objects and functions as it should - creating new objects from the xml file. I created a 'update' button that starts the exact same process of retrieving the file the proceeding with the parse & build. All is well until its time to delete the core data objects. This 'deleteObject' call creates a "EXC_BAD_ACCESS" error each time. This only happens on the second time through.
Captured errors return null. If I log the 'conditions' array I get a list of NSManagedObjects on the first run. On the second this log request causes a crash exactly as the deleteObject call does.
I have a feeling it is something very simple I'm missing or not doing correctly to cause this behavior. The data works great on my tableviews - its only when trying to update I get the crashes.
I have spent days & days on this trying numerous alternative methods. Whats left of my hair is falling out. I'd be willing to ante up some cash for anyone willing to look at my code and see what I'm doing wrong. Just need to get past this hurdle.
Thanks in advance for the help!
Did you save the context after removing the objects in the for loop? Be aware that deleteObject: does not delete the object immediately, it simply schedule it for deletion when changes are committed, i.e., when you save the context.
EDIT: Your problem may be related to how you present your data to the user on your table view. Without additional code is difficult to tell exactly (are you using NSFetchedResultsController or not?), but my guess is that the interaction between deleting the data and showing them on the table is not correct. Probably, what is happening is that your table is told to visualize your data, but then, when you delete them, you are not updating correctly the table.
Wow, so after a few days of testing I went down a path that led me to tracking down Zombies & memory mgmt. This was a situation where the errors I was recieving were leftover from issues in another area. When parsing the data and placing it into Core Data I released an object that was to be autoreleased. So any subsequent calls to that item within core data (save, deletec, etc) caused a crash. Thank you all for your help and I vow to pay closer attention with my memory mgmt calls.