I've already searched for the problem, and the only hint was the performSelectorOnMainThread solution. I did that, but the view is not updating while the for loop is running. After the loop the progress value is displayed correctly – but that's not the point of an "Progress" View =)
The for loop is within a controller. I call a method to set the progress value:
CGFloat pr = 0.5; // this value is calculated
[self performSelectorOnMainThread:#selector(loadingProgress:) withObject:[NSNumber numberWithFloat:pr] waitUntilDone:NO];
And this is the loading progress method
-(void)loadingProgress:(NSNumber *)nProgress{
NSLog(#"Set Progress to: %#", nProgress);
[[self progress] setProgress:[nProgress floatValue]];
}
I also tried to put the method with the for loop into a background thread by using this at the top of the method:
if([NSThread isMainThread]) {
[self performSelectorInBackground:#selector(importData) withObject:nil];
return;
}
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
Maybe somebody can help me with this.
CGFloat pr = 0.5; // this value is calculated
[self performSelectorInBackground:#selector(loadingProgress:) withObject:[NSNumber numberWithFloat:pr]];
try this to set the progress value
Related
So self.pic is the UIButton that I want to hide. But it never actually hides it for some reason.
Please Help
- (void)tick:(NSTimer*)time
{
self.pic.hidden = YES;
[NSThread sleepForTimeInterval:1];
self.pic.hidden = NO;
[self.pic setEnabled:NO];
if (self.checkPic == YES)
{
self.lives--;
self.livesLabel.text = [NSString stringWithFormat:#"Lives : %d", self.lives];
}
[self.pic setImage:[self backgroundImageForGame] forState:UIControlStateNormal];
[self check];
self.pic.enabled = YES;
}
Just to elaborate on what #rmaddy said in his comment, you shouldn't ever sleep the main (or UI) thread. When you do this, you're blocking the thread that is responsible for making the visual changes to the button, like whether or not it is hidden. The solution is to do the waiting asynchronously, and GCD has a built in function that makes this painless.
dispatch_after() allows you to create a block that will be executed after a specified delay, on a queue of your choosing. Since you want to update the UI, you'll want to come back to the main queue to make the changes to the on screen button. Here's an example:
self.pic.hidden = YES;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.pic.hidden = NO;
});
When loading my UIViewController, I basically put a spinner in the middle of the page until the content loads, then come back on the main thread to add the subviews :
- (void)viewDidLoad {
[super viewDidLoad];
UIActivityIndicatorView *aiv_loading = ... // etc
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Load content
NSString *s_checkout = [[BRNetwork sharedNetwork] getCheckoutInstructionsForLocation:self.locBooking.location];
UIView *v_invoice_content = [[BRNetwork sharedNetwork] invoiceViewForBooking:locBooking.objectId];
// Display the content
dispatch_sync(dispatch_get_main_queue(), ^{
if (s_checkout && v_invoice_content) {
[aiv_loading removeFromSuperview];
[self showContentWithText:s_checkout AndInvoice:v_invoice_content];
} else {
NSLog(#"No data received!"); // is thankfully not called
}
});
});
}
- (void) showContentWithText:(NSString *)s_checkout AndInvoice:(UIView *)v_invoice {
[self.view addSubview:[self checkoutTextWithText:s_checkout]]; // Most of the time displayed text
[self.view addSubview:[self completeCheckout]]; // always Displayed UIButton
[self.view addSubview:[self divider]]; // always displayed UIImageView
// Summary title
UILabel *l_summary = [[UILabel alloc] initWithFrame:CGRectMake(0, [self divider].frame.origin.y + 6 + 10, self.view.bounds.size.width, 20)];
l_summary.text = NSLocalizedString(#"Summary", nil);
[self.view addSubview:l_summary];
CGRect totalRect = CGRectMake([self divider].frame.origin.x, [self divider].frame.origin.y + 6 + 30, self.view.bounds.size.width - [self divider].frame.origin.x, 90);
_v_invoice = v_invoice;
_v_invoice.frame = totalRect;
[self.view addSubview:[self v_invoiceWithData:v_invoice]]; // THIS Pretty much never displayed
UITextView *l_invoice = [[UITextView alloc] initWithFrame:CGRectMake(0, _v_invoice.frame.origin.y + _v_invoice.frame.size.height + offset, 320.0, 50)];
l_invoice.text = NSLocalizedString(#"summary_emailed", nil);
[self.view addSubview:l_invoice]; // Always displayed
}
However, not all the content is displayed. The invoice is never there at first, but gets displayed after a couple of minutes. The other async-created string, s_content is sometimes not displayed.
This seems to be random with the content creation. The end result is pretty neat, but not reliable for a production version.
I used the undocumented [self.view recursiveDescription] to check if everything was there, and even if I don't see it, they are all there with what seems to be correct frames.
Any pointers?
- layoutSubviews did not help!
- putting a background color to the invoice view is showing the background color
I suspect this line is your problem:
UIView *v_invoice_content = [[BRNetwork sharedNetwork] invoiceViewForBooking:locBooking.objectId];
As you are calling this in a background dispatch queue. Any work involving UIKit should be done on the main queue/thread. Either move that into the main thread block, or if building the view is dependent on data from a network call, change your invoiceViewForBooking method to return the data first, and build your view in the main thread with that data.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Load content
NSString *s_checkout = [[BRNetwork sharedNetwork] getCheckoutInstructionsForLocation:self.locBooking.location];
id someData = [[BRNetwork sharedNetwork] invoiceDataForBooking:locBooking.objectId];
// Display the content
dispatch_async(dispatch_get_main_queue(), ^{
UIView *v_invoice_content = [invoiceViewWithData:someData];
});
});
I'd also suggest using dispatch_async instead of dispatch_sync on the main queue.
Solved! I ended up removing the dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
To only leave the async block on the main queue:
// Display the content from a new async block on the main queue.
dispatch_async(dispatch_get_main_queue(), ^{
[aiv_loading removeFromSuperview];
NSString *s_checkout = [[BRNetwork sharedNetwork] getCheckoutInstructionsForLocation:self.locBooking.location];
[self showContentWithText:s_checkout AndInvoice:[[BRNetwork sharedNetwork] invoiceViewForBooking:locBooking.objectId]];
});
which did the trick, viewcontroller's view can appear without waiting for the view to load the remote data!
You should put your code in viewDidAppear instead of viewDidLoad. Whatever is related to display (even launching async blocks) should always be in viewWillAppear or viewDidAppear.
Also, I recommend you to use dispatch_async(dispatch_get_main_queue(), ^{}) instead of your dispatch_sync since you still want your block to be performed async (but on main thread).
Dont forget to call super methods in your viewDidAppear.
My application will query a database 50 times and will then react accordingly by adding the right UIImageView to another UIImageView, which I then want the application to display immediately at each loop.
Alas, after many nights I have failed to get it to work. The application will not immediately display the UIImageView. Having said that, when I zoom in or zoom out during mid loop the UIImageViews will appear! I must be missing something...
So far every code works except the last part [UIScrollView performSelector....
Please help and thank you in advance.
UIScrollView *firstView;
UIImageView *secondView;
UIImageView *thirdView;
NSOperationQueue *queue;
NSInvocationOperation *operation;
- (void)viewDidAppear:(BOOL)animated
{
queue = [NSOperationQueue new];
operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(getData) object:nil];
[queue addOperation:operation];
}
- (void) getData
{
for (i=0 ; i < 50 ; i++)
{
//All the codes to facilitate XML parsing here
[nsXMLParser setDelegate:parser];
[BOOL success = [nsXMLParser parse];
if (success)
{
if ([parser.currentValue isEqualToString:#"G"])
thirdView.image = greenTick.jpg;
[secondView addSubview:thirdView];
}
else
{
NSLog(#"Error parsing document!");
}
}
[thirdView performSelectorOnMainThread:#selector(setNeedsDisplay) withObject:nil waitUntilDone: YES];
}
I discovered the solution and truly hope this will help someone...
if (success)
{
if ([parser.currentValue isEqualToString:#"G"])
// Make the changes here - start
dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_async(main_queue, ^{
thirdView.image = greenTick.jpg;
[secondView addSubview:thirdView];
});
// Make the changes here - end
}
else
{
NSLog(#"Error parsing document!");
}
}
// Remove performSelectorOnMainThread
I have started playing with UIProgressView in iOS5, but havent really had luck with it. I am having trouble updating view. I have set of sequential actions, after each i update progress. Problem is, progress view is not updated little by little but only after all have finished. It goes something like this:
float cnt = 0.2;
for (Photo *photo in [Photo photos]) {
[photos addObject:[photo createJSON]];
[progressBar setProgress:cnt animated:YES];
cnt += 0.2;
}
Browsing stack overflow, i have found posts like these - setProgress is no longer updating UIProgressView since iOS 5, implying in order for this to work, i need to run a separate thread.
I would like to clarify this, do i really need separate thread for UIProgressView to work properly?
Yes the entire purpose of progress view is for threading.
If you're running that loop on the main thread you're blocking the UI. If you block the UI then users can interact and the UI can't update. YOu should do all heavy lifting on the background thread and update the UI on the main Thread.
Heres a little sample
- (void)viewDidLoad
{
[super viewDidLoad];
[self performSelectorInBackground:#selector(backgroundProcess) withObject:nil];
}
- (void)backgroundProcess
{
for (int i = 0; i < 500; i++) {
// Do Work...
// Update UI
[self performSelectorOnMainThread:#selector(setLoaderProgress:) withObject:[NSNumber numberWithFloat:i/500.0] waitUntilDone:NO];
}
}
- (void)setLoaderProgress:(NSNumber *)number
{
[progressView setProgress:number.floatValue animated:YES];
}
Define UIProgressView in .h file:
IBOutlet UIProgressView *progress1;
In .m file:
test=1.0;
progress1.progress = 0.0;
[self performSelectorOnMainThread:#selector(makeMyProgressBarMoving) withObject:nil waitUntilDone:NO];
- (void)makeMyProgressBarMoving {
NSLog(#"test %f",test);
float actual = [progress1 progress];
NSLog(#"actual %f",actual);
if (progress1.progress >1.0){
progress1.progress = 0.0;
test=0.0;
}
NSLog(#"progress1.progress %f",progress1.progress);
lbl4.text=[NSString stringWithFormat:#" %i %%",(int)((progress1.progress) * 100) ] ;
progress1.progress = test/100.00;//actual + ((float)recievedData/(float)xpectedTotalSize);
[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(makeMyProgressBarMoving) userInfo:nil repeats:NO];
test++;
}
I had similar problem. UIProgressView was not updating although I did setProgress and even tried setNeedsDisplay, etc.
float progress = (float)currentPlaybackTime / (float)totalPlaybackTime;
[self.progressView setProgress:progress animated:YES];
I had (int) before progress in setProgress part. If you call setProgress with int, it will not update like UISlider. One should call it with float value only from 0 to 1.
I'm having a hard time removing my animation from memory. What is the best way to do this?
This is my animation code:
-(IBAction)animationOneStart {
NSMutableArray *images = [[NSMutableArray alloc] initWithCapacity:88];
for(int count = 1; count <= 88; count++)
{
NSString *fileName = [NSString stringWithFormat:#"testPic1_%03d.jpg", count];
UIImage *frame = [UIImage imageNamed:fileName];
[images addObject:frame];
}
loadingImageView.animationImages = images;
loadingImageView.animationDuration = 5;
loadingImageView.animationRepeatCount = 1; //Repeats indefinitely
[loadingImageView startAnimating];
[images release];
}
Thanks!
[images removeAllObjects];
[images release];
Don't need to release it anymore with ARC under Xcode 4.2! Update today :)
As far as I can tell, there is not a callback to know when the imageView's animation is finished. This is unfortunate, because it means that to remove the animation when the imageView is finished animating, you will need repeatedly check the imageView's isAnimating method until it returns false. The following is an example of what I mean:
You might be able to do something like this—in your original code, add the following line
[loadingImageView startAnimating];
[images release];
[self performSelector:#selector(checkIfAnimationIsFinished) withObject:nil afterDelay:4.5f];
Then create a method
-(void)checkIfAnimationIsFinished {
if (![loadingImageView isAnimating]) {
[loadingImageView release];
} else {
[self performSelector:#selector(checkIfAnimationIsFinished) withObject:nil afterDelay:0.5f];
}
}
This was just an example, I used the values 4.5f and 0.5f because, unfortunately, the timing is not precise, it is more of a ballpark figure. 4.9f and 0.1f might work better. Regardless, the problem is that if you set the method to fire 5.0f seconds after you start the animation, it is not 100% certain that the animation will be be finished or not. So you will need to check repeatedly if it is not finished.
Anyway, this is not the cleanest solution, but unless someone else knows how to get a callback when an imageView is finished animating, I don't know of a better solution.