views:

37

answers:

2

Hi,

I was stopped in my work by a small problem: my application contains some web queries, which are almost immediately performed if wifi connection is used, however they need a couple of seconds to finish in case of 3G or even EDGE connection. During this period, I would like to indicate to the user that the query is in progress, e.g. by displaying a message on a label.

The code looks something like this:

-(void)query{
    alertLabel.text = @"in progress…";

    // the query follows...
    NSString *queryURLstring = [NSString stringWithFormat:@"http://xyz.xyz"];
    NSURL *queryURL = [[NSURL alloc] initWithString:queryURLstring];
    NSString *receivedData = [[NSString alloc] initWithContentsOfURL:queryURL];
    //query done

    alertLabel.text = @"";
}

What happens in practice is that when the query is being executed, the user can not actually do anything. When the requested data is received, the "in progress…" text appears just for a moment and it disappears immediately. I can not understand that, as this is not how it should work. The text should be displayed on the label before the query is executed, and should be cleared only upon finishing. Have anyone faced such issue? Can you recommend a solution? Thanks for all kind of suggestions! Miv

A: 

You should load the data asynchronously.

  1. Show the alert label
  2. Start downloading asynchronously
  3. Upon finishing (successful or error) remove the label.

When something takes longer than expected and you are blocking the main thread be prepared to get the whole application killed automatically - you definitely do not want this to happen!

As for the updates on screen. Updates happen only at the end of the current run loop iteration, so this is totally expected behavior. (To be honest, I'm a bit surprised the test is visible at all).

Eiko
Instead of / in addition to an alert view, you might also use a UIProgressView: http://developer.apple.com/iphone/library/documentation/uikit/reference/UIProgressView_Class/Reference/Reference.html
Kalle
Well, he actually uses a non-obstrusive label. But progress view is a good suggestion, or an activity view.
Eiko
Eiko,thanks for your answer. However I solved in a bit different way than you suggested.My new problem: query method returns the result (as parameter) in an object. The app was constructed to work in a single thread way… If the query runs background, the main thread will miss the data, which will be available only when query finishes. How can I make the main thread wait? A repeating timer set to 0.1 sec to check if data is already returned…? Actually I have no other idea, but there should be a better way to solve it.
cactusdev
A: 

There are few solutions for your problem.
@Eiko has explained the problem and the reasons very well.

Next is one possible solution (with minimal amount of changes to your code):

Just execute your query method in the background thread like this:

[self performSelectorInBackground:@selector(query) withObject:nil];

And replace the alertLabel.text = @"in progress…"; by:

[alertLabel performSelectorOnMainThread:@selector(setText:) withObject:@"in progress…" waitUntilDone:NO];

[the same about the alertLabel.text = @"";]


It would be better if you would:

  • Add 2 methods: - (void)showLoadingWithText:(id)loadingText; and - (void)hideLoading;.
  • Call these methods in the main thread (instead of performing selectors directly on the label).
  • Add some additional activity indicators like [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; (will display a loading indicator in the header) or your custom UIActivityIndicatorView (you should locate it somewhere near the alertLabel).
Michael Kessler
Michael,thanks! The suggested solution is good, however I had to implement the following way:1. Show the alert label - in a background thread2. Start downloading in main thread3. Upon finishing (successful or error) remove the label - in background threadThat is:-(BOOL) query:(myDataT*)data{[alertLabel performSelectorInBackground:@selector(setText:) withObject:@"in progress…"];//query ...//query done// processing received data... data->result = … ; // ... [alertLabel performSelectorInBackground:@selector(setText:) withObject:@""]; return retVal;}
cactusdev
@cactusdev, this is exactly the opposite of what should be done. All the interaction with the UI (e.g. setText to label) has to be done in the main thread and all the heavy/long processing (e.g. synchronous connection) should be done in the background.
Michael Kessler
Michael, yes you are right of course, but actually I do not know how to implement opposite (that is, the normal) operation. Moreover the problem that I mentioned in my response to Eiko's answer (main thread would have to wait for data from query), other problem is that attempting to run the query in bkgnd crashes the app... This is not an error of unknown origin but certainly my fault in the code - just need to figure out what is wrong...
cactusdev
@cactusdev, I have explained the 2 changes to your implementation in my answer. Just follow these and you will have a normal behavior that I talk about. Call to `query` this way: `[self performSelectorInBackground:@selector(query) withObject:nil];` and change the `alertLabel.text = @"in progress…";` and `alertLabel.text = @"";` to `[alertLabel performSelectorOnMainThread:@selector(setText:) withObject:@"in progress…" waitUntilDone:NO];` and `[alertLabel performSelectorOnMainThread:@selector(setText:) withObject:@"" waitUntilDone:NO];`. That's it.
Michael Kessler
If you have posted the entire `query` implementation then it shouldn't crash.
Michael Kessler