Core Data and textfiles in iOS applications - ios

I'm creating a simple iOS application consisting of a few UITableViewControllers. The information displayed in the view controllers will come from a text file (that I'll include in the project's Resources). The text file's contents will come from a spreadsheet.
Since this is my first time working with Core Data I have a few questions:
What format is most common for the text file? CSV, XML or something else?
What's the easiest way to import the data?
A few notes:
The data is static. Ideally the app will load the data into "Core Data" just once (1st time the app is run).
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.

If the data is structured in a relational way then XML or JSON allows that structure to be easily preserved and then easily parsed and saved in your Core Data store. You'll need to use an XML or JSON parser, which will turn your data into an array of dictionaries (or multiple levels thereof if your data structure requires it). You'll simply iterate through the array and dig into the dictionaries (and sub-arrays and sub-dictionaries, if appropriate) and add objects to your store as you go.
If it's flat data, a simple single table that will become a single entity in Core Data, then tab-delimited or CSV text files are fine (and tab-delimited is even easier to parse if there wouldn't be any tabs within the data itself). You can then grab individual rows, break the rows down into an array of data bits (this is where tab delimiting makes is super-simple), create a new object for each row, set its properties to the array elements, and save the context.
The XML/JSON version is more complex than is worth writing out here -- search SO and you'll find lots of examples -- but here's the tab-delimited version (this assumes you don't have a gigantic ball of data that can't reasonably be held in memory):
// Standard Core Data setup here, grabbing the managedObjectContext,
// which is what I'll call it
// Then parse your text
NSString *path = [[NSBundle mainBundle] pathForResource:#"YourTextFileName" ofType:#"txt"];
NSString *content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL];
NSArray *rows = [content componentsSeparatedByString:#"\n"];
// Now that we have rows we can start creating objects
YourManagedObject *yourManagedObject = nil;
for (NSString *row in rows) {
NSArray *elements = [row componentsSeparatedByString:#"\t"];
YourManagedObject *yourManagedObject = (YourManagedObject *)[NSEntityDescription insertNewObjectForEntityForName:#"YourManagedObject" inManagedObjectContext:managedObjectContext;
[YourManagedObject setName:[elements objectAtIndex:0]];
[YourManagedObject setCountry:[elements objectAtIndex:1]];
// Etc. You may need an NSNumberFormatter and/or an NSDateFormatter to turn
// your strings into dates and numbers, depending on your data types
[managedObjectContext save];
}
Poof, all done.

If the data doesn't change, why bother including the text file in the app? Instead, create Core Data file on your Mac and include that as a resource in the app. I presume it's a lot of data that'll take a while to parse, so there's no sense in making your users each wait for that to happen when you could do the parsing once and distribute the result.
To make that happen, take the data model and the parsing code from your app and use them to build a small command-line app that just reads the text file, writes the Core Data file, and exits.

Related

iOS - Best way to save a list of user's favourite objects in memory

I am working on an app that display photos of buildings along with some information about them. The photos are jpegs that all come in the app bundle, and the Building objects associated with them have a few properties such as name, address, and of course, a photos array. I'm loading the Building objects from a plist when the app launches. Everything's pretty straight forward, except that when the user scrolls through the images of buildings they have the option to add the ones they like to a favourite's list for viewing later.
Originally I was going to save each Building object they favorited using Core Data, but now that I think about it it seems a little redundant because all of the Building objects are already loaded from the plist. When the user is scrolling through the images of each building the photos have to display an indication of weather that building has been added to the favourite's list yet, which means I'll have to continuously compare an array of objects taken from CoreData with an array of objects loaded from a plist, which seems like it could be a memory wasting task.
I was wondering if, as an alternative to CoreData, I could add a unique ID property to the Building object and store it in the plist with them. Then when the user adds a Building to their favourites list I can just add it's UID to an array in NSUSerDefaults and make the comparison that way. Does that seem like a sensible way to tackle this?
Storing data like this in NSUserDefaults is a really bad habit to get into. Save user preferences in NSUserDefaults, not sets of data.
Much better idea is to save the user's favorites in a file in the Documents folder. If you've added an "ID property" to your Building object, you can maintain an array of selected IDs. That array can be saved to and read from disk with:
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [documentPaths objectAtIndex:0];
NSString *myFile = [documentsDirectory stringByAppendingPathComponent:#"myFavs"];
and then
// save the array contents to file named "myFavs"
[a writeToFile:myFile atomically:YES];
or
// read the saved array when you launch the app
NSArray *b = [NSArray arrayWithContentsOfFile:myFile];
Of course, you'll want to add error handling, but this is a much, much better idea than stuffing an array into NSUserDefaults.
Perhaps using CoreData for favourites is OK, although, consider using Sets which stores distinct values of the same type in a collection with no defined ordering
Assumptions:
- Your buildings data isn't coming from a dynamic/external source
- You don't have a massive amount of data.
why not add a property to the buildings to flag it as a favorite and store that local when the app goes to the background. (NSUserDefaults or document directory as plist)?. Then in future app loads use the local one that has the flags.
That way you don't need an additional data structure to store identifiers and a list of favorites.
CoreData seems like overkill for the problem you're describing.

Iterate through all objects that have been encoded using NSCoding iOS

I have a class (SpendrList) that has an NSMutableArray property that acts a list that the user saves multiple things to while using an app (it's a datasource for a UITableView). Using the NSCoding protocol, I encode/decode this class as needed.
This works fine, as the array property holds list items from a class I created, SpendrListItem, (also adhering to NSCopying protocol) and I encode it as the user makes any edit in the UITableView, like so:
NSURL *dataFile = [FileSystemHelper pathForDocumentsFile:kFilePathList];
NSString *filePath = [dataFile path];
[NSKeyedArchiver archiveRootObject:_list toFile:filePath];
Right now, I am just dealing with one list so this was relatively easy to set up after a few tutorials on NSCoding. Now, what I want to do is code in support for the user to have multiple lists in my app.
I have a collection view in another ViewController set up where I want to display all the lists created, and I am wondering if I can iterate through all objects I have encoded contained in the app sandbox.
Psuedocode:
for (encodedObject in App Sandbox){
if([encodedObject isTypeOfClass: SpendrList]){
//Add to iVar array to show in collection view
}
}
Multiple Lists [in isolation]?
Sounds like documents. Specifically, if you have multiple chunks of data that are held in relative isolation, even if identical in structure, you should likely manage and persist them separately. Potentially, you might have a master document that keeps track of the inventories of other documents.
That is, persist those lists to separate files.
Fortunately, iOS has an entire infrastructure for helping you with this; see UIDocument.

Multidimensional NSMutableArrays in Objective-C

I am very confused on how to use NSMutableArrays to store multiple dimensions of data.
I'm trying to store a car's make, model, year, and a description of a car (which is an NSString) in an array. The app plays a sound, loads the description, and shows a picture of the car. Originally, I was using a PLIST file to store this information. This was then loaded into a dictionary as the view loaded. However, I am no longer implementing the PLIST file, since it would be much easier for the program to automatically generate this data at runtime, based on the folders in the project.
The folders are broken down as follows:
(Main Resources Folder) -> Makes -> Models -> Years -> Folder containing picture, .mp3, and file with description
For example:
Cars -> Ford -> Mustang -> 1965 -> Pic, .mp3, description in file
//Go to folder
NSString * resourcePath = [[NSBundle mainBundle] resourcePath];
NSString * documentsPath = [resourcePath stringByAppendingPathComponent:#"Cars"];
NSError *error;
//Load names of all makes in folder into memory
NSArray * listOfMakes = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:documentsPath error:&error];
I then use multiple 'for' loops to iterate through this and all other folders, and to load everything else in to NSArrays.
However, I need one single array that is accessible program-wide. So, I am doing this within a singleton. I would like to add these arrays to an NSMutableArray, so that I can access it anywhere. I have viewed many other questions, like these:
objective-c accessing arrays, Adding objects in Multidimensional NSMutableArray, How to add string objects to NSMutableArray, But am still very confused since these only cover up to three dimensions. Having multiple arrays of arrays is very confusing...
Any help would be very much appreciated.
when it comes to multi-dimension...
I suggest you to use NSMutableDictionary or NSDictionary.
But the worst thing I see in your implementation is you are trying to load/hold the whole app data at once, Which will remain in memory till the app is not quit, since its store in singleton.
It will be better if you use CoreData and load only content which is required.
I don't know how you are building your app's logic, but if I were you, I would do something like this:
1. Make navigation based app.
2. In the first view show the list of models (load the list of models from Makes folder).
3. When particular model is selected, in the next view show the list of years.
etc.
And, no need to read all the contents of all folders in to the array. Good Luck!
If you want a 2 dimensional array, than just one car array to it for each car:
[2dimArray addObject:#[make, model, year, description]];

Best practice in recreating CoreData entities with actual data retrieved from the server

Purpose: I have to create entities from files.
So entities represent my data model in CoreData and files have all information for this entities.
All files I get from Internet. For this I use AFNetworking framework.
How I get files (algorithm):
Request plist file. Plist file has values to other urls that I have to download.
When plist was downloaded to my Documents directory on device. I parse it.
When I parse plist I grab url from each item from NSDictionary that represent this plist.
Then I request zip files from this urls.
After zip files were downloaded I unzip them and go to the next step.
Parse unzipped files and create data model.
It is my problem. I have version of file that stored locally and that stored on the server and when version on the server changed I need to reload my data model with actual data. The bad way it is load all data from server again next delete all entities in storage and make new entities from new data. But it is not professional way at first and second it is an additional burden on the traffic, because if I have just one entity that I need to reload why I have to reload other entities that are in the actual state. So maybe someone knows best practice with this question. Of course I can create my solution and it will work, but I want to see how people solve this problem and figure out with the differences in my solution and in the other solutions also.
This is trivial. You simply keep an attribute with the time stamp of the last update and just request the changed and new entities from your server. Then you insert, update or delete as appropriate.
It sounds like you are talking about find-or-create. Depending on the size of the data set and your performance requirements you can do this a couple of ways:
The first way is to fetch your existing Core Data objects and store them in an a dictionary with a unique attribute of the entity serving as the key. Then when you download the new data you can take this key for each parsed object and check your dictionary to find (and optionally update) any existing object, or create a new one:
NSArray *fetchedObjects = //Fetch the existing objects;
NSMutableDictionary *existingObjects = [NSMutableDictionary dictionary];
for (MyClass *object in fetchedObjects)
[existingObjects setObject:object forKey:object.uniqueKey];
//Now iterate through the new data (using JSON as example here)
NSDictionary *downloadedItems = //Download data and serialise into JSON
for (NSDictionary *item in downloadedItems){
NSString *uniqueValue = [item valueForKey:#"uniqueKey"];
MyClass *object = [existingObjects objectForKey:uniqueValue];
if (object==nil){
object = //Insert new MyClass entity into MOC
object.uniqueKey = uniqueValue;
//Set any other values you only need to set once
}
//Set any values you may need to update every time
//This would be where to check a modified date attribute
}
The second way is more sophisticated, and involves less memory overhead. It's described in the Efficiently Importing Data section of the Core Data Programming Guide.
The guide gives a good start but doesn't offer a complete solution; I attempted my own in an answer here: Basic array comparison algorithm

Excel data to be inserted into a coredata model

I have an app that has some data ( static ) that needs to be shipped out with the app. The client gave me this data in Excel(over 300 rows), now I created a model for this in coredata, I was wondering is there a way or a tutorial that shows how can I make an import from excel into my model ? or if someone thinks of a better approach to incorporate this data( SQLLite maybe)?
Thanks
There is a way to import CSV data into sqlite3 at the command line; however, it is recommended that you not do this, since Core Data creates sqlite fields that would need to be dealt with.
If you start with CSV, you could look at doing something like this:
http://macresearch.org/cocoa-scientists-part-xxvi-parsing-csv-data
perhaps with modification to work with your dataset.
To import the data into your application and store it in Core Data I would export the Excel Data to a CSV file. That filetype is easy to parse. I don't know about any libraries that parse Excel files directly. Your code does than look like this.
NSString *fileContent = [NSString stringWithContentsOfFile:#"path/to/file.csv" usedEncoding:&enc error:nil];
NSArray *lines = [fileContent componentsSeparatedByString:#"\n"];
for (NSString *currentLine in lines) {
// Handle this line and store in model
NSArray *fields = [currentLine componentsSeperatedByString:#";"]; // Or other delimiter
// From the excel file you know which column is which property in the model.
// Populate the entity appropriately and store it
}
The above code is without guarantee. I didn't test the code.

Resources