Is it possible to use an Xcode build script to download JSON files to the app bundle? - ios

I have a web server, running locally, which serves JSON-formatted data from a number of endpoints. I currently include the data from each endpoint in separate .json files, which I manually add to the app bundle for use within the app. Is it possible to automate this process whenever the project is built, perhaps using an Xcode build script?
Below is an example of what I am trying to achieve.
Fetch the JSON-formatted data from localhost:3000/example.
Stop here if the endpoint cannot be reached.
Save the data in a file called example.json.
Add example.json to the app bundle for use within the app.
Any help with this would be greatly appreciated.
Edit
I have fetched the JSON-formatted data, however I am now looking to see how this data can be copied into the app bundle.
curl -o example.json http://localhost:3000/example

Here's how I did the same thing in my project.
I went to my target -> Build Phases -> + -> 'New Run Script Phase' and added the following script:
curl -o ${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/your_file.json http://localhost:3000/your_file.json
echo "Your file downloaded to ${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/your_file.json"
I am currently working on adding some validation to the process, but as a starting point this is working for me.
Hope that helps!
UPDATE
Here is the same code with some added JSON validation:
YOUR_DATA=$(curl -s "http://localhost:3000/your_file.json" | python -m json.tool);
if [ -n "$YOUR_DATA" ];
then
echo "$YOUR_DATA" > ${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/your_file.json;
fi

Not sure if I'm getting your specifications right here but this is what I have implemented as well as some help from this solution: How can I save a JSON response to a file that would be accessible from within a local HTML file loaded inside UIWebWiew
(1) Here is the function that will grab the json file. This function is basically assigning
the url you are targeting to a string. Then formatting it to a NSURL. This is then
formatted as NSData which will grab the contents of the url. The data will be serialised and
then ready to target the appropriate elements.
- (void)fetchJson
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *urlString = #"localhost:3000/example";
NSURL *url = [NSURL URLWithString:urlString];
NSData *jsonData = [NSData dataWithContentsOfURL:url];
NSError *error = nil;
if(jsonData != nil)
{
NSError *error = nil;
id result = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error];
if (error == nil)
NSLog(#"%#", result);
// (2) This part is dependent on how your json data is arranged (Target the
// right elements).
_endpoint = [result objectForKey:#"endpoint"];
if(_endpoint == null){
NSLog(#"%#", result);
}
else{
//(3) This will save your data into the specified file name under the application documents directory.
NSString *filePath = [NSString stringWithFormat:#"%#/%#", documentsDirectory,#"example.json"];
[result writeToFile:filePath atomically:YES];
}
}
else {
NSLog(#"%#", error);
}
}
(4) To refer to the file in the application documents directory
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSError *jsonError = nil;
NSString *jsonFilePath = [NSString stringWithFormat:#"%#/%#", documentsDirectory,#"data.json"];
NSData *jsonData = [NSData dataWithContentsOfFile:jsonFilePath options:kNilOptions error:&jsonError ];

Related

How to upload .xml file in IOS app using Objective-C?

In my IOS app I have one abc.xml file through which I get data and display it on screen (I have already done with that, no issue for doing this), my problem is,on button click I want to upload any .xml file to my app (I have some .xml file in my ipad) and want to replace that .xml file with abc.xml file, (then i will fetch data through that newly uploaded file)
It is just like "choose file" option in web
can we do this in iOS app using Objective-C?
iOS uses .plist setting configuration files. I don't know how well this would work, but you could try something a little like this...
NSMutableDictionary *abcDict = [NSMutableDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"abc" fileType:#"xml"]];
NSString *someString = (NSString*)[abcDict valueForKey:#"someString"];
NSNumber *someNumber = (NSNumber*)[abcDict valueForKey:#"someNumber"];
NSDictionary *someDictionary = (NSDictionary*)[abcDict valueForKey:#"someDict"];
NSArray *someArray = (NSArray*)[abcDict valueForKey:#"someArray"];
BOOL *someBoolean = [((NSNumber*)[abcDict valueForKey:#"someBool"]) booleanValue]
Just change your code depending on your data type. BOOL is a bit weird, but everything else is written just like that. Use the right key names, and if XML doesn't work, try plugging a .plist file into your app!
If i understood what you want, this should make the trick.
NSURL *url = [NSURL URLWithString:linkURL];
NSData *urlData = [NSData dataWithContentsOfURL:url];
if ( urlData )
{
BOOL success = [db writeToFile:#"locationOfTheFile" atomically:YES];
if (success) {
//all went well
}else{
//error
}
}
edit:
if the file is at the root of your app, you can get the path with this.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
NSString *documentsDir = [paths objectAtIndex:0];
BOOL success = [db writeToFile:[documentsDir stringByAppendingPathComponent:#"fileName"];

Need to get an XML from server encoded in windows-1252 for my iOS application

I have an iOS application which is download from server an XML file encoded in Windows 1252.
I am using the following code to save it to my document folder :
NSString *path = #"http://server/file.xml";
NSURL *URL = [NSURL URLWithString:[path stringByAddingPercentEscapesUsingEncoding:NSWindowsCP1252StringEncoding]];
NSData *xmlData = [NSData dataWithContentsOfURL:URL];
if(xmlData == nil) {
// Error - handle appropriately
NSLog(#"ERROR");
}
NSString *applicationDocumentsDir=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *storePath=[applicationDocumentsDir stringByAppendingPathComponent:#"annonces.xml"];
[xmlData writeToFile:storePath atomically:TRUE];
NSLog(#"write xml");
It doesn't work, I've got an nil response when I try to read it with the parser. How could I do to get it properly. I cannot change the encoding of te xml which is on the server. If I change the XML encoding manually, I've got a correct response.
This is how I pass the XML string to parse it with XML Dictionnary class :
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *finalPath = [path stringByAppendingPathComponent:#"annonces.xml"];
NSString *string = [[NSString alloc] initWithContentsOfFile:finalPath encoding:NSWindowsCP1252StringEncoding error:NULL];
NSLog(#"string: %#", string);
NSDictionary *items = [NSDictionary dictionaryWithXMLString:string];
NSLog(#"dictionary: %#", items);
Your URL variable contains the url address of the location of the XML file you want to download.
However you are applying NSWindowsCP1252StringEncoding to the url, not to the content of the file.
xmlData is nil because dataWithContentsOfURL: cannot find the file at the location you have specified within URL.
You need to download the file first, then once its downloaded then you can be concerned about what encoding its in and how to parse it.
The way you are using NSWindowsCP1252StringEncoding has got nothing to do with the content of the file.

How to load app with local data and subsequently update it when online.

Right now I have an app that successfully parses JSON from my website. So whenever there is no internet connection, my app crashes. Now I am trying to make it so that when the app is loaded with no internet connection, it will show the data that was shown previously. What would be the best way to do this?
I read this article but I don't know how to embed a JSON file into my app bundle. Could someone explain how to do this?
Thanks in advance!
The best way is:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"YourParsedJSON.plist"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (noInternet){
if ([fileManager fileExistsAtPath: path]){
// if this is true, you have a saved version of your JSON
YourArray = [[NSMutableArray alloc] initWithContentsOfFile: path];
// or
YourDict = [[NSMutableArray alloc] initWithContentsOfFile: path];
}
else{
// first time the app is running, and no internet, no json, inform user about this
}
}
else{
// make an array or dictionary ( what is your JSON )
// response can be a NSDictionary or NSArray
// YourArray = parsedJSON or YourDict = parsedJSON
[YourArray writeToFile:path atomically:YES];
//or
[YourDictionary writeToFile:path atomically:YES];
}
I hope it helps !
Use Apple Reachability sample code to check if your app is able to establish connection to your server.
On first successful request-response, parse the JSON and cache it to disk as a .plist file. This will save you parsing the stored response again. A parsed JSON response can be a NSDictionary or NSArray. Use the writeToFile:atomically: API to write it to disk.
On subsequent request, if reachability fails, i.e. no network connectivity, read the cached response from disk. You need to decide the cache duration and update the plist when a fresh response is fetched.
Hope that helps!
EDIT:
I think I did not understand the question completely. Thanks Xman, for pointing it out. What I would have done in this case is - save the last loaded JSON file to my bundle and use it for displaying information while querying the server and loading updates in the background.
The flow should be like this:
Parse and display data using local JSON file. (Assuming there is local copy of JSON file)
Query the server for latest data.
Upon receiving response, update the bundle with the latest JSON file.
Then, do step 1. In case there is no JSON file, just start from step 2. If there is a Network error display the appropriate information.
This SO question answers how to handle Network connections in iOS: How to check for an active Internet connection on iOS or OSX?
Saving file locally:
Assuming you have the unparsed JSON data in a NSString (responseString) do the following:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [NSString stringWithFormat:#"%#/%#", documentsDirectory, #"latest_json.json"];
NSError *error;
[jsonString_ writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error];
NSLog(#"%#", error)
Reading file
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [NSString stringWithFormat:#"%#/%#", documentsDirectory, #"latest_json.json"];
NSString *jsonString_ = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&error];
Previous Answer
Embedding JSON file is similar to embedding any resource into your project. The following method shows you how I added an XML file and accessed it in my app.
Drag and drop your JSON/XML file to your resources group/folder in your XCode window. If you don't have the Resouces folder, it is better you create it. Then in your code do this:
NSString* filePath_ = [[NSBundle mainBundle] pathForResource:#"fileName" ofType:#"json"];
NSString *jsonString = [[NSString alloc] initWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error: NULL];
the variable jsonstring contails the JSON information. It is upto you how you would like to parse it.

writing string to txt file in objective c

Pulling my hair out trying to work this out. i want to read and write a list of numbers to a txt file within my project. however [string writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:&error] doesnt appear to write anything to the file. I can see there is the path string returns a file path so it seems to have found it, but just doesnt appear to write anything to the file.
+(void)WriteProductIdToWishList:(NSNumber*)productId {
for (NSString* s in [self GetProductsFromWishList]) {
if([s isEqualToString:[productId stringValue]]) {
//exists already
return;
}
}
NSString *string = [NSString stringWithFormat:#"%#:",productId]; // your string
NSString *path = [[NSBundle mainBundle] pathForResource:#"WishList" ofType:#"txt"];
NSError *error = nil;
[string writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:&error];
NSLog(#"%#", error.localizedFailureReason);
// path to your .txt file
// Open output file in append mode:
}
EDIT: path shows as /var/mobile/Applications/CFC1ECEC-2A3D-457D-8BDF-639B79B13429/newAR.app/WishList.txt so does exist. But reading it back with:
NSString *path = [[NSBundle mainBundle] pathForResource:#"WishList" ofType:#"txt"];
returns nothing but an empty string.
You're trying to write to a location that is inside your application bundle, which cannot be modified as the bundle is read-only. You need to find a location (in your application's sandbox) that is writeable, and then you'll get the behavior you expect when you call string:WriteToFile:.
Often an application will read a resource from the bundle the first time it's run, copy said file to a suitable location (try the documents folder or temporary folder), and then proceed to modify the file.
So, for example, something along these lines:
// Path for original file in bundle..
NSString *originalPath = [[NSBundle mainBundle] pathForResource:#"WishList" ofType:#"txt"];
NSURL *originalURL = [NSURL URLWithString:originalPath];
// Destination for file that is writeable
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSURL *documentsURL = [NSURL URLWithString:documentsDirectory];
NSString *fileNameComponent = [[originalPath pathComponents] lastObject];
NSURL *destinationURL = [documentsURL URLByAppendingPathComponent:fileNameComponent];
// Copy file to new location
NSError *anError;
[[NSFileManager defaultManager] copyItemAtURL:originalURL
toURL:destinationURL
error:&anError];
// Now you can write to the file....
NSString *string = [NSString stringWithFormat:#"%#:", yourString];
NSError *writeError = nil;
[string writeToFile:destinationURL atomically:YES encoding:NSUTF8StringEncoding error:&error];
NSLog(#"%#", writeError.localizedFailureReason);
Moving forward (assuming you want to continue to modify the file over time), you'll need to evaluate if the file already exists in the user's document folder, making sure to only copy the file from the bundle when required (otherwise you'll overwrite your modified file with the original bundle copy every time).
To escape from all the hassle with writing to a file in a specific directory, use the NSUserDefaults class to store/retrieve a key-value pair. That way you'd still have hair when you're 64.

iOS Efficiently Parse Large JSONs from Documents Dir

When I load my app I download around 10-15 JSON files and store them into my apps documents directory-- some range from a few KBs to 30MB.
Once that is finished, I need to grab each of them from the documents dir, convert to a NSDictionary, and parse into NSManagedObjects.
But, when I do that with the code below, as it goes though each JSON it seems to keep them in memory, until the app ends up crashing. Instruments shows nothing in the 'Leaks' tool, but my app is keeping a ton in memory.
Heres the code that grabs the JSON files:
UPDATED
- (void)parseDownloadedFiles
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [paths objectAtIndex:0];
docDir = [docDir stringByAppendingString:#"/jsons"];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *files = [fileManager contentsOfDirectoryAtPath:docDir error:&error];
if (files == nil) {
// error...
}
for (NSString *file in files)
{
NSString *fileName = [NSString stringWithFormat:#"%#/jsons/%#",
docDir, file];
NSString *content = [[NSString alloc] initWithContentsOfFile:fileName
usedEncoding:nil
error:nil];
NSDictionary *JSON =
[NSJSONSerialization JSONObjectWithData: [content dataUsingEncoding:NSUTF8StringEncoding]
options: NSJSONReadingMutableContainers
error: &error];
...create my NSManagedObjects & store
JSON = nil;
}
}
--
Heres a look at my allocations:
--
Drilling into that first Malloc 44.79 brings shows me these problem lines:
--
This is within the for loop in the code above
Would that NSLog really cause such an issue?
You should put #autoreleasepool {} inside that loop where you read the file. The objects are not being released until the method returns, so the memory will build up inside the loop.
ARC will help you by autoreleasing the objects but you need them to release faster.
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html
Yep, your getContentFromFile... method is returning a retained string. And you never release it on the receiving end.
You need to either autorelease the string when you return it or explicitly release if after you've parsed it into JSON.
(I'd think Analyzer would have found this.)

Resources