I use GCD to download Images from server and updates the processing on UILabel then print the label to the screen (ex: it will print to the screen : "Downloading: 3/15 Images")
But at the beginning the label is : "Downloading: 0/15 Images". Then when it finish downloading, the label is "Downloading: 15/15 Images".The user cant see the download processing.
What I want is user can see the processing like:
"Downloading: 1/15 Images","Downloading: 2/15 Images"."Downloading: 3/15 Images",...,"Downloading: 15/15 Images".
This is my code:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Here your non-main thread.
NSString *text;
for (int i = 0;i<[self.pageImages count];i++){
NSString *image = [self.pageImages objectAtIndex:i];
[dataManage downloadImagesFromUrl: image ];
text = [NSString stringWithFormat:#“Downloading %d/%d”,i,self.pageImages.count];
}
dispatch_async(dispatch_get_main_queue(), ^{
//Here you returns to main thread.
[downloadLabel setText:text];
});
});
Move
dispatch_async(dispatch_get_main_queue(), ^{
//Here you returns to main thread.
[downloadLabel setText:text];
});
inside the for loop so that the UI is updated after each download (rather than only at the end of all iterations).
Try this code inside the dispatch_async block:
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[downloadLabel setText:text];
}];
Related
I am trying to call a method in which I send to the background making use of dispatch_async.
It should be something that is simple, but for some reasons the UI is still blocked until the method returns.
Here is what I have:
dispatch_queue_t privateQueue = dispatch_queue_create("com", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(privateQueue, ^
{
__block UIImageView *imgView = [[UIImageView alloc] initWithFrame:self.view.frame];
dispatch_async(dispatch_get_main_queue(), ^
{
imgView = [controllerB startProcess];
controllerC.imageView = imgView;
});
});
I still have to wait for startProcess returns before UI is free again.
Then I tried to move imgView = [controllerB startProcess]; outside of dispatch_get_main_queue():
dispatch_async(privateQueue, ^
{
__block UIImageView *imgView = [[UIImageView alloc] initWithFrame:self.view.frame];
imgView = [controllerB startProcess];
dispatch_async(dispatch_get_main_queue(), ^
{
controllerC.imageView = imgView;
});
});
In this case, the UI is never updated with imgView but UI is not locked up.
I have tried to use a global queue, but the result is the same (UI has to wait):
dispatch_queue_t myQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
I think I am missing something very obvious here. Either that or it has been long day for me.
EDIT:
In [controllerB startProcess];
I am making use of:
UIGraphicsBeginImageContextWithOptions(self.frame.size, NO, 0.0);
UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
I am not sure if these methods have anything to do with GCD that causes my problem. The image is just .png.
Been thinking hard on this. Am running out of ideas. The only way I can update the UI with the image is to place the method call within dispatch_get_main_queue(), which beats the purpose of using GCD because all UI is blocked until the image is ready and method returns.
Any suggestion would be greatly greatly appreciated.
Use the second approach. Modify startProcess to use completion blocks and update your imageView inside the completion block. This ensures that imageView is updated after startProcess is complete.
Is it possible, that -in your 2nd example- when you try to set the imageView on the main queue the asynchronous calculation of the imageView in the background has not finished yet, so it can't display the imageView?
In that case a dispatch group might help:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.name.queue”, DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
//Do work
imgView = [controllerB startProcess];
});
dispatch_group_notify(group,queue,^{
//This code runs as soon as the code in the dispatch group is done.
controllerC.imageView = imgView;
});
I'm trying to update my textView on screen before it starts downloading data. Right now, it only updates the view after all of the downloads are complete. How can I do it before or in between the downloads?
Edit: I want the self.textView.text = #"Connection is good, start syncing..."; to update the UI before the downloading starts. But right now, it only updates after the download finishes.
Here is what the code looks like.
if ([self.webApp oAuthTokenIsValid:&error responseError:&responseError]) {
self.textView.text = #"Connection is good, start syncing...";
[self.textView setNeedsDisplay];
[self performSelectorInBackground:#selector(downloadCustomers:) withObject:error];
}
I'm new to this and have yet to learn how threads work, but from what I read, the downloadCustomers function should be using a background thread leaving the main thread to update the UI.
if ([self.webApp oAuthTokenIsValid:&error responseError:&responseError]) {
self.textView.text = #"Connection is good, start syncing...";
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self downloadCustomers];
dispatch_async(dispatch_get_main_queue(), ^{
//Do whatever you want when your download is finished, maybe self.textView.text = #"syncing finished"
});
});
}
The pattern here is to initialize your download on background thread and then call back to main thread for UI update.
Below is an example using GCD. The advantage of GCD version is that you can consider using whatever you do in -downloadCustomers, to insert in-line where you call it.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self downloadCustomers];
dispatch_async(dispatch_get_main_queue(), ^{
[self.textView setNeedsDisplay];
});
});
I have the following For loop which iterates through an NSMutableArray and calls the setImage method :
//Code to iterate through pictures and create ImageView class for each one.
for (int i =0; i<=[pictureThumbnailArray count]-1; i++) {
NSLog(#"Thumbnail count is %d", [pictureThumbnailArray count]);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void) {
[self setImage:[pictureThumbnailArray objectAtIndex:i]:i];
});
}
The setImage method sets various parameters on the picture and then finally adds it to the subview on the main thread :
dispatch_sync(dispatch_get_main_queue(), ^(void) {
[self.view addSubview:onePicture];
});
The problem is that the images appear on the screen randomly, rather than loading in order one by one. Can any suggest how I can improve this ?
Thanks.
The global background queue is concurrent, not serial, meaning that it doesn't guarantee ordering. You can create a custom serial queue that does:
dispatch_queue_t queue = dispatch_queue_create("com.example.MyQueue", NULL);
and use that in your dispatch_async.
I create a UIView contentView and display it. I then fetch data from server and create a bunch of subviews displaying the data. I am using MBProgressHUD to display while waiting on data.
if (datasetSubBar.panels == nil) {
MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:datasetSubBar.filterListView animated:YES];
HUD.labelText = #"Creating Panels";
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[datasetSubBar createPanels];
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:datasetSubBar.filterListView animated:YES];
});
});
}
in createPanels method, i fetch the data then create the panels. The panel is created, added to filterListView (the content view) and then add the constraints:
for (int i = 0; i < panels.count; i++) {
NSLog(#"thread: %#", [NSThread currentThread]);
NSDate *startDate = [NSDate date];
DatasetFilterListPanelView *panel = [panels objectAtIndex:i];
[contentView addSubview:panel];
// add constraints to position each panel
}
these are run on a separate thread which is what I believe the issue is. The UI can only be updated on the main thread.
I tried adding:
dispatch_async(dispatch_get_main_queue(), ^{
[contentView addSubview:panel];
});
But that raises errors for the constraints (the constraints don't have reference to it since its in a different thread).
If I run createPanels on the main thread, the panels will display but it also locks up the UI until its complete.
Any ideas?
I'm not sure which constraints are violated, but you could also try:
[contentView performSelctorOnMainThread:#selector(addSubview) withObject:panel waitUntilDone:YES];
instead of
dispatch_async(dispatch_get_main_queue(), ^{
[contentView addSubview:panel];
});
Not sure whether this will help.
In my app I just implement a dispatch_async block that will grab images from a NSDictionary and then eventually when the image is ready, set it to a UITableViewCell UIImageView. What I want to do is make a UIActivityIndicatorAppear in the middle of the UITableViewCell's UIImageView while the dispatch_async is occurring.
This is my code for the dispatch_async block:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^(void) {
NSData *data = [myDictionary objectForKey:#"myKey"];
UIImage *cellImage = [UIImage imageWithData:data];
//we get the main thread because drawing must be done in the main thread always
dispatch_async(dispatch_get_main_queue(),^ {
[cell.imageView setImage:cellImage];
} );
});
Anyway is this possible? And if so, how?
Thanks!
Show the progress indicator before your dispatch_async and remove or hide it your main queue block where you set the image.