Why loading data Indicator is not working properly? - ios

In my code I am using an UIActivityIndicatorView on an UIAlertView. It is working fine but my problem is it is not showing up on correct time. I mean to say when the device get data from web service after that this loading indicator is appearing in the end and its not rite thing I think because I want it to be appear when the web service is sending or receiving data.
I need help as I am new to iOS app development. If there is any other easy way to do this thing then suggest me.
I hope my question is clear, my problem is according to this code the loading indicator is appearing after i get reply from web service but i want to run this indicator as the user will press update button and web service should be called after that. Tell me where i am wrong.
Here is the code I am using
-(IBAction)update:(id)sender
{
av=[[UIAlertView alloc] initWithTitle:#"Updating Image..." message:#"" delegate:self cancelButtonTitle:nil otherButtonTitles:nil];
ActInd=[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
[ActInd startAnimating];
[ActInd setFrame:CGRectMake(125, 60, 37, 37)];
[av addSubview:ActInd];
[av show];
{
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
int gid=[defaults integerForKey:#"gid"];
NSString *gameid=[NSString stringWithFormat:#"%i", gid];
NSLog(#"%#",gameid);
img=mainImage.image;
NSData *imgdata=UIImagePNGRepresentation(img);
NSString *imgstring=[imgdata base64EncodedString];
NSLog(#"%#",imgstring);
NSString *escapedString = (NSString *)CFURLCreateStringByAddingPercentEscapes(
NULL,
(CFStringRef)imgstring,
NULL,
CFSTR("!*'();:#&=+$,/?%#[]"),
kCFStringEncodingUTF8);
NSLog(#"escapedString: %#",escapedString);
#try
{
NSString *post =[[NSString alloc] initWithFormat:#"gid=%#&image=%#",gameid,escapedString];
NSLog(#"%#",post);
NSURL *url=[NSURL URLWithString:#"http://mywebspace/updategameimage.php"];
NSData *postData = [post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString *postLength = [NSString stringWithFormat:#"%d", [postData length]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:url];
[request setHTTPMethod:#"POST"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:postData];
NSError *error = [[NSError alloc] init];
NSHTTPURLResponse *response = nil;
NSData *urlData=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSLog(#"Response code: %d", [response statusCode]);
if ([response statusCode] >=200 && [response statusCode] <300) {
NSString *responseData = [[NSString alloc]initWithData:urlData encoding:NSUTF8StringEncoding];
NSLog(#"Response ==> %#", responseData);
SBJsonParser *jsonParser = [SBJsonParser new];
NSDictionary *jsonData = (NSDictionary *) [jsonParser objectWithString:responseData error:nil];
NSLog(#"%#",jsonData);
NSInteger type = [(NSNumber *)[jsonData objectForKey:#"type"] integerValue];
NSLog(#"%d",type);
if (type==1) {
[self alertStatus:#"You can Keep on Drawing" :#"Sketch Updated"];
}
}
}
#catch (NSException * e) {
NSLog(#"Exception: %#", e);
[self alertStatus:#"Unable to connect with game." :#"Connection Failed!"];
}
}
[av dismissWithClickedButtonIndex:0 animated:YES];
[av release]; av=nil;
}

UI updates are done on main thread. You have started activity indicator on main thread.It's fine.
Now, you are making synchronous network call on main thread. It should be asynchronous. Here until you will receive the response from network call, your main thread will remain busy and your UI will not be updated.
To update the UI, you can either make the network call asynchronous or you can start the activity indicator in a separate function and then delay the call of network activity by performselector:afterdelay method.

You can use GCD, Raywenderlich Tutorial
-(IBAction)update:(id)sender
{
/*
Setup indicator and show it
*/
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
/*
Do network call
*/
dispatch_async(dispatch_get_main_queue(), ^{
/*
Update UI
*/
});
});
}

Related

POST request iOS: what's happening under the hood?

I'm facing a really strange issue and I hope somebody can help me understand where is the problem on which I'm stuck since three days ago.
In simple I make a NSMutableRequest to a simple endpoint where I send (using POST) a json and I'm supposed to receive a response 0 or 1.
The code works the first time the app is run on the device, but somehow it does not work anymore the following times.
To explain myself better, if I uninstall and reinstall the app every time, I get the correct response, but if I run the code twice the second time I get something like [CSRF verification failed] from the endpoint. This error means that I'm not sending the correct format ( or I'm sending something strange).
My question is: how is that possible? Is it possible that I'm sending something else?
The endpoint works correctly because with the android version I don't have any problems...
The code is the following, hope somebody can explain me what is happening under the hood and how I can manage to solve this problem.
NSString *mail = [profile valueForKey:#"email"];
NSString *provider = [profile valueForKey:#"provider"];
// making a GET request to endpoint
NSString *baseUrl = ENDPOINT_URL;
NSString *targetUrl = [NSString stringWithFormat:#"%#", baseUrl];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setHTTPMethod:#"POST"];
[request addValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request addValue:#"application/json" forHTTPHeaderField:#"Accept"];
NSString *body = [NSString stringWithFormat:#"{\"mail\":\"%#\", \"provider\":\"%#\"}",mail,provider];
NSData *postData=[body dataUsingEncoding:NSUTF8StringEncoding];
[request setHTTPBody:postData];
[request setURL:[NSURL URLWithString:targetUrl]];
[request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:
^(NSData * _Nullable data,
NSURLResponse * _Nullable response,
NSError * _Nullable error) {
if (data){
NSString *myString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"Data received: %#", myString);
if ([myString isEqualToString:#"[\"CSRF validation failed\"]"]){
NSLog(#"ENDPOINT ERROR");
dispatch_async(dispatch_get_main_queue(), ^{
[(AppDelegate *)[[UIApplication sharedApplication] delegate] loginAborted];});
} else {
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
NSLog(#"Data received: %#", json);
NSNumber *value = [NSNumber numberWithInt:[[json objectForKey:#"profile_exists"] intValue]];
if ([value intValue] == 1){
//Profile exists.
NSLog(#"Profile exists.");
[self silentLogin:profile];
} else if ([value intValue] == 0) {
//Profile does not exists.
NSLog(#"Profile does not exist.");
[self silentRegistration:profile];
}
else {
//Error.
NSLog(#"Error in ENDPOINT VALUE");
dispatch_async(dispatch_get_main_queue(), ^{
[(AppDelegate *)[[UIApplication sharedApplication] delegate] loginAborted];});
}
NSLog(#"%#",json);
}
} else {
NSLog(#"No Data received");
dispatch_async(dispatch_get_main_queue(), ^{
[(AppDelegate *)[[UIApplication sharedApplication] delegate] loginAborted];});
}
}] resume];
I was able to solve the problem deleting the cookies every time I make a request. It seems that these cookies are re sent every time.
+ (void)clearCookiesForURL: (NSString *)url {
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
NSArray *cookies = [cookieStorage cookiesForURL:[NSURL URLWithString:url]];
for (NSHTTPCookie *cookie in cookies) {
NSLog(#"Deleting cookie for domain: %#", [cookie domain]);
[cookieStorage deleteCookie:cookie];
}
}

NSURLConnection method needs to be changed to pass authentication challenge

i have a method for http connection, which was working fine for me until the server i am trying to have an invalid ssl certificate.
Since i am using
[NSURLConnection sendSynchronousRequest:returningResponse:error]
There is no chance to pass authentication challenge by using NSURLConnection delegate methods.
Now, i need to change my service call code as fast as possible.
My method returns the data received from the connection, that is the major problem i can not easily change mine to
NSURLConnection to initWithRequest:delegate:
My service call method is as follows;
-(id)serviceCall:(NSString*)str withURL:(NSString*)serviceUrl withIdentifier:(NSString*)type
{
globalURL = [[NSURL alloc] initWithString:serviceUrl];
shouldAllowSelfSignedCert = YES;
// if(ISDEBUG) NSLog(#"%#",serviceUrl);
NSMutableDictionary* json;
NSURLResponse* response;
NSData* responseData = [NSMutableData data];
NSError* error = nil;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:globalURL];
[request setHTTPMethod:#"POST"];
[request setHTTPBody: [str dataUsingEncoding: NSUTF8StringEncoding]];
NSString* msgLength = [[NSString alloc] initWithFormat:#"%lu", (unsigned long)[str length]];
[request addValue:#"text/json; charset=utf-8" forHTTPHeaderField:#"Content-Type"];
[request addValue:msgLength forHTTPHeaderField:#"Content-Length"];
request.timeoutInterval = 180;
responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if([type isEqualToString:#"image"])
{
if(ISDEBUG) NSLog(#"%#",responseData);
return responseData;
}
else
{
if(error)
{
UIAlertView *message = [[UIAlertView alloc] initWithTitle:NO_WS_CONNECTION message:#"" delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil];
if(ISDEBUG) NSLog(#"%#",error);
}
else
{
if(responseData !=nil)
{
json = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
}
else
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error" message:NO_WS_CONNECTION delegate:self cancelButtonTitle:#"Ok" otherButtonTitles:nil, nil];
[alert show];
}
}
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
if(ISDEBUG) NSLog(#"%#",responseString);
}
return json;
}
I hope i am clear enough.
What is your advise?
Thanks.
You should have a good reason to do it synchronously, so, I will try to help you without changing the flow.
Try wrapping the request into a class where you can implement the request using initWithRequest:delegate: and make the class return the response using block.
You will have something like:
[YourRequestClass requestWithURL:serviceURL callback:^(NSData *yourData, NSError *error){
}];
Ok, at this point you have a new tool that makes ASYNCHRONOUS requests, make the authentication challenge stuff for you and returns the result on a block.
Now, you can simply use dispatch_semaphore to block your thread until the request returns a response ....
-(id)serviceCall:(NSString*)str withURL:(NSString*)serviceUrl withIdentifier:(NSString*)type {
__block NSData *myData = nil;
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
[YourRequestClass requestWithURL:serviceUrl callback:^(NSData *yourData, NSError *error){
//ignoring error (this is an example !)
myData = yourData;
dispatch_semaphore_signal(sem);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
//Use myData and make yourObject here!
return yourObject;
}
Note that It's just an example, and I'm just trying to pointing you the right way ... I didn't test this code, but I believe it should work as expected!

iOS async task freezes

I have an app which does an async post to a server. Then it decodes the json and returns the message from the server. I put a few debugging log entries in my code, so I know that the response from the server, as well as the decoding of the json are instantaneous. The problem is that after the json is decoded, the async task runs for about 6 seconds before it calls the next event (Showing the popup dialog).
- (IBAction)register:(id)sender {
[self startPost]; // Starts spinner animation
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self doPost]; // performs post
});
}
-(void)doPost
{
#try {
NSString *post =[[NSString alloc] initWithFormat:#"request=register&platform=ios&email=%#&password=%#",self.email.text,self.password.text];
//NSLog(#"PostData: %#",post);
NSURL *url=[NSURL URLWithString:#"https://site.com/api.php"];
NSData *postData = [post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString *postLength = [NSString stringWithFormat:#"%d", [postData length]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:url];
[request setHTTPMethod:#"POST"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:postData];
//[NSURLRequest setAllowsAnyHTTPSCertificate:YES forHost:[url host]];
NSError *error = [[NSError alloc] init];
NSHTTPURLResponse *response = nil;
NSData *urlData=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
//NSLog(#"Response code: %d", [response statusCode]);
if ([response statusCode] >=200 && [response statusCode] <300)
{
NSString *responseData = [[NSString alloc]initWithData:urlData encoding:NSUTF8StringEncoding];
//NSLog(#"Response ==> %#", responseData);
NSData *responseDataNew = [responseData dataUsingEncoding:NSUTF8StringEncoding];
NSError* error = nil;
NSDictionary *myDictionary = [NSJSONSerialization JSONObjectWithData:responseDataNew options:NSJSONReadingMutableContainers error:&error];
if ( error ){
[self alertStatus:#"Unknown response code from server" :#"Whoops!"];
NSLog(#"Response ==> %#", responseData);
[self postDone];
}else{
if ([myDictionary[#"error"] isEqualToNumber:(#1)])
{
NSLog(#"ERROR DETECTED");
[self alertStatus:myDictionary[#"message"]:#"Whoops!"];
[self postDone];
}
else
{
[self alertSuccess];
[self postDone];
}
}
} else {
if (error) NSLog(#"Error: %#", error);
[self alertStatus:#"Connection Failed" :#"Whoops!"];
[self postDone];
}
}
#catch (NSException * e) {
NSLog(#"Exception: %#", e);
[self alertStatus:#"Registration Failed." :#"Whoops!"];
[self postDone];
}
}
-(void)startPost
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
self.email.enabled = false;
self.password.enabled = false;
self.confirm.enabled = false;
self.cancelButton.enabled = false;
}
- (void) alertStatus:(NSString *)msg :(NSString *)title
{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title
message:msg
delegate:self
cancelButtonTitle:#"Ok"
otherButtonTitles:nil, nil];
[alertView setTag:0];
[alertView show];
}
- (void) alertSuccess
{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Success!"
message:#"You have been successfully registered."
delegate:self
cancelButtonTitle:#"Ok"
otherButtonTitles:nil, nil];
[alertView setTag:1];
[alertView show];
}
-(void)postDone
{
self.registerButton.hidden = false;
self.spinner.hidden = true;
self.loadingText.hidden = true;
//[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
self.email.enabled = true;
self.password.enabled = true;
self.confirm.enabled = true;
self.cancelButton.enabled = true;
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{if (alertView.tag == 1)
{
[self dismissViewControllerAnimated:YES completion:nil];
}}
The alertStatus and alertSuccess functions just pop up a message box briefly.
When I run the code, I purposefully enter bad information so the log says "ERROR DETECTED". The problem is that it takes another 6 seconds before anything happens after that.
After you have called:
NSData *urlData=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
and obtained the data, you should switch back to the main thread to use it. This is because all UI updates must be done on the main thread.
So, all that code after you get the data should be moved to a new method and called as:
dispatch_async(dispatch_get_main_queue(), ^{
[self handleData:urlData withResponse:response error:error];
}
And you should also put the exception catch code inside dispatch_async(dispatch_get_main_queue(), ^{ because you try to update the UI there too...

How to enumerate through NSArray of NSDictionaries with a block call inside the loop?

I get an self.usersArray with 2 elements in the format:
(
{
userCreated = "2012-01-05 12:27:22";
username = Simulator;
},
{
userCreated = "2013-01-01 14:27:22";
username = "joey ";
}
)
This is gotten in a completion block after which I call another method to fetch points for these 2 users through a helper class:
-(void)getPoints{
self.usersPointsArray = [[NSMutableArray alloc] init];
for (NSDictionary *usersDictionary in self.usersArray) {
[SantiappsHelper fetchPointsForUser:[usersDictionary objectForKey:#"username"] WithCompletionHandler:^(NSArray *points){
if ([points count] > 0) {
[self.usersPointsArray addObject:[points objectAtIndex:0]];
}
NSLog(#"self.usersPointsArray %#", self.usersPointsArray);
}];
}
}
The final self.usersPointsArray log looks like:
(
{
PUNTOS = 5;
username = Simulator;
},
{
PUNTOS = 2;
username = joey;
}
)
But the problem is that the way the call for points is structured, the self.usersPointsArray is returned twice, each time with an additional object, due to the for loop, I know.
Here is the Helper class method:
+(void)fetchPointsForUser:(NSString*)usuario WithCompletionHandler:(Handler2)handler{
NSURL *url = [NSURL URLWithString:#"http://myserver.com/myapp/readpoints.php"];
NSDictionary *postDict = [NSDictionary dictionaryWithObjectsAndKeys:usuario, #"userNa", nil];
NSData *postData = [self encodeDictionary:postDict];
// Create the request
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"POST"];
[request setValue:[NSString stringWithFormat:#"%d", postData.length] forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/x-www-form-urlencoded charset=utf-8" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:postData];
__block NSArray *pointsArray = [[NSArray alloc] init];
dispatch_async(dispatch_get_main_queue(), ^{
// Peform the request
NSURLResponse *response;
NSError *error = nil;
NSData *receivedData = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
if (error) {
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
NSLog(#"HTTP Error: %d %#", httpResponse.statusCode, error);
return;
}
return;
}
NSString *responseString = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
pointsArray = [NSJSONSerialization JSONObjectWithData:[responseString dataUsingEncoding:NSASCIIStringEncoding] options:0 error:nil];
if (handler)
handler(pointsArray);
});
}
I cannot use the self.usersPointsArray with the initial objects, only with the finalized object. It wont always be 2 elements, i actually dont know how many it will be.
What would be the way to structure it so I get a final call when the self.usersPointsArray is complete and then I reload my tableview?
I think of your problem as a standard consumer-producer problem. You can create a queue count for the amount of items that will be processed (int totalToProcess=self.usersArray.count). Each time the completion handler is hit, it will do totalToProcess--. When totalToProcess reaches 0 you have processed all of the elements in your queue and can refresh your table.
If I understand your question correctly I believe this solves your problem. If not, hopefully I can with a bit more information.

Using user authentication/sessions with Django and ASIHttpRequest

I am trying to make a basic authentication system in iOS that sends a POST to Django and on the Django side authenticates the user and starts a session. Right now I am able to send the user information by passing the values as data in the URL and authenticating it, but how do I retrieve the session data or cookie from the Django response? When I try to store or print out the cookie, it tells me the array is empty. I have tried both request.requestCookies and request.responseCookies.
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:#"test_user", #"username", #"pass", #"password", nil];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http://127.0.0.1:8000/login/"]];
NSError *error;
NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:0 error:&error];
if ( error ) {
NSLog( #"ERROR - %#", error.localizedDescription );
} else {
__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request addRequestHeader: #"Content-Type" value:#"application/json; charset=utf-8"];
[request appendPostData:data];
[request setRequestMethod:#"POST"];
[request setCompletionBlock:^{
UIAlertView *alerView = [[UIAlertView alloc] initWithTitle:#"Login"
message:#"Login was sent"
delegate:nil
cancelButtonTitle:nil
otherButtonTitles:#"Ok", nil];
[alerView performSelectorOnMainThread:#selector(show) withObject:nil waitUntilDone:NO];
NSLog(#"RESPONSE: %#", [[NSString alloc] initWithData:request.responseData encoding:NSUTF8StringEncoding]);//, [request.requestCookies objectAtIndex:0]);
NSLog(#"COOKIE: %#", [request.requestCookies objectAtIndex:0]);
[ASIHTTPRequest addSessionCookie:[request.requestCookies objectAtIndex:0]];
}];
Okay, so I resolved this issue and it turns out that on the server side I was putting in the data but not officially logging in with Django (was not returning the proper cookies) which meant my app was not receiving the proper header.

Resources