I am currently using this in my Xcode 5 application and I can't seem to get it working
NSString *bundleVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleShortVersionString"];
NSString *latestVersionString = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"http://mywebsite.com/version.txt"] encoding:NSUTF8StringEncoding error:nil];
#import NSString+compareToVersion.h
if ([bundleVersion isOlderThanVersion:latestVersionString]) {
// show a blocking modal view, with a button to link to the app store}
First, I do not recommend using synchronous network calls, regardless of how small the .txt file may be. You can try this for your code: Make sure to include <UIAlertViewDelegate> in your .h file first.
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[[UIApplication sharedApplication]beginIgnoringInteractionEvents];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:
[NSURL URLWithString:#"http://mywebsite.com/version.txt"]];
request.timeoutInterval = 10.0;
request.cachePolicy = NSURLRequestReloadIgnoringLocalAndRemoteCacheData;
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:
^(NSURLResponse *response, NSData *data, NSError *connectionError)
{
if (data)
{
NSString *bundleVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleShortVersionString"];
NSString *latestVersionString = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
if (![bundleVersion isEqualToString:latestVersionString])
{
// The two strings are not equal, dispatch to the UI thread and show a UIAlertView
dispatch_async(dispatch_get_main_queue(), ^
{
UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:#"Update App!"
message:#"Please download the newer version available first on the App Store!"
delegate:self cancelButtonTitle:#"App Store"
otherButtonTitles:nil];
[alertView show];
[[UIApplication sharedApplication]endIgnoringInteractionEvents];
});
}
else
{
// Up to date; no new version is available, proceed to game play routines
[[UIApplication sharedApplication]endIgnoringInteractionEvents];
}
}
else
{
// No internet, or there is no data at the url, show error
// Uncomment below line if you want the user to interact with app again
// [[UIApplication sharedApplication]endIgnoringInteractionEvents];
}
}];
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
[[UIApplication sharedApplication]openURL:[NSURL URLWithString:#"Your App Store app link here"]];
}
First, check the value of latestVersionString in the debugger. Make sure it is the value you want it to be (and not e.g. nil). Also, declare an NSError * to capture any error that arises from trying to initialise a string from an HTTP URL (consider the case where there may be no internet connection, or temporary routing or DNS issue etc.).
NSError *error = nil;
NSString *latestVersionString = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"http://mywebsite.com/version.txt"] encoding:NSUTF8StringEncoding error:&error];
if (error != nil)
{
// figure out what went wrong based on the error
}
else
{
// check latestVersionString contains something valid and then compare
// with bundleVersion.
}
If both bundleVersion and latestVersionString contain what you expect, then the issue may be in the isOlderThanVersion: method.
Related
I'm working on my first app that integrates with a web service. Right now I have two views with their respective view controllers. I have a login view, which is where users will login to the app and I will verify and store there login credentials and I have a main view which shows all the users info from the web service. My two views work correctly individually however after verifying the credentials of the user on my login view I want to switch views to my login view. To do this I'm using UIViewController. Here is my code for when the login button is pressed:
-(IBAction)logIn:(id)sender{
//Show network activity is happening
UIApplication *application = [UIApplication sharedApplication];
application.networkActivityIndicatorVisible = YES;
//Validate credentials
[_loginNetworkingContorller checkCredentialsWithUsername:self.username.text withPassword:self.password.text completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if(!error){
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
if (httpResp.statusCode == 200) {
//if we get back a successful status code save username and password in keychain.
NSLog(#"SUCESS");
NSDictionary *credentials = #{self.username.text: self.password.text};
[KeychainUserPass save:#"INSERT APP NAME HERE" data:credentials];
NSLog(#"go to new page");
//print response from webservice for debugging purposes
NSMutableDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"%#", jsonObject);
//switch to new view controller
UIViewController *mainController = [[RDMainViewController alloc] initWithNibName:#"RDMainViewController" bundle:nil];
[self.navigationController pushViewController:mainController animated:NO];
}
else{
//Error case, handle it.
NSLog(#"ERROR");
}
}
else{
//Error case, handle it.
NSLog(#"ERROR");
}
}];
}
And here checkCredentialsWithUsername method:
-(void)checkCredentialsWithUsername:(NSString *)username withPassword:(NSString *)password completionHandler:(void (^)(NSData *data,NSURLResponse *response, NSError *error))myCompletion
{
//Create request URL
NSString *requestString = #"WEB_SERVICE_URL";
NSURL *url = [NSURL URLWithString:requestString];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
//Store password and user name for authentication
NSData *userPasswordData = [[NSString stringWithFormat:#"%#:%#", username, password] dataUsingEncoding:NSUTF8StringEncoding];
NSString *base64EncodedCredential = [userPasswordData base64EncodedStringWithOptions:0];
NSString *authString = [NSString stringWithFormat:#"Basic %#", base64EncodedCredential];
//Create an NSURL session
NSURLSessionConfiguration *sessionConfig=[NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.HTTPAdditionalHeaders=#{#"Authorization":authString};
self.session=[NSURLSession sessionWithConfiguration:sessionConfig];
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:req completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
myCompletion(data, response, error);
}];
[dataTask resume];
}
My issue is that it's taking an incredibly long time to switch views. Like sometimes over a minute. At first I thought it was the network connection but then I printed the data I was receiving from the web service and it was appearing very quickly. Even after I printed the data it was still taking a very long time for views to switch. I'm not really sure why but I think it has to do with the way I'm doing my blocks. Any idea why it's taking me such a long time to switch views?
Any help would be greatly appreciated.
#Danyun was right. I needed to do the following:
if (httpResp.statusCode == 200) {
//if we get back a successful status code save username and password in keychain.
NSLog(#"SUCESS");
NSDictionary *credentials = #{self.username.text: self.password.text};
[KeychainUserPass save:#"INSERT APP NAME HERE" data:credentials];
NSLog(#"go to new page");
//print response from webservice for debugging purposes
NSMutableDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"%#", jsonObject);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
//test to make sure we are on the main Queue for UI update
//switch to the new view controller
UIViewController *mainController = [[RDMainViewController alloc] initWithNibName:#"RDMainViewController" bundle:nil];
[self.navigationController pushViewController:mainController animated:NO];
}];
}
In my application and at startup I check if databases has been changed on server with the copy stored locally. I'm using a synchronous request to the server and I'm doing a check based on the last modified date time fields in the HTTP response
If the last modified data time of the file on the server is > last modified date time of the local file I ask user if he would like to update the database, if he accepts I download the database.
I'm using my machine as server but the problem when I shutdown my machine, the application crash at startup
Thanks for you help
You will find below my code
#import "FirstViewController.h"
#interface FirstViewController ()
#end
#implementation FirstViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// check connectivity
if ([[Reachability reachabilityForInternetConnection] currentReachabilityStatus] == NotReachable) {
[self displayConenctivityAlert];
}else{
[self checkDatabases];
}
}
- (void) checkDatabases {
bool res = [self fileUpdated];
if (res){
// Ask user if he would like to update the databases
}
}
-(void) displayConenctivityAlert{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:[TSLanguageManager localizedString:#"NO_CONNECTED"] delegate:self cancelButtonTitle:[TSLanguageManager localizedString:#"OK"] otherButtonTitles:nil];
[alert show];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
NSLog(#"Error HTTP ...");
}
- (BOOL)fileUpdated {
NSString *urlString = #"http://192.168.0.10:8888/fuel/stations.db";
NSLog(#"Downloading HTTP header from: %#", urlString);
NSURL *url = [NSURL URLWithString:urlString];
//store locally data into the resource folder.
NSString *documentsDirectory = [Utility documentsPath];
NSString *cachedPath = [documentsDirectory stringByAppendingPathComponent:#"stations.db"];
NSLog(#"Local URL / %#", cachedPath);
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL downloadFromServer = NO;
NSString *lastModifiedString = nil;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"HEAD"];
NSHTTPURLResponse *response;
[NSURLConnection sendSynchronousRequest:request returningResponse:&response error: NULL];
if ([response respondsToSelector:#selector(allHeaderFields)]) {
lastModifiedString = [[response allHeaderFields] objectForKey:#"Last-Modified"];
}
NSDate *lastModifiedServer = nil;
#try {
NSDateFormatter *df = [[NSDateFormatter alloc] init];
df.dateFormat = #"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'";
df.locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US"];
df.timeZone = [NSTimeZone timeZoneWithAbbreviation:#"GMT"];
lastModifiedServer = [df dateFromString:lastModifiedString];
}
#catch (NSException * e) {
NSLog(#"Error parsing last modified date: %# - %#", lastModifiedString, [e description]);
}
NSLog(#"lastModifiedServer: %#", lastModifiedServer);
NSDate *lastModifiedLocal = nil;
if ([fileManager fileExistsAtPath:cachedPath]) {
NSError *error = nil;
NSDictionary *fileAttributes = [fileManager attributesOfItemAtPath:cachedPath error:&error];
if (error) {
NSLog(#"Error reading file attributes for: %# - %#", cachedPath, [error localizedDescription]);
}
lastModifiedLocal = [fileAttributes fileModificationDate];
NSLog(#"lastModifiedLocal : %#", lastModifiedLocal);
}
// Download file from server if we don't have a local file
if (!lastModifiedLocal) {
downloadFromServer = YES;
}
// Download file from server if the server modified timestamp is later than the local modified timestamp
if ([lastModifiedLocal laterDate:lastModifiedServer] == lastModifiedServer) {
downloadFromServer = YES;
}
return downloadFromServer;
}
#end
Your app is crashing because it is taking too long to complete didFinishLaunching the system watchdog kills your app. This is because you are making a synchronous http request in viewDidLoad of your root view controller, which must be completed before you can finish launching. You can resolve this issue multiple ways, either do your HTTP request asynchronously by calling sendAsynchronousRequest:queue:completionHandler on your NSURLConnection. The other option is to move this code out of your launching pipeline, perhaps by moving the code to viewDidAppear of your view controller. This has the side effect of performing the check on every return to the view, not just the initial load.
In short, doing a synchronous HTTP request is a big no no because your UI will hang until the request is complete. In this case it's especially bad because you're forcing your launch to be delayed until the request is complete, which is causing it to fail when the server is down.
Problem solved
I'm using now
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
}
instead of
[NSURLConnection sendSynchronousRequest:request returningResponse:&response error: NULL];
Here is my question. How do I display an error if my app is unable to load my remote JSON file? I turned my wifi off on my computer and ran the app in the Simulator. It NSLogs the message that should be displayed if there is connection. How can I fix this? Thanks.
Here is my code:
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *jsonStr = #"http://xxx.com/server.php?json=1";
NSURL *jsonURL = [NSURL URLWithString:jsonStr];
// NSData *jsonData = [NSData dataWithContentsOfURL:jsonURL];
// NSError *jsonError = nil;
NSURLRequest *jsonLoaded = [NSURLRequest requestWithURL:jsonURL];
if(!jsonLoaded) {
UIAlertView *alertView = [[UIAlertView alloc] init];
alertView.title = #"Error!";
alertView.message = #"A server with the specified hostname could not be found.\n\nPlease check your internet connection.";
[alertView addButtonWithTitle:#"Ok"];
[alertView show];
[alertView release];
NSLog(#"No connection, JSON not loaded...");
}
else {
NSLog(#"JSON loaded and ready to process...");
}
}
Your code just creates the request. It doesn't actually fetch the data. You need to use NSURLConnection to fetch the data.
There are multiple ways to fetch the data. This example is for iOS 5.0 or higher:
NSOperationQueue *q = [[[NSOperationQueue alloc] init] autorelease];
NSURLRequest *jsonRequest = [NSURLRequest requestWithURL:jsonURL];
[NSURLConnection sendAsynchronousRequest:jsonRequest
queue:q
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// data was received
if (data)
{
NSLog(#"JSON loaded and ready to process...");
// ... process the data
}
// No data received
else
{
NSLog(#"No connection, JSON not loaded...");
// ... display your alert view
}
}];
You have not actually requested anything yet. Read here on how to make requests.
Basically, what you want to do is to implement NSURLConnectionDelegate protocol and override connection:didFailWithError: function to listen for failure event.
I have searched for awhile and tried many things, but I cannot get this error to go away. I have a table that is refreshed by a pull down method. I also use Apple's Reachability to determine internet connectivity. When I run my application WITH internet, and then while the app is running, shut the internet off, my app crashes trying to display a UIAlertView. Although, when I start my app WITHOUT internet and then turn on internet while the app is running and turn it back off, the app does not crash and everything works as it should. Any help would be greatly appreciated.
The error occurs on the [message show] line.
Edited code:
- (void)loadCallList {
NSURL *theURL = [NSURL URLWithString:#"http://www.wccca.com/PITS/"];
NSURLRequest *theRequest = [NSURLRequest requestWithURL:theURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:10.0];
NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
NSLog(#"Reachable");
self.headerHeight = 45.0;
NSData *data = [[NSData alloc] initWithContentsOfURL:theURL];
xpathParser = [[TFHpple alloc] initWithHTMLData:data];
NSArray *elements = [xpathParser searchWithXPathQuery:#"//input[#id='hidXMLID']//#value"];
if (elements.count >= 1) {
TFHppleElement *element = [elements objectAtIndex:0];
TFHppleElement *child = [element.children objectAtIndex:0];
NSString *idValue = [child content];
NSString *idwithxml = [idValue stringByAppendingFormat:#".xml"];
NSString *url = #"http://www.wccca.com/PITS/xml/fire_data_";
NSString *finalurl = [url stringByAppendingString:idwithxml];
xmlParser = [[XMLParser alloc] loadXMLByURL:finalurl];
[callsTableView reloadData];
}
}
else {
NSLog(#"Not Reachable");
self.headerHeight = 0.0;
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"No Internet Connection"
message:#"Please check your internet connection and pull down to refresh."
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[message show];
[message release];
}
[pull finishedLoading];
}
I tested the following method using sendAsynchronousRequest:queue:completionHandler: as the method to download data from the URL. It seems to work fine, I can get the else clause to fire, either by messing up the URL or making the timeout interval .1 seconds.
NSURL *theURL = [NSURL URLWithString:#"http://www.wccca.com/PITS/"];
NSURLRequest *theRequest = [NSURLRequest requestWithURL:theURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:5];
[NSURLConnection sendAsynchronousRequest:theRequest queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (error == nil) {
//do whatever with the data. I converted it to a string for testing purposes.
NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"%#",text);
}else{
NSLog(#"%#",error.localizedDescription);
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"No Internet Connection" message:#"Please check your internet connection and pull down to refresh." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[message show];
}
}];
You have two problems:
You shouldn't be using Reachability to determine whether there is an internet connection. Reachability is only useful to determine when an offline connection comes back online. Really. Making the networking calls is sometimes enough to power up the radios and connect so if you try to pre-check whether there is an internet connection the radios will remain off.
The second problem is that no synchronous networking calls can be made on the main thread. This includes Reachability and [NSData dataWithContentsOfURL:xxx]. Make them on a secondary thread using Grand Central Dispatch, NSOperationQueues, NSURLConnections, or NSThreads. Otherwise your application can lock up if the network isn't in good shape and the system watchdog timer will kill your app.
I am building my App conected to a Rest API and until now I only made a GET request with the following code :
//Start login process
NSString *emailstring = email.text;
NSString *passstring = pass.text;
// Create the URL from a string.
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http://www.myserver.com/Rest/API/users?format=json&email=%#&password=%#",emailstring,passstring]];
NSLog(#"%#",url);
// Create a request object using the URL.
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// Prepare for the response back from the server
NSHTTPURLResponse *response = nil;
NSError *error = nil;
// Send a synchronous request to the server (i.e. sit and wait for the response)
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSLog(#"Reponse from web:%#", response);
// Check if an error occurred
if (error != nil) {
NSLog(#"%#", [error localizedDescription]);
// Do something to handle/advise user.
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"Login error"
message:#""
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[message show];
}
else {
// Convert the response data to a string.
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
// View the data returned - should be ready for parsing.
NSLog(#"%#", responseString);
// Add data to a Plist file for next time
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"login.plist"];
NSArray *values = [[NSArray alloc] initWithObjects:emailstring,passstring,#"IDtest",nil];
[values writeToFile:path atomically:YES];
[values release];
[self dismissModalViewControllerAnimated:YES];
}
This code work fine just for a GET request. I saw it is there a lot of framework (e.g RestKit, ....). But I am getting a bit lost with other request ! So what is the best solution to make POST DELETE PUT request for an IOS App ?
It's similar code, but using the class NSMutableRequest. You can set the httpbody and other parameters to communicate with the server.
check the documentation: https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSMutableURLRequest_Class/Reference/Reference.html
to post something, just put setHTTPMethod:#"POST"and assign the data to post using setHTTPBody: