I have a class that generates images to be printed by the user. These images are created using QuartzCore (and some UIKit elements) and need to be run on the main thread.
In the view visible to the user while the images are being generated, I have a progress bar. This view is the delegate of the class that does the printing, and a method is called by the printer on the view to update the progress bar. The problem is, the progress bar doesn't visibly update until the printer is finished because the printer is clogging up the main thread. I can't move the progress bar off of the main thread because all UI updates must be done on the main thread.
I'm fairly new to multithreading; do I have any other options or must I go with an activity indicator or something similar?
Update your code so that the image creation is done on a background thread. This should be safe.
Then you can make calls onto the main thread to update the progress bar.
You can use GCD, Raywenderlich Tutorial
- (void)generatePage
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
/*
Here you can generate page and update progress
For example:
*/
[self updateProgress:10.0f];
sleep(1000);
[self updateProgress:20.0f];
sleep(3000);
[self updateProgress:100.0f];
});
}
- (void)updateProgress:(float)progress
{
dispatch_async(dispatch_get_main_queue(), ^{
progressView.progress = progress;
});
}
Related
I'm trying to display a view because the method takes a good few seconds to complete. However, the loading UIWindow doesn't actually become key and visible until the whole method is complete. Any idea what's going on here?
// this line creates a uiwindow with view controller as root view controller, an activity indicator, and a label and makes the loading view key and visible
[XSELLoadingView presentLoadingViewWithTitle:#"Generating Report"]; // use loading view for long times
// these three lines process a lot of data and present the view on the main window
XSELCount *startCount = [[XSELCount counts] objectAtIndex:[self.startingPick selectedRowInComponent:0]];
XSELCount *endCount = [[XSELCount counts] objectAtIndex:[self.endingPick selectedRowInComponent:0
[self.navigationController pushViewController:[XSELReportView viewWithReportData:[XSELReportData compareStartCount:startCount toEndCount:endCount]] animated:false];
// this line makes the main view key and visable
[XSELLoadingView dismissLoadingView];
Apply your task inside this one.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Do background work
dispatch_async(dispatch_get_main_queue(), ^{
//Update UI
});
});
I have an UITableView with some UITableViewCells and when I click on a specific cell the application will download some information from my server. The question is: "How can I show IMMEDIATELY* a view with only an UIActivityIndicator which is animated during all the time of the download and stops its animation when the download is complete?"
*Note: It should not be after other strange operations from the app, it must be the first thing after the click on the cell.
You can use the below method which starts and stops the activity indicator on main thread in a single method
- (void)showIndicatorAndStartWork
{
// start the activity indicator (you are now on the main queue)
[activityIndicator startAnimating];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// do your background code here
dispatch_sync(dispatch_get_main_queue(), ^{
// stop the activity indicator (you are now on the main queue again)
[activityIndicator stopAnimating];
});
});
}
Note:
I am doing some background stuff so I have used dispatch_async, if you also want to download something in background you can also use dispatch_async else you can also download the stuff on main thread.
I am having a UIViewController that shows progress status of an calculations that are done on my Iphone app, to see the percent of the progress I need to push button called refresh on the same UIViewController, how can I make that automatically done without the need to push the button manually
here is my code:
- (void)viewDidLoad{
[NSThread detachNewThreadSelector:#selector(autoRefresh) toTarget:self withObject:nil];
...
}
and then :
- (void) autoRefresh{
while (1) {
sleep(2);
sendFromButton = false;
if (flag == 1) { // which button pushed last
[self CaptureButton:self];
}
if (flag == 2) { // which button pushed last
[self ExampleButtonfun:self];
}
if (flag == 3) { // which button pushed last
[self CommuintyButton:self];
}
}
}
when the controller is viewed for the first time the viewDidLoad is called that creates a thread to run the autorefresh function , but that controller is not refreshed although I did it in the right way I guess!, please help me with that.
If you want to do a series of calculations, and show that progress, you have to do the calculations in a background thread, and then update the UI on the main thread. If you did your calculations in the main thread, your UI would never have a chance to update itself.
But, assuming that you've successfully initiated your time consuming calculations on a background thread, you could then use a timer (or display link) to update your progress bar, for example, define a timer property:
#property (nonatomic, weak) NSTimer *timer;
Then schedule a repeating timer from the main queue and start your background process:
// start the timer
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0/60.0
target:self
selector:#selector(updateProgress:)
userInfo:nil
repeats:YES];
self.timer = timer;
// use whatever mechanism you want for initiating your background process (though dispatch queues and operation queues may be easier than dealing with threads directly)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self startTimeConsumingProcess]
});
You'd then obviously implement an updateProgress: method that updates your UI:
- (void)updateProgress:(NSTimer *)timer
{
// update the UI progress bar here
}
And don't forget to invalidate that timer when your calculation is done or when the view is dismissed, because if you don't, the timer will maintain strong reference to your view controller and you'll have a strong reference cycle (aka retain cycle).
By the way, the other logical approach, instead of using a timer, is to just have the background calculation dispatch UI updates that update the progress bar back to the main queue, e.g.
- (void) startSomeTimeConsumingProcess
{
// start time consuming process in background queue
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
BOOL finished = NO;
while (!finished)
{
// do something, updating `finished` when done
// update UI to report the progress in the main queue, though:
dispatch_async(dispatch_get_main_queue(), ^{
// update progress UI here
});
}
});
}
The only consideration with this approach is how quickly this background process dispatches the main queue with progress bar updates. If it updates too quickly, you can backlog the main queue with progress updates. Hence, the appeal of the timer (or display link) based approach, above. But if you're confident that these updates will happen slowly enough, this alternative approach might be easier.
In my application i am using back ground thread for hitting multiple service and perform operation with core data. I have used main thread for back ground process ,Its working fine.
Here is my code
dispatch_queue_t main = dispatch_get_main_queue();
dispatch_async(main,
^{
[self backGroundCall];
});
-(void)backGroundCall
{
NSLog(#"Done");
if([CacheManager refreshDBforFirstTimeUseWithDelegate:self])
{
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"IsDBInitialized"];
ContainerViewController *containerViewControllerInstance = [ContainerViewController getContainerInstance];
[containerViewControllerInstance setUserId:_userID];
[progressView setHidden:YES];
[self.view setUserInteractionEnabled:YES];
[self.navigationController setDelegate:containerViewControllerInstance];
[self.navigationController pushViewController:containerViewControllerInstance animated:YES];
}
}
once i initialize the data base , i need to navigate to the container view.During the initialization i will display one progress bar. That is working fine, when the entire background process is completed(app is in minimized state). During the background process if i come to the foreground progress bar is not showing at that time black screen is display instead of progress view . After the completion of the main threat container view all not display[if i comes to foreground of main thread process].
i need to show the progress bar, if i come back to the app in the middle of the main thread process. Please guide me to fix this issue.
Thanks.
dispatch_queue_t main = dispatch_get_main_queue();
dispatch_async(main,
^{
[self backGroundCall];
});
This is a bit misleading... You call the method backGroundCall, but you are actually doing this on the main thread. If you want to make some operation on a working thread, you can do this:
// Declare the queue
dispatch_queue_t workingQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(workingQueue,
^{
// My background job
dispatch_async(dispatch_get_main_queue(),
^{
// Update the UI
}
);
});
I have a button on the currently navigated to viewcontroller, connected to an IBAction.
In the IBAction I create a UIActivityIndicatorView as usual, with [self.view addSubView], then load some pictures.
I've tried setNeedsDisplay on the indicator view, the view controller, and the window, but it still loads the pictures before showing the indicator, which of course is quite useless to me.
So I'm looking for a way to either force an instant redraw (which when I think a little more about it is unlikely to make work), or a way to load the pictures after the indicator has appeared, or a way to launch a separate thread or similar to start animating / show the indicator, or put the indicator in a separate viewcontroller and somehow force it to add/show itself before going on to the picture-loading.
Recommendations?
What I do in this situation is spawn a new thread, which frees up the main thread to handle UI interaction while stuff is loading in the background.
First show the UIActivityIndicatorView, then spawn a new thread that loads the images, then on the last line of the method that is executed in the new thread, hide the UIActivityIndicatorView.
Here's an example:
//do stuff...
[activityIndicatorView startAnimating];
[NSThread detachNewThreadSelector:#selector(loadImages) toTarget:self withObject:nil];
In your loadImages method:
- (void) loadImages {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//load images...
[activityIndicatorView performSelectorOnMainThread:#selector(stopAnimating)];
[pool drain];
}