2

I have a button called "Sync". When tapped / pressed it sends data to a webserver and retrieves the answer by the server (what takes a while and freezes the gui / button) so how do I add a "Please wait" and activity indicator until the data / answer from the server arrives and the text / indicator is deleted and replaced by the answer from the server?

This code freezes my app:

- (IBAction) syncButtonTapped
{

[syncButton setEnabled:NO];
//resultText.text= @"Bitte warten ...";
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [paths objectAtIndex: 0];
NSString *docFile = [docDir stringByAppendingPathComponent: @"deck.txt"];
NSString *post = [NSString stringWithContentsOfFile:docFile encoding:NSUTF8StringEncoding error:nil];


NSString *post2 = [post stringByReplacingOccurrencesOfString:@"\n" withString:@","];
NSString *post3 = [post2 stringByReplacingOccurrencesOfString:@"\r" withString:@""];
NSString *post4 = [NSString stringWithFormat:@"{\"results\":[%@]}",post3];
NSString *urlString = [NSString stringWithFormat:@"http://storecheck.cortona.de/fetchresults.php?results=%@",[post4 stringByAddingPercentEscapesUsingEncoding: NSASCIIStringEncoding]];
NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease]; 
[request setURL:[NSURL URLWithString:urlString]]; 
[request setHTTPMethod:@"GET"];
 NSString *data4 = @"";
[data4 writeToFile: docFile atomically: NO encoding: NSUTF8StringEncoding error:nil];
//send request & get response

NSData *returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
NSString *returnString = [[NSString alloc] initWithData:returnData encoding:NSUTF8StringEncoding];
}

i am working on a Mac Mini with iOS 10.6.7

Filip Radelic
  • 26,607
  • 8
  • 71
  • 97
Martin Huwa
  • 53
  • 1
  • 2
  • 6
  • I also like putting a black background, 0.5 alpha UIView in front of everything to darken them and disable any user interaction with the UI elements. –  Sep 05 '11 at 08:55
  • the inputs are disabled so ti dont want this and dont need this want you mean – Martin Huwa Sep 05 '11 at 08:58
  • http://stackoverflow.com/questions/593234/how-to-use-activity-indicator-view-on-iphone – PJR Sep 05 '11 at 09:05
  • It seems the main problem is that the whole program freezes until it has the answer from the server. So button pressed - app freezes - answer arrived. – Martin Huwa Sep 05 '11 at 09:10

5 Answers5

5

Adding an activity indicator in UIAleartView will help. Checkout this...

UIAlertView *waitAlert = [[[UIAlertView alloc] initWithTitle:@"Please Wait...." message:nil delegate:self cancelButtonTitle:nil otherButtonTitles: nil] autorelease];

[waitAlert show];

UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];

// Adjust the indicator so it is up a few pixels from the bottom of the alert
indicator.center = CGPointMake(waitAlert.bounds.size.width / 2, waitAlert.bounds.size.height - 50);
[indicator startAnimating];
[waitAlert addSubview:indicator];
[indicator release];

EDIT: Probably the reason why your application is getting blocked is this method

sendSynchronousRequest:(NSURLRequest *)request

Following is the description of the method.

A synchronous load is built on top of the asynchronous loading code made available by the class. The calling thread is blocked while the asynchronous loading system performs the URL load on a thread spawned specifically for this load request. No special threading or run loop configuration is necessary in the calling thread in order to perform a synchronous load.

If you don't want you application to block then call this from other thread. Read following too.

Important: Because this call can potentially take several minutes to fail (particularly when using a cellular network in iOS), you should never call this function from the main thread of a GUI application.

Please refer to Class reference of NSURLConnection. Both of above blocks are taken from that.

http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSURLConnection_Class/Reference/Reference.html

Mohammad
  • 1,636
  • 1
  • 13
  • 17
  • use of undeclared identifier 'waitAlert'? – Martin Huwa Sep 05 '11 at 08:57
  • Declare UIAleartView *waitAlert in your code you just don't have the variables that i am using. so declare them in code. – Mohammad Sep 05 '11 at 08:58
  • that worked, tahnks, but the problem with the freeze is not solved =( – Martin Huwa Sep 05 '11 at 09:27
  • Are you trying to show this alert from the thread which is uploading data? Then it will take time. Display alert from main thread OR from separate method and call that method form performSelector. – Mohammad Sep 05 '11 at 09:31
  • tried i9t but dont know how to make it work right. now it shows the spinner after the freeze like before – Martin Huwa Sep 05 '11 at 09:43
  • Can you please post code that shows your mechanism to call web server? At least class name and method name that is being used for fetching data. Seems that if i know the mechanism than I may find the reason for app freeze. – Mohammad Sep 05 '11 at 10:10
  • I changed my answer... Basic suggestion is to call sendSynchronousRequest from other thread. – Mohammad Sep 06 '11 at 08:28
4

Your synchronous request blocks the UI thread and that's why activity indicator will not start. You should use NSURLConnection asynchronously for multiple reasons, and that's one of them.

Instead of:

NSData *returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
NSString *returnString = [[NSString alloc] initWithData:returnData encoding:NSUTF8StringEncoding];

Do:

NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[conn start];
[conn release];

Then make returnData instance variable and implement NSURLConnectionDeletgate methods:

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    returnData = [[NSMutableData alloc] init];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    [returnData appendData:data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    NSString *returnString = [[NSString alloc] initWithData:returnData encoding:NSUTF8StringEncoding];
    // do whatever you need with your string and stop activity indicator
    [returnData release];
}
Filip Radelic
  • 26,607
  • 8
  • 71
  • 97
  • exptected ; token before : token on: - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response – Martin Huwa Sep 06 '11 at 09:39
  • I hope you are not pasting these methods inside other method? Show me your code now. And please edit your question and put it there, it's not gonna be pretty inside a comment. – Filip Radelic Sep 06 '11 at 12:39
  • i posted it outside of every method – Martin Huwa Sep 06 '11 at 13:04
  • Well you pasted something wrong. If you want help, edit your question and insert the code you have so far or I can't help you. – Filip Radelic Sep 06 '11 at 13:05
  • i pasted your code blocks and it didnt work so how can i paste something wrong? i get now undeclared identifier returnData do i have to add some more code that this error is solved? Then make returnData instance variable => do i have to write some code by my own? – Martin Huwa Sep 06 '11 at 13:24
  • Read my answer before saying it doesn't work. "Then make returnData instance variable and..." – Filip Radelic Sep 06 '11 at 13:25
  • and how do o do that? thought you did that? – Martin Huwa Sep 06 '11 at 13:57
  • If you don't know how to make instance variable, you should consider reading some basic tutorials like [this one](http://www.cocoadevcentral.com/d/learn_objectivec/). Add `NSMutableData *returnData;` to your header file between `{` and `}`. – Filip Radelic Sep 06 '11 at 14:06
  • thanks forgot this =) this was my very first app in under a week =) – Martin Huwa Sep 06 '11 at 14:48
4

For synchroneous use: (bad form, but working solution)

//Right before the request is made
UIActivityIndicatorView* indicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[indicatorView setFrame:CGRectMake(0, 0, 16, 16)];
[indicatorView setHidesWhenStopped:YES];
[indicatorView startAnimating];
[self.view addSubview:indicatorView];
// Create your request (synchronously or asynchronously) 
[self performSelector:@selector(myDoRequest:) withObject:self afterDelay:0.1];
//this gives time for the runloop to complete at least one screen draw..
...

-(void)myDoRequest:(id)sender {
   // Create your request (synchronously or asynchronously) 
   ...
   // When request is done
   [indicatorView stopAnimating];
   [indicatorView release];
}
RabinDev
  • 658
  • 3
  • 13
1
//Right before the request is made
UIActivityIndicatorView* indicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[indicatorView setFrame:CGRectMake(0, 0, 16, 16)];
[indicatorView setHidesWhenStopped:YES];
[indicatorView startAnimating];
[self.view addSubview:indicatorView];
// Create your request (synchronously or asynchronously) 
...

// When request is done
[indicatorView stopAnimating];
[indicatorView release];
apouche
  • 9,703
  • 6
  • 40
  • 45
  • it doesnt appear do i have to create the view somewhere or add it in the header ...? its just the same as before when i add your code? – Martin Huwa Sep 05 '11 at 08:56
  • @Martin you'll have to add it to your current view, with `addSubView:` –  Sep 05 '11 at 09:01
  • I've added the `setFrame` and `addSubview` so that you have the full picture – apouche Sep 05 '11 at 09:03
  • It seems the main problem is that the whole program freezes until it has the answer from the server. So button pressed - app freezes - answer arrived. – Martin Huwa Sep 05 '11 at 09:10
  • i am guessing you are calling the server through a synchronous call, then you must add the `UIActivityIndicatorView` to your controllerView **before** the last cycle of UI refresh. (for example in `viewDidLoad` or through your `NIB file`). and then simply call the `[indicatorView startAnimating];` when the button is pressed before the request is made – apouche Sep 05 '11 at 09:14
1

Create:

spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
[spinner setCenter:CGPointMake(kScreenWidth/2.0, kScreenHeight/2.0)]; // I do this because I'm in landscape mode
[self.view addSubview:spinner]; // spinner is not visible until started

Start:

[spinner startAnimating]; 

Stop:

 [spinner stopAnimating];

When you're finally done, remove the spinner from the view and release.

PJR
  • 13,052
  • 13
  • 64
  • 104