Updating Plist file via download iOS - ios

I've got a quiz app on the Apple app store. The questions are stored in a Plist file. What I'm looking to do is find a way to update the Plist file via downloading a new version and not having to submit an update every time I have new questions to add
Does anyone know of a decent tutorial which may help me?
Many thanks.

I'm not sure about tutorials, but the steps to achieve what you describe are pretty simple:
Create a url request to your remote data
Parse the returned data
Write the parsed data to a new local plist
Eg:
// Create a NSURL to your data
NSString *dataPath = #"www.mydata.com/path";
NSURL *dataURL = [NSURL URLWithString:dataPath];
// Create an asycnhronous request along with a block to be executed when complete
[NSURLConnection sendAsynchronousRequest:[[NSURLRequest alloc] initWithURL:dataURL]
queue:[[NSOperationQueue alloc] init]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if (error) {
NSLog(#"%#", error.localizedDescription);
// Handle the request error
}
else {
// We have some data, now we need to serialize it so that it's usable
NSError *serializationError;
NSDictionary *serializedDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&serializationError];
if (serializationError) {
NSLog(#"%#", serializationError.localizedDescription);
// Handle the serialization error
}
else {
// We have a serialized NSDictionary, now we just want to write it
// First we create the path to write it to, eg. uers documents directory
NSURL *documentsDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
// Then write it
BOOL success = [serializedDictionary writeToFile:documentsDirectory.path atomically:YES];
if (!success) {
NSLog(#"Error writing file");
}
}
}
}];
Note: You may want to think about storing your data in a remote database like Parse. This way you can query for new questions and only download those so you're not using unnecessary bandwidth. You may also want to consider using Core Data to maintain your local data rather than writing it to a plist. The main advantage being that you don't have to serialise the entire plist into memory to use it - you can just query for the particular questions you need.
Hope this helps.

Related

What is used when we want to bring down and present data from an API, but don't require persistence?

I use Core Data for most of my projects that require data persistence from launch to launch, but what if I just want to pull information down from an API such as Twitter's and present it to the user, and I don't require it to be persistent from launch to launch?
Is the typical solution here to simply use NSMutableArray and store all the objects therein, and create a class for what's stored in it, similar to Core Data's managed objects?
I've had great success using Mantle.
Yes, an NSMutableArray of custom objects will work. An NSMutableArray of NSDictionary's is also a common solution, and works well for simple data sets.
https://developer.apple.com/library/ios/referencelibrary/GettingStarted/RoadMapiOS/ThirdTutorial.html#//apple_ref/doc/uid/TP40011343-CH10-SW1
Assuming that you can request a JSON response from the server, the code to pull information from the server would look something like this. The jsonData object produced by this code consists of nested NSArrays and NSDictionarys. The NSLog at the end will dump the data set returned by the server.
- (void)retrieveJSONdataFromServer:(NSString *)param1
{
NSError *error;
NSString *str = [NSString stringWithFormat:#"http://www.example.com/sometopic/api/json?param1=%#", param1 ];
NSURL *url = [NSURL URLWithString:str];
NSData *data = [NSData dataWithContentsOfURL:url options:0 error:&error];
if ( !data )
{
NSLog( #"%#", error );
return;
}
id jsonData = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if ( !jsonData )
{
NSLog( #"%#", error );
return;
}
// do something with the JSON data here
// for this sample code, we just dump the data to the debug console
NSLog( #"%#", jsonData );
}

How to update embedded JSON file in iOS app bundle whenever refresh button is clicked

I have a application where my data is offline in JSON format but i want the app to update the JSON file from the internet whenever refresh button is touched.
I have used JSON file as local database and the updated database would be generated on the server when the database is updated. The app should download the JSON file from internet and use it as the local database.
How this can be made possible? I am confused with what method is to be used and how it can be done.
You cannot overwrite a JSON that is bundled with your app. However, you can update a JSON stored outside of your app bundle - for example in your app's Documents folder.
You will need methods for saving a JSON:
-(void)saveJSONWithData:(NSData *)data
{
NSString *path = [[self applicationDocumentsDirectory] stringByAppendingFormat:#"/data.json"];
[data writeToFile:path atomically:YES];
}
- (NSURL *)applicationDocumentsDirectory
{
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
inDomains:NSUserDomainMask] lastObject];
}
You will also need to download the JSON. Bet way to do it is to perform a request in the background so the UI thread is not blocked.
- (void)getJSONDataAtURL:(NSURL *)urlWithJSON
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^
{
// Download the data in background.
NSData *data = [NSData dataWithContentsOfURL:urlWithJSON];
[self saveJSONWithData:data];
dispatch_async(dispatch_get_main_queue(), ^
{
// Do your tasks on the main thread.
});
});
}

UIManagedDocument does not save to disk

I followed the Stanford iOS 7 course of Fall 2013 and I'm getting used to all the concepts, though I encounter a problem with Core Data's UIManagedDocument and persistent saving.
My application is similar to the courses one, and all the Model/Object works fine and displays correctly. The code does not differ much from the provided material; all with instantiation, file handling, object context and that stuff and with some NSLog debugging and manual save control I made sure, the context saves all the changes made to it (e.g. via the notification the UIManagedDocument fires).
What does NOT work is keeping that stuff on file. I mean, it does not save it to file, though it says it saves the context. I thought that was related to autosaving, so I created a Button in the UI to manually do that. But still no persistent save. I have no idea how that can be. I checked the storage first with the App with the parts that actually add objects to the document being disabled. Then I moved on to use a third party app that can display the contents of an apps storage. The structure is there, but no saved data.
I came across a website, where someone said that can be related to missing any required values in the model when setting the objects values. Not the case, tested with a subclass of UIManagedDocument with that handleError: thing.
Anyone else got this? Data structure is fairly simple, just one entitity with about 6 strings and one other type of value. I don't want to post all the code here, since it's a lot. If you have a suspicion on what it can be I can post parts of the code.
Code of creating the document in AppDelegate.m:
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *documentsDirectory = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *url = [documentsDirectory URLByAppendingPathComponent:#"AppDocument"];
DebuggingManagedDocument *document = [[DebuggingManagedDocument alloc] initWithFileURL:url];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
document.persistentStoreOptions = options;
if ( [fileManager fileExistsAtPath:[url path]] ) {
[document openWithCompletionHandler:^(BOOL success){
if ( !success ) {
NSLog(#"Could not open document.. :(");
} else {
[self documentIsReady:document];
}
}];
} else {
// Create
[document saveToURL:url forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success){
if ( !success ) {
NSLog(#"Could not save document... :(");
} else {
NSLog(#"Saved document!!");
[self documentIsReady:document];
}
}];
}
Code of
Regards
Try using the JOURNAL=DELETE mode persistentStoreCoordinator option instead of the default WAL mode.

iOS NSURL queuing mechansim for multiple requests from file

I am very new to iOS development, but I would like to make an app that has two table view controllers (columns): both are a row of images that act as links. The first would be a column of YouTube videos and the second a column of websites. I would like to have all these listed in a file file.txt listed like so: V, http://youtube.com/example W, http://example.com
There would be a long list of those, the V meaning its a video (for the video column) and W for the websites. Now, I understand how to being the single file in, but what happens afterwards is my concern. Can I read each line into some sort of queue and then fire the NSURL request for each one consecutively? How can that be done with NSURL? Is there perhaps a better approach?
There are two questions for me:
Is a text file really the best format?
I might suggest a plist or archive (if the file is only going to exist only in your app's bundle and/or documents folder) or JSON (if it's going to live on a server before delivering it to the user) instead of a text file. It will make it easier to parse this file than a text file. For example, consider the following dictionary:
NSDictionary *dictionary = #{#"videos" : #[#"http://youtube.com/abc", #"http://vimeo.com/xyz"],
#"websites": #[#"http://apple.com", #"http://microsoft.com"]};
You can save that to a plist with:
NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *plistPath = [documentsPath stringByAppendingPathComponent:#"files.plist"];
[dictionary writeToFile:plistPath atomically:YES];
You can add that file to your bundle or whatever, and then read it at a future date with:
dictionary = [NSDictionary dictionaryWithContentsOfFile:plistPath];
You can, alternatively, write that to a JSON file with:
NSError *error = nil;
NSData *data = [NSJSONSerialization dataWithJSONObject:dictionary options:NSJSONWritingPrettyPrinted error:&error];
NSString *jsonPath = [documentsPath stringByAppendingPathComponent:#"files.json"];
[data writeToFile:jsonPath atomically:YES];
You can read that JSON file with:
data = [NSData dataWithContentsOfFile:jsonPath];
dictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
Either way, you can get the list of videos or web sites like so:
NSArray *videos = dictionary[#"videos"];
NSArray *websites = dictionary[#"websites"];
Now that you have your arrays of videos and websites, the question then is how you then use those URLs.
You could do something like:
for (NSString *urlString in videos) {
NSURL *url = [NSURL URLWithString: urlString];
// now do something with the URL
}
The big question is what is the "do something" logic. Because you're dealing with a lot of URLs, you would want to use a NSOperation based solution, not a GCD solution, because NSOperationQueue lets you control the degree of concurrency. I'd suggest a NSOperation-based networking library like AFNetworking. For example, to download the HTML for your websites:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 4;
for (NSString *urlString in websites)
{
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// convert the `NSData` responseObject to a string, if you want
NSString *string = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
// now do something with it, like saving it in a cache or persistent storage
// I'll just log it
NSLog(#"responseObject string = %#", string);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error = %#", error);
}];
[queue addOperation:operation];
}
Having said that, I'm not sure it makes sense to kick off a ton of network requests. Wouldn't you really prefer to wait until the user taps on one of those cells before retrieving it (and for example, then just open that URL in a UIWebView)? You don't want an app that unnecessarily chews up the user's data plan and battery retrieving stuff that they might not want to retrieve. (Apple has rejected apps that request too much data from a cellular connection.) Or, at the very least, if you want to retrieve stuff up front, only retrieve stuff as you need it (e.g. in cellForRowAtIndexPath), which will retrieve the visible rows, rather than the hundreds of rows that might be in your text/plist/json file.
Frankly, we need a clearer articulation of what you're trying to do, and we might be able to help you with more concise counsel.

iOS: Get file's metadata

I have an mp3 file on a server. I want to get this file's information like what's the size of this file, what's the artists name, what's the album name, when was the file created, when was it modified, etc. I want all this information.
Is it possible to get this information without actually downloading the whole file? Using NSURLConnection or otherwise?
EDIT:
The following code doesn't give me the required information, i.e. file created by, artist name, etc
NSError *rerror = nil;
NSURLResponse *response = nil;
NSURL *url = [NSURL URLWithString:#"http://link.to.mp3"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"HEAD"];
NSData *result = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&rerror];
NSString *resultString = [[[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding] autorelease];
NSLog(#"URL: %#", url);
NSLog(#"Request: %#", request);
NSLog(#"Result (NSData): %#", result);
NSLog(#"Result (NSString): %#", resultString);
NSLog(#"Response: %#", response);
NSLog(#"Error: %#", rerror);
if ([response isMemberOfClass:[NSHTTPURLResponse class]]) {
NSLog(#"AllHeaderFields: %#", [((NSHTTPURLResponse *)response) allHeaderFields]);
}
The "AllHeaderFields" is:
AllHeaderFields: {
"Cache-Control" = "max-age=0";
Connection = "keep-alive";
"Content-Encoding" = gzip;
"Content-Type" = "text/plain; charset=ascii";
Date = "Fri, 17 Feb 2012 12:44:59 GMT";
Etag = 19202n;
Pragma = public;
Server = dbws;
"x-robots-tag" = "noindex,nofollow";
}
It is quite possible to get the ID3 information embedded in an MP3 file (artist name, track title) without downloading the whole file or using low-level APIs. The functionality is part of the AVFoundation framework.
The class to look at is AVAsset and specifically it's network friendly subclass AVURLAsset. AVAsset has an NSArray property named commonMetadata. This commonMetadata property will contain instances of AVMetadataItem, assuming of course that the reference URL contains metadata. You will usually use the AVMetadataItem's commonKey property to reference the item. I find this method of iterating through an array checking commonKeys irritating so I create an NSDictionary using the commonKey property as the key and the value property as the object. Like so:
-(NSDictionary *)dictionaryOfMetadataFromAsset:(AVAsset *)asset{
NSMutableDictionary *metaData = [[NSMutableDictionary alloc] init];
for (AVMetadataItem *item in asset.commonMetadata) {
if (item.value && item.commonKey){
[metaData setObject:item.value forKey:item.commonKey];
}
}
return [metaData copy];
}
With the addition of this simple method the AVAsset's metadata becomes quite easy to use. Here is an example of getting an MP3's metadata through a URL:
NSURL *mp3URL = [NSURL URLWithString:#"http://'AddressOfMP3File'"];
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:mp3URL options:nil];
NSDictionary *metaDict = [self dictionaryOfMetadataFromAsset:asset];
NSLog(#"Available Metadata :%#",metaDict.allKeys);
NSLog(#"title:%#",[metaDict objectForKey:#"title"]);
I have found that this code seems to load just the first few seconds of your MP3 file. Also note that this code is synchronous; So use with caution. But AVURLAsset does have some async functionality described in the docs.
Once you have the AVAsset you can create a AVPlayerItem with it and feed that to an AVPlayer and play it, or not.
Yes and no. Things like the file size and modification date often come as part of the HEAD response. But not always: with a lot of dynamic URLs, you won't get all of the information.
As for the artist and album name, they're part of the MP3's ID3, which is contained inside the file, and so you won't be able to get them with a HEAD request. Since the ID3 tag is typically at the beginning of a file, you could try to grab just that part and then read the ID3 tag. But you won't be able to do it with NSURLConnection since it doesn't support just fetching part of a file, so you'll need to find a more low-level way of getting data by HTTP.
Yep, you're right on target with NSURLConnection.
I think you want to send a HEAD request for the resource you want information about and then check the information you receive in connection:didReceiveResponse: and connection:didReceiveData:
Edit
Admittedly I didn't read your question in its entirety. It won't be possible to get ID3 information, but you should be able to get size of file and maybe creation date etc.
This answer does give some good information about how to get the ID3 information. You'd need to set up a php page to examine the mp3 file server-side and return just that information you require instead of the entire mp3.

Resources