iOS setting Progress bar value while images are being processed - iphone

I have an app processing a couple of images using Quartz, and i wanted to have a UIProgressView that changes after each actions. (e.g. 0.0 0.2 0.4 0.6 0.8 1.0)
The problem is, it seems while my image is being processed the UI is completely locked, and the value only changes after all of the process is done (meaning it just gets to 1.0 without going through the sub-steps),
Did any of you ever encounter this ?
Pseudo:
for(uint i=0;i<5;i++){
// Execute some Quartz based action here, such as CGContextDrawTiledImage etc...
myProgress.progress = (i+1) * 0.2;
}
So actually instead of the progress bar changing after each action, it only changes once at the end to 1.0. Would appreciate your feedback or experience or this.
Thank you
Shai.

You'll need to update either your assets or your progress bar in a separate thread so that the two can update in parallel.
Have a look at [NSThread detachNewThreadSelector:selector toTarget:target withObject:object];
Make your progress a member variable
[NSThread detachNewThreadSelector:#selector(updateFilterProgress) toTarget:self withObject:nil];
prgLoader.progress = x;
- (void) updateFilterProgress{
NSAutoreleasePool *pool = [NSAutoreleasePool new];
while ([prgLoader.progress floatValue] < 1.0f) //Keep this thread alive till done loading. You might want to include a way to escape (a BOOL flag)
{
GTMLoggerInfo(#"Updating progress to %f", [progress floatValue]);
prgLoader.progress = [progress floatValue];
}
[pool release];
}

Related

Having Trouble With GCD and Loading Thumbnails in TableView

I have the following code that attempts to load a row of thumbnails in a tableview asynchronously:
for (int i = 0; i < totalThumbnails; i++)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
__block GraphicView *graphicView;
__block Graphic *graphic;
dispatch_async(dispatch_get_main_queue(),
^{
graphicView = [[tableViewCell.contentView.subviews objectAtIndex:i] retain];
graphic = [[self.thumbnailCache objectForKey: [NSNumber numberWithInt:startingThumbnailIndex + i]] retain];
if (!graphic)
{
graphic = [[self graphicWithType:startingThumbnailIndex + i] retain];
[self.thumbnailCache setObject: graphic forKey:[NSNumber numberWithInt:startingThumbnailIndex + i]];
}
[graphicView setGraphic:graphic maximumDimension:self.cellDimension];
});
[graphicView setNeedsDisplay];
dispatch_async(dispatch_get_main_queue(),
^{
CGRect graphicViewFrame = graphicView.frame;
graphicViewFrame.origin.x = ((self.cellDimension - graphicViewFrame.size.width) / 2) + (i * self.cellDimension);
graphicViewFrame.origin.y = (self.cellDimension - graphicViewFrame.size.height) / 2;
graphicView.frame = graphicViewFrame;
});
[graphicView release];
[graphic release];
});
}
However when I run the code I get a bad access at this line: [graphicView setNeedsDisplay]; It's worth mentioning that the code works fine when I have it set up like this:
for (int i = 0; i < totalThumbnails; i++)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
dispatch_async(dispatch_get_main_queue(),
^{
//put all the code here
});
}
It works fine and the UITableView loads asynchronously when it's first called, however the scrolling is still really choppy.
So I'd like to get the to get the first bit of code to work so I can get the drawing done in the global thread instead of the main thread (which I assume will fix the choppy scrolling?).
Since iOS4 drawing is able to be done asynchronously so I don't believe that is the problem. Possibly I'm misusing the __Block type?
Anyone know how I can get this to work?
You completely misunderstand how to use GCD. Looking at your code:
__block GraphicView *graphicView;
Your variable here is not initialised to nil. It is unsafe to send messages to.
__block Graphic *graphic;
dispatch_async(dispatch_get_main_queue(),
^{
//statements
});
Your dispatch statement here returns immediately. The system works for you to spin this task off on a different thread. Before, or perhaps at the same time as, the above statements are executed we move on to the next line of execution here...
[graphicView setNeedsDisplay];
At this point graphic view may or may not have been initialised by your dispatch statement above. Most likely not as there wont have been time. As it still hasn't been initialised it points to random memory and hence trying to send messages to it causes EXC_BAD_ACCESS.
If you want to draw cell contents asynchronously (or pre-render images or whatever.) I thouroughly reccommend watching WWDC 2012 session 211 "Building Concurrent User Interfaces on iOS". They do almost exactly what you seem to be attempting to do and explain all the pitfalls you can run into.
I think the issue is because you are trying to re-draw the UIView on a working thread. You should move this:
[graphicView setNeedsDisplay];
To the main queue.

How to Implement UIProgressView in iphone sdk

I have this code in a button click [NSThread detachNewThreadSelector: #selector(spinBegininapp) toTarget:self withObject:nil]; to show a activity indicator for user that the background thread is running ,i put this code to enable the activity-indicator
- (void)spinBegininapp
{
_activityindictor.hidden = NO;
}
and it works fine,when i click the button it shows the activity-indictor animating ,when the thread goes it hides the activity-indicator,but my need is to show a progressView instead of activity-indicator,it progresses according to the thread,and if the thread finishes it need to reach progress completely and self hide.is that possible .
#pragma mark - Loading Progress
static float progress = 0.0f;
-(IBAction)showWithProgress:(id)sender {
progress = 0.0f;
_progressView.progress = progress;
[self performSelector:#selector(increaseProgress) withObject:nil afterDelay:0.3];
}
-(void)increaseProgress {
progress+=0.1f;
_progressView.progress = progress;
if(progress < 1.0f)
[self performSelector:#selector(increaseProgress) withObject:nil afterDelay:0.3];
else
[self performSelector:#selector(dismiss) withObject:nil afterDelay:0.2f];
}
-(void)dismiss {
[self progressCompleted];
}
now call the following function whenever/where ever you want to show progress
[self showWithProgress:nil];
progress range lies between 0.0 and 1.0
1.0 means 100%
you can add a progress view no doubt but usually it used for definite quantities like time or data.. for eg. If you are downloading a 2mb file then you can always tell how much data you have downloaded and show the in the progress view as a factor. So if something similar is happening inside your thread you can use this..
UIProgressView *progressView = [[UIProgressView alloc] initWithProgressViewStyle:whateverStyle];
progressView.progress = 0.75f;
[self.view addSubview: progressView]
[progressView release];
you just need to update your progress as the value changes.... hoping this helps.

how to use the progress bar in the iphone app

In my iPhone app I am downloading some data from an FTP server. To show the action I am using UIActivityIndicator. If I put UIProgressView there instead of UIActivityIndicator, it will be more appropriate. How do I use UIProgressView while downloading some data? Can anybody give me a tutorial link or example code? Thanks in advance.
first you create IBOutlet in .h file
IBOutlet UIProgressView * threadProgressView;
Then in .m file in viewdidload first set progress to 0.0 and then call makeMyProgressMoving method
threadProgressView.progress = 0.0;
[self performSelectorOnMainThread:#selector(makeMyProgressBarMoving) withObject:nil waitUntilDone:NO];
then add below method
- (void)makeMyProgressBarMoving {
float actual = [threadProgressView progress];
if (actual < 1) {
threadProgressView.progress = actual + ((float)recievedData/(float)xpectedTotalSize);
[NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:#selector(makeMyProgressBarMoving) userInfo:nil repeats:NO];
}
else{
}
}
also give your review for answer. is it useful to you?
It is quite simple. You just need to set appropriate value of property progress of UIProgressView.
In delegate of NSURLConnection you should receive the amount of data you are waiting to download and update the progress during downloading. Progress is represented by a floating-point value between 0.0 and 1.0, inclusive, where 1.0 indicates the completion of the task.
You can display progress of progress bar with these line of code
-(void) connection:(NSURLConnection *) connection
didReceiveData:(NSData *) data {
if (file)
{
[file seekToEndOfFile];
progressView.progress = ((float)recievedData / (float) xpectedTotalSize);
}
[file writeData:data];
recievedData += data.length;
NSLog(#"Receiving Bytes: %d", recievedData);
}
One of the option is AFNetworking.
AFURLConnectionOperation also allows you to easily stream uploads and downloads, handle authentication challenges, monitor upload and download progress, and control the caching behavior or requests.
noted: self.progressionBalance.progress = 5.0/10.0;
you must set decimal.

How can UI elements in iOS be updated from a loop without threads?

I want to update a label from a loop, for example like this:
- (void)viewDidLoad {
[super viewDidLoad];
int i=0;
while (1) {
i++;
[NSThread sleepForTimeInterval:0.05]; // do some computation,
[myLabel setText:[NSString stringWithFormat:#"%d", i]]; // show the result!
[self.view setNeedsDisplay];
}
}
Assume that instead of sleep some heavy computation is done.
I do not want the overhead of doing the computation in the background.
The Windows equivalent for handling this problem would be .DoEvents, as this example shows:
http://www.tek-tips.com/viewthread.cfm?qid=1305106&page=1
Is there a similar solution for this in iOS?
[self.view setNeedsDisplay] does not work at all!
There must be some way to process application events from iOS on a controlled schedule in the main thread... Like .DoEvents in windows, despite all its shortcomings is quite useful for some simple applications.
I guess this is like a game-loop but with UI components.
The way you would do this is as follows:
-(void) incrementCounter:(NSNumber *)i {
[myLabel setText:[NSString stringWithFormat:#"%d", [i intValue]]]; // show the result!
[self performSelector:#selector(incrementCounter:) withObject:[NSNumber numberWithInt:i.intValue+1] afterDelay:0.05];
}
- (void)viewDidLoad {
[super viewDidLoad];
// start the loop
[self incrementCounter:[NSNumber numberWithInt:0]];
}
The basic idea here is to increment the counter after a slight delay 0.05 to give the main UI thread a chance to flush all UI events, which makes up for explicitly calling .DoEvents in the Windows world.
I assume you want to implement a counter using a label? You can use NSTimer to call a method that updates your counter every X milliseconds, for instance.
Use NSTimer in iOS if you want to update UI components.
NSTimer* myTimer = [NSTimer scheduledTimerWithTimeInterval: 60.0 target: self
selector: #selector(callAfterSomeSecond:) userInfo: nil repeats: YES];
Implement the callAfterSomeSecond: as below :
-(void) callAfterSomeSecond:(NSTimer*) timer
{
static int counter = 0;
if(counter == 100)
{
[timer invalidate];
}
[myLabel setText:[NSString stringWithFormat:#"%d", counter ]];
[self.view layoutSubviews];
counter++;
}
in your code, the while loop runs in the Main thread, and the UI update also should be done in Main thread, so while the loop is running, the Main thread is kind of 'blocked'(busy), so UI update cannot be performed.
I think what I want to say is not what you want. to resolve it, you have to put the heavy computing in another thread, using NSOperation or GCD for example.
The best way is to do your computation on a different thread, but when that's not feasible, you can wrap the code that affects the UI in a CATransaction. In my tests this will update the UI immediately rather than needing to wait till the next run loop.
while (1) {
[CATransaction begin];
//Your UI code here
[CATransaction commit];
}

View not Updating

Kinda new to iPhone programming and was experimenting with threads
- (void)viewDidLoad {
[super viewDidLoad];
[NSThread detachNewThreadSelector:#selector(changeMain) toTarget:self withObject:nil];
[NSThread detachNewThreadSelector:#selector(changeThread) toTarget:self withObject:nil];
}
- (void)changeMain{
NSAutoreleasePool* arp = [[NSAutoreleasePool alloc] init];
for (int i = 0; i < 1000000; i++) {
[mainValue setText:[NSString stringWithFormat:#"%d",i]];
[self.view setNeedsDisplay];
}
[arp release];
}
- (void)changeThread{
NSAutoreleasePool* arp = [[NSAutoreleasePool alloc] init];
for (int i = 0; i < 1000000; i++) {
[threadValue setText:[NSString stringWithFormat:#"%d",i]];
[self.view setNeedsDisplay];
}
[arp release];
}
mainValue and threadValue are both just UILabels. I expected this to run and see both labels run up to 999999 but instead it starts at some low number (what it is when the screen initally refreshing i assume), pauses for a bit, then updates to 999999. I'm thinking the screen just isn't refreshing.
Is this correct? Am I doing it wrong?
You have to perform any Cocoa Touch operations in main thread, in other case results are unpredictable.
You don't have to call setNeedsDisplay manually.
So I'd recommend to use the following construction:
[threadValue performSelectorOnMainThread:#selector(setText:) withObject:[NSString stringWithFormat:#"%d",i] waitUntilDone:YES];
Additional notes:
1. 100000 runs can overflow the main thread queue so some values will disappear
2. You can use waitUntilDone:NO too
The setNeedsDisplay message triggers a redraw, but it only happens during the next time the main thread becomes active. So your side threads trigger a million redraws but they are queued. As soon as the main thread continues, it "collapses" all requests into one redraw.
Most likely setNeedsDisplay just sets a flag that is checked once during each run of the main loop, so setting it 1000000 to true doesn't influence anything. Try to let the "worker threads" sleep after each iteration to give the main thread some time to redraw.
Don't use for() for animations. The for will process in the same "frame". Maybe just have an ivar i and in changeMain you can have if (i<10000) { mainValue.text = [NSString stringWithFormat:#"%d",i]; i++;} or something like that. This way the setText only happens once per "frame".
I'm not sure if this will work, but you could try to force the setNeedsDisplay method to be executed on the main thread, using e.g. [self performSelectorOnMainThread:#selector(setNeedsDisplay) withObject:nil waitUntilDone:YES]. This should (hopefully, i didn't test it!) update the view after every increment. You could also try to set waitUntiDone:NO, but I'm unsure what will happen then.
See here