stopping a programming loop - iphone

When my main viewController is first clicked, it starts showing a demo (on repeat) as follows:
showingDemo = YES;
[self startDemo];
- (void)startDemo {
if (showingDemo) {
[self performSelector:#selector(stepone) withObject:nil afterDelay:1.5f];
[self performSelector:#selector(steptwo) withObject:nil afterDelay:2.0f];
[self performSelector:#selector(stepthree) withObject:nil afterDelay:3.8f];
[self performSelector:#selector(stepfour) withObject:nil afterDelay:4.3f];
[self performSelector:#selector(startDemo) withObject:nil afterDelay:5.6f];
}
}
When it is clicked a second time, I bring a new ViewController to the screen
showingDemo = NO;
[self.view addSubview:newView];
I thought this would stop the endless loop.
When the user returns back to my main viewController:
[newView.view removeFromSuperview];
And clicks on the screen again:
showingDemo = YES;
[self startDemo];
In testing my app, if I click back quickly (before the loop has had time to end, the program seems to be running through the loop twice - the one that was previously going and the new one - and therefore it looks all weird, with the 'stepthree' function happening before 'stepone' and so forth.
Anybody know a better way to STOP the loop I've programmed for good so that when I go to start it again later, it doesn't run multiple loops thinking that the previous one hasn't been finished?
Thanks so much!

When you set showingDemo to NO, call NSObject's cancelPreviousPerformRequestsWithTarget: to cancel any pending perform requests:
showingDemo = NO;
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[self.view addSubview:newView];

Related

How do I run a process without blocking user interface in my iphone app

I am accessing the photo library on the iphone and it takes a long time to import the pictures i select in my application, how do i run the process on a secondary thread , or what solution do i use to not block the user interface?
I did a full explanation with sample code using performSelectOnBackground or GCD here:
GCD, Threads, Program Flow and UI Updating
Here's the sample code portion of that post (minus his specific problems:
performSelectorInBackground Sample:
In this snippet, I have a button which invokes the long running work, a status label, and I added a slider to show I can move the slider while the bg work is done.
// on click of button
- (IBAction)doWork:(id)sender
{
[[self feedbackLabel] setText:#"Working ..."];
[[self doWorkButton] setEnabled:NO];
[self performSelectorInBackground:#selector(performLongRunningWork:) withObject:nil];
}
- (void)performLongRunningWork:(id)obj
{
// simulate 5 seconds of work
// I added a slider to the form - I can slide it back and forth during the 5 sec.
sleep(5);
[self performSelectorOnMainThread:#selector(workDone:) withObject:nil waitUntilDone:YES];
}
- (void)workDone:(id)obj
{
[[self feedbackLabel] setText:#"Done ..."];
[[self doWorkButton] setEnabled:YES];
}
GCD Sample:
// on click of button
- (IBAction)doWork:(id)sender
{
[[self feedbackLabel] setText:#"Working ..."];
[[self doWorkButton] setEnabled:NO];
// async queue for bg work
// main queue for updating ui on main thread
dispatch_queue_t queue = dispatch_queue_create("com.sample", 0);
dispatch_queue_t main = dispatch_get_main_queue();
// do the long running work in bg async queue
// within that, call to update UI on main thread.
dispatch_async(queue,
^{
[self performLongRunningWork];
dispatch_async(main, ^{ [self workDone]; });
});
}
- (void)performLongRunningWork
{
// simulate 5 seconds of work
// I added a slider to the form - I can slide it back and forth during the 5 sec.
sleep(5);
}
- (void)workDone
{
[[self feedbackLabel] setText:#"Done ..."];
[[self doWorkButton] setEnabled:YES];
}
Use an asynchronous connection. It won't block the UI while it does the fetching behind.
THIS helped me a lot when I had to do download images, lot of them.
One option is use performSelectorInBackground:withObject:

Add view overlay to iPhone app

I'm trying to do something like this:
- (void)sectionChanged:(id)sender {
[self.view addSubview:loadingView];
// Something slow
[loadingView removeFromSuperview];
}
where loadingView is a semi-transparent view with a UIActivityIndicatorView. However, it seems like added subview changes don't take effect until the end of this method, so the view is removed before it becomes visible. If I remove the removeFromSuperview statement, the view shows up properly after the slow processing is done and is never removed. Is there any way to get around this?
Run your slow process in a background thread:
- (void)startBackgroundTask {
[self.view addSubview:loadingView];
[NSThread detachNewThreadSelector:#selector(backgroundTask) toTarget:self withObject:nil];
}
- (void)backgroundTask {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// do the background task
[self performSelectorOnMainThread:#selector(backgroundTaskDone) withObject:nil waitUntilDone:NO];
[pool release];
}
- (void)backgroundTaskDone {
[loadingView removeFromSuperview];
}
Two potential problems spring to mind, both centred around how you've implemented the 'do something slow here' code.
First off, if it's locking up the main thread then it's possible the application's UI isn't being redrawn in time to display the view, i.e. Add Subview, tight loop/intensive processing tying up the main thread, then immediately after the view is removed.
Secondly if the 'something slow' is being done asynchronously, then the view is being removed while the slow processing is running.
One things for sure, your requirements are as follows:
Add a subview to display some kind of 'loading' view
Invoke a slow running piece of functionality
Once the slow running functionality completes, remove the 'loading' subview.
- (void)beginProcessing {
[self.view addSubview:loadingView];
[NSThread detachNewThreadSelector:#selector(process) toTarget:self withObject:nil];
}
- (void)process {
// Do all your processing here.
[self performSelectorOnMainThread:#selector(processingComplete) withObject:nil waitUntilDone:NO];
}
- (void)processingComplete {
[loadingView removeFromSuperview];
}
You could also achieve something similar with NSOperations.

view hierarchy refresh timing

I'm trying to add a progress meter, or other "I'm busy right now" notification to my view hierarchy right before doing some intense computation that will block the UI. My code looks some thing like:
//create view
[currentTopView addSubView:imBusyView];
//some initialization for the intense computation
[computation startComputing];
Unfortunately, my progress meter doesn't display until after the computation completes. It appears like the views aren't re-drawn until the run loop completes. I'm pretty sure that setNeedsDisplay and setNeedsLayout will also wait until the run loop completes.
How do I get the view to display immediately?
Redrawing only occurs when your code returns control to the run loop. So the easiest way would be for you to schedule the startComputing call with a zero delay. That way, it will be executed during the next run loop iteration (right after redrawing):
[computation performSelector:#selector(startComputing) withObject:nil afterDelay:0.0];
Be aware, though, that unless you do your computation in another thread you will not be able to update the UI during the computation.
If you are doing heavy calculations maybe spawning a new thread is a good idea.
Here I have an activityIndicator displayed and starts a large XMLParse operation in a background thread:
- (void) setSearchParser {
activityIndicator = [[ActivityIndicatorView alloc] initWithActivity];
[self.view addSubview:activityIndicator];
[NSThread detachNewThreadSelector:#selector(getSearchResults:) toTarget:self withObject:[searchParser retain]];
}
then the getSearchResults method:
- (void) getSearchResults: (SearchResultParser *) parser {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
[parser startParser];
[self performSelectorOnMainThread:#selector(searchResultsReady:) withObject:[parser data] waitUntilDone:NO];
[pool release];
}
So firstly make a new thread:
[NSThread detachNewThreadSelector:#selector(getSearchResults:) toTarget:self withObject:[searchParser retain]];
this means that all code inside the getSearchResults will be executed on a different thread. getSearchResults also get's passed a parameter of "searchParser" thats a large object that just needs startParse called on it to begin.
This is done in getSearchResults. When the [parser startParser] is done, the results is passed back to the main thread method called "searchResultsReady" and the threads autorelease pool is released.
All the time it took from my parser began to it had finished, a gray view covered the screen an an activityIndicator ran.
You can have the small activityIndicator class I wrote:
-(id) initWithActivity {
[self initWithFrame:[self bounds]];
[self setBackgroundColor:[UIColor blackColor]];
[self setAlpha:0.8];
activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
activityView.center = CGPointMake(160, 240);
[self addSubview:activityView ];
[activityView startAnimating];
return self;
}
- (void) dealloc {
[activityView release];
[super dealloc];
}
Hope it helps you out, even though threads seems a bit confusing, they can help to make the UI not freeze up, which is especially important on the iPhone.

resignFirstResponder for numberpad

There is a text field to enter PIN in my login form. When i press "login" button i call the following method:
* (IBAction) loginBeforeAction:(id) sender {
[pin resignFirstResponder];
[progressView performSelectorInBackground:#selector(startAnimating) withObject:nil];
[self login];
}
but i the number pad is not hiding before the control moves to login method. In effect, i am can see the progress view with the number pad up. Is there any way to hide the number pad first and then show progress view ? plz help
Yes, The UI won't update until you get through the runloop. Meaning, your UI doesn't update until your login method finishes. Also, no need to update your progressView in the background either.
So, just delay the calls:
[progressView performSelector:#selector(startAnimating) withObject:nil afterDelay:0.1];
[self performSelector:#selector(login) withObject:nil afterDelay:0.25];

Release NSObject and stop all method calling

So my menu calls a game with this piece of code:
game = [[Game alloc] init];
[self presentModalViewController:memoryTest animated:FALSE];
A UIViewController then appears with a countdown. The player can go back to the menu DURING the countdown. However when I try this, the countdown keeps running and eventually the game starts, even thought the UIViewController has been dismissed (therefore the UIView has disappeared) in the backToMenu method.
[self.parentViewController dismissModalViewControllerAnimated:FALSE];
I've tried to release the game object in the viewDidAppear method of the menu, but no luck. I thought of having a "quit" BOOL value, so the countdown can check wether or not the player has quit the game, but there must be a better way to release an object AND stop all method calls inside it.
Thanks for helping me out.
CountDown method:
- (void)countDown {
SoundEffect *sound = [[SoundEffect alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"tick" ofType: #"wav"]];
[sound playAndRelease];
if(self.countDownStep > 0) {
feedback.image = [UIImage imageNamed:[NSString stringWithFormat:#"countdown%d.png",self.countDownStep]];
feedback.hidden = FALSE;
[self performSelector:#selector(countDown) withObject:nil afterDelay:0.8];
}
else {
[self displayMessage:self.startMessage];
[self.game performSelector:#selector(start) withObject:nil afterDelay:0.8];
[self performSelector:#selector(hide) withObject:nil afterDelay:0.8];
}
self.countDownStep--;
}
In the viewcontroller's viewWillDisappear, check if the timer is running. If it is, then just invalidate it.
[timerObject invalidate] will stop the timer
How do you handle the countdown? It seems like you want to explicitly void that countdown in the viewWillDisappear method for the UIViewController you mentioned.
I was assuming you would use something like NSTimer. In fact, that might still not be a bad idea. NSTimer can handle re-running your countdown method every 0.8 seconds. If you decrement the value to 0, your countdown has expired. If your view disappears, invalidate the timer in viewWillDisappear.