When are a methods GUI operations actually carried out? - iphone

I am working on a web-services data processing app and I am trying to make the app run as quickly as possible. When a certain 3 finger pan gesture is performed, I call a method that sends updated information off to the server to get a new batch of images to update the existing ones with.
So lets say there are 15 images in an array, I filter through them with a 2 finger gesture, and then if I want to change something about them, I can do the 3 finger gesture, and I get that same set back, just tweaked a bit (contrast/brightness, etc.).
Is what I want though is to be able to update the imageView that is displaying the images after the first image has been retrieved, so as to give the user a feel for what the rest in the series are going to look like. But no matter what I try, and no matter how many different threads I try and implement, I can't get the imageView to update before the entire download is complete. Once the batch download is done (which is handled on a separate thread) the imageView updates with the new images and everything is great.
The first step in the process is this:
if(UIGestureRecognizerStateEnded == [recognize state]){
[self preDownload:windowCounter Level:levelCounter ForPane:tagNumber];// Where this method is what gets the first image, and tries to set it to the imageView
[self downloadAllImagesWithWL:windowCounter Level:levelCounter ForPane:tagNumber]; //And this method goes and gets all the rest of the images
}
This is my preDownload method:
-(void)preDownload:(int)window Level:(int)level ForPane:(int) pane{
int guidIndex = [[globalGuids objectAtIndex:pane] intValue];
UIImage *img = [DATA_CONNECTION getImageWithSeriesGUID:[guids objectAtIndex:guidIndex] ImageID:counter Window:window Level:level];
if(pane==0){
NSLog(#"0");
[imageView3 setImage:img];
}else if(pane==1){
NSLog(#"1");
[imageView31 setImage:img];
}else if(pane==2){
NSLog(#"2");
[imageView32 setImage:img];
}else if(pane==3){
NSLog(#"3");
[imageView33 setImage:img];
}
}
So by separating this out into two different methods (there are no threads being implemented at this point, these methods are being called before all that) I was thinking that after the preDownload method completed, that the imageView would update, and then control would continue on down into the downloadAllImagesWithWL method, but that doesn't appear to be the case.
Am I missing something simple here? What can I do to update my GUI elements before that second method is through running?

You are right. However the viewn won't refresh until your code reaches runloop. You can do 2 things:
Make your downloadAllImagesWithWL method async, so it will return after you called it, your main thread reaches runloop, gui updates, and the download method will tell your logic through a callback when its done.
OR
A simplier hackier (and bad) solution would be to run runloop for some time before you call your download method. Something like this: [[NSRunloop currentRunLoop] runUnitlDate: [Date dateWithTimeIntervalSinceNow: 0.1]]; It will run runloop for 0.1 second.

When the image is set, the image view will mark itself as needing display. The actual display won't occur until the beginning of the next run loop. In OS X, you can use -display to draw the view immediately, but I don't think Apple created a public method to do this on iOS. However, if the next method simply creates the background thread, then it will return quickly and the display update will probably occur before the thread finishes.

Related

Use UIProgressView for image editing

In my app I apply filters for the image. I want to add UIProgressView as it takes about couple of seconds to complete the editing. How to add a UIProgressView for such method and also update how many percent it is completed? I have gone through many examples for UIProgressView but they are for Http request and loading network data. How to implement that for custom method ?
I guess you need to use Operation Queue for this.
[NSThread detachNewThreadSelector:#selector(showProgressView:) toTarget:self withObject:nil];
This has worked for me I hope it works for you too.. Have a happy Coding.!!
If you have control over how much of the editing is completed, like any delegate events you can update the progress just like network calls or others. Even if you don't have such events, you might be having some inner methods, which can give you a delta of the total progress happened.
Then you can have a method which takes in this delta, add it to existing progress.
- (void)updateProgress:(CGFloat)delta
{
[self.progressView setProgress:self.progressView.progress+delta];
self.progressLabel.text = [NSString stringWithFormat:#"Completed : %.0f",self.progressView.progress*100];
}
It's difficult to answer this without knowing what data you have available to you, but essentially, you need to have a method that gets called at a consistent periodic time interval until the image filter gets applied. A trick for doing this would be if you could come up with a time estimate for a filter and then just use an NSTimer to fire a method until that time is complete. Although if you could figure out an exact time for it to filter, that would obviously be preferable.
From there you need to set the progress of the progress view to that time over the amount of time total like so...
self.progressView.progress = (float) progress
Keep in mind though that the progress view is a percent, so you will need to store two values to compare (divide over each other) 1) the total amount of time 2) the total amount of time passed. You can just do this though by setting up a float progress in your user #interface.

stop and restart a method

I have to stop and restart a simple method that takes from 1 to 2 seconds to execute. How can this be accomplished? I have already tried [NSObject cancelPreviousPerformRequestsWithTarget:self] but it works only with performselector after delay. I have also tried to create a new thread but it doesn't seem to work...
This is my method:
-(IBAction)MyMethod
{
NSLog(#"start");
//Here is the code that takes time to execute. It regards UI intervention,graphic calculation, x and y position etc.
NSLog(#"end");
}
I want this effect: one click on the linked UIButton and the method start (so print start log and end log). If I click the linked UIButton before the NSLog is printed, the method must stop. Is this possible?
You'll want to use a background task. I would suggest subclassing and using a NSOperation and checking for isCancelled within the body of main. See Apple's documentation on using NSOperation and NSOperationQueue.
Well, to do this with threads what I usually do is dividing things into 3 sections:
start.
processing.
finish.
And it looks like this:
-(void)start:(id)sender{
//prepare everything and anything
[NSThread detachNewThreadSelector:#selector(processing:) toTarget:self withObject:nil];
}
-(void)processing:(id)sender{
//Perform all your calculations, you can't modify UI elements here
[self performSelectorOnMainThread:#selector(finish:) withObject:nil waitUntilDone:NO];
}
-(void)finish:(id)sender{
//Wrap everything up and do any modifications to the UI
}
Now, to cancel this you could add maybe use:
Cancels perform requests previously registered with
performSelector:withObject:afterDelay:.
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anArgument

How to make a static image appear after 3 seconds?

How would I make an image appear after 3 seconds?
You can use:
[self performSelector: withObject: afterDelay: ]
I'm a big fan of using GCD (iOS 4+) because you can simplify your code with inline blocks.
In your case, you should set the image to hidden in Interface Builder, then create an IBOutlet with a connection to an ivar in your class.
Then you can simply run this in viewDidLoad or similar:
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 3.0);
dispatch_after(delay, dispatch_get_main_queue(), ^(void){
yourImage.hidden = NO;
});
This assumes that you are calling performSelector:withObject:afterDelay from the main thread, and that your UIImageView is initially hidden.
//assumes theImageView.hidden = YES
[self performSelector:#selector(showImage:) withObject:theImageView afterDelay:yourTimeInterval];
-(void)showImage:(UIImageView*)anImageView {
anImageView.hidden = NO;
}
It is important that performSelector is called from the main thread because the selector that is called after the delay will run on the same thread, and you do not want to update UI from anything other than the main thread as a general rule.
I haven't used XCode in awhile, but I'll take a stab for ya..
In your Interface Builder set the image's visibility as hidden
When your app starts up, set some global variable to the current time in an init fxn
In the main control loop for your UI, check if that global var contains a time that is more than 3 seconds ago, if so, change that image's visibility parameter to shown.
Best I can really say without really taking a look, which isn't possible right now.
Good luck!

Asynchronous function execution?

in my iOS app I do the following.
viewDidAppear(){
// Load a spinner in a view on the top
[DSBezelActivityView newActivityViewForView:self.view];
// Execute code that require 3 seconds
...
// Stop the spinner
[DSBezelActivityView removeViewAnimated:YES];
}
The problem is that the spinner doesn't appear, because the the cpu is working hard (something similar). It's like that the code betweek the start and stop has precedence on the rendering of the view.
I would love to find a way to show effectively the start of the spinner, without using a timer to delay the code execution.
Thanks
If you have a method like
-(void) showSpinner:(UIView*)view {
dispatch_async(dispatch_get_main_queue(), ^{
[DSBezelActivityView newActivityViewForView:view];
});
}
there are several ways to call it from a different thread. Choose one from the following:
[NSThread detachNewThreadSelector:#selector(showSpinner:) toTarget:self withObject:self.view];
// or
[self performSelectorInBackground:#selector(showSpinner:) withObject:self.view];
// or
NSInvocationOperation *invOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(showSpinner:) object:self.view];
NSOperationQueue *opQueue = [[NSOperationQueue alloc] init];
[opQueue addOperation:invOperation];
// or
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self showSpinner:self.view];
});
Alt + click for details.
Move code between start and stop activity indicator into separate thread because it's blocking main thread. That's why activity indicator is not showing.
Edit: Example
I agree with the 1st answer with a couple of modifications. I just went through this exact same problem. The problem is that anything graphical is going to automatically move to the background updating when you have code that takes time to get through. Throwing the spinner to the background is what it essantially doing anyway. What you want is (sadly) for you main code to run in the background and the spinner to run in the foreground. I know this sounds bad, but in some cases allowing your code to run a bit slower to give indication that the app is doing something useful is beneficial to the user.
In order to get the spinner to work:
1) Take all the code that takes the 3 seconds to run, and put that into a function that is a void function
2) Instantiate your spinner but store it to a variable that is accessible outside your viewDidAppear routine.
3) Startup a new NSTimer with that runs continuously with an increment of about every quarter second or so. I will define what goes into the routine that gets called every cycle later.
4) Call the routine you created in step 1 using the performSelectorInBackground capability. This essentially is now going to run your startup (3 seconds worth) in the background which is really the only way to allow the animated spinner to show up and truly animate.
5) In the routine you created in step 1, add a line of code right at the top that updates a (global to the object) boolean to true, stating that we are in the middle of our main 3 second routine.
6) At the end of the routine defined in step 1 add a line of code setting the same global defined in step 5 to false indicating that our 3 second routine is completed.
7) In the timer routine, we now want to do something that looks like the following:
// If busy that start the spinner
if(YES == busy){
[spinner startAnimating];
}else{
[spinner stopAnimating];
// Here we can also stop and deallocate the timer
}
If you need more aid on this subject, I can indeed provide exact code. Take a look at the example app that I have developed for the Pepperdine News Group. When you press a button, the spinner comes up on the top right of the screen.
http://itunes.apple.com/us/app/pepperdine-graphic-for-iphone/id516343215?mt=8

iPhone : Best way to detect the end of UIImageView image sequence animation

We know that UIImageView has a very nice support for image sequence animation. We can easily create an array of UIImage objects, set the animationImages property, configure animation duration, repeat count etc. and then just fire. But there seems to be no way to know when this animation has ended.
Say I have ten images and then I want to run an animation (repeat count = 1) with them. And when the animation is over, I want to run some other code. What is the best way to know that animation has ended?
I already understand that I can create a NSTimer and schedule it to fire after animation duration. But you really cannot rely on timer if you need good precision.
So my question is, is there any better way to know that an UIImageView image sequence animation has ended without using the timer?
The code is something like this
myImageView.animationImages = images; // images is a NSArray of UIImages
myImageView.animationDuration = 2.0;
myImageView.animationRepeatCount = 1;
[myImageView startAnimating]
The isAnimating property on UIImageView should go to NO when it's done animating. It's not a formal property, though, so you can't set up observation on it. You can poll it on a fine-grained timer (like CADisplayLink's).
There's no "animation completed" delegate for this, if that's the sort of thing you're looking for. The timing can be variable based on loading delay of the images, etc, and no, there's no sure-fire way to know precisely when it's done.
The image animation stuff on UIImageView is a convenience, and not heavyweight enough to do serious animation work with. Consider rolling your own if you need that kind of precision.
I made this (method of my UIImageView subclass).
-(void)startAnimatingWithCallback:(UIImageViewAnimationCompletitionBlock) completitionCallback
{
[self startAnimating];
dispatch_queue_t animatingQueue = dispatch_get_current_queue();
dispatch_queue_t pollingQueue = dispatch_queue_create("pollingQueue", NULL);
dispatch_async(pollingQueue, ^{
while(self.isAnimating) { usleep(10000); }
dispatch_async(animatingQueue, ^{ if (completitionCallback) completitionCallback(); });
});
}
Simple usage:
[self.oneView fadeOut];
[self.otherView startAnimatingWithCallback:^
{
[self.oneView fadeIn];
}];
Furthermore I could recommend setting default image of the UIImageView (property image) on the first frame of the animation and changing it to the last frame just after launching the animation (startAnimating method). This way we avoid the ugly blick which can occur when the animation is finished but the callback is not invoked.