Loop Until Condition Reached, iPhone - iphone

I have a problem here...
After a button is pressed I want a loop to run until a condition is reached:
- (IBAction)buttonclick1 ...
if ((value2ForIf - valueForIf) >= 3) { ...
I would like a loop to run until
((value2ForIf - valueForIf) >= 3)
and then execute the code associated with the IF statement.
What I am aiming to achieve is the program to keep checking if the above statement is true, before continuing with the code. On top of this there is an else statement beneath the IF, though I don't know if this will affect a loop.
I am unsure of the format of the loop required here and everything I have tried has caused errors. Any help would be greatly appreciated.
Stu

- (IBAction)buttonclick1 ...
{
//You may also want to consider adding a visual cue that work is being done if it might
//take a while until the condition that you're testing becomes valid.
//If so, uncomment and implement the following:
/*
//Adds a progress view, note that it must be declared outside this method, to be able to
//access it later, in order for it to be removed
progView = [[MyProgressView alloc] initWithFrame: CGRectMake(...)];
[self.view addSubview: progView];
[progView release];
//Disables the button to prevent further touches until the condition is met,
//and makes it a bit transparent, to visually indicate its disabled state
thisButton.enabled = NO;
thisButton.alpha = 0.5;
*/
//Starts a timer to perform the verification
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval: 0.2
target: self
selector: #selector(buttonAction:)
userInfo: nil
repeats: YES];
}
- (void)buttonAction: (NSTimer *) timer
{
if ((value2ForIf - valueForIf) >= 3)
{
//If the condition is met, the timer is invalidated, in order not to fire again
[timer invalidate];
//If you considered adding a visual cue, now it's time to remove it
/*
//Remove the progress view
[progView removeFromSuperview];
//Enable the button and make it opaque, to signal that
//it's again ready to be touched
thisButton.enabled = YES;
thisButton.alpha = 1.0;
*/
//The rest of your code here:
}
}

Rather than run a tight loop, which would block your app's execution unless run on another thread, you could use an NSTimer to call a method at a time interval of your choosing and check the condition in that method. If the condition is satisfied, you can invalidate the timer and continue.

From what you stated, what you want is a while loop
while( (value2ForIf - valueForIf) < 3 ) { ...Code Here... }
This will run the code in the braces as long as the difference in values is less than 3, meaning it will run until their difference is 3 or greater. But as Jasarien said. This is a bad idea since you will be blocking your program. If the values are being updated by the code itself that is fine. But if they are being updated by some UI from the user, your while loop will block the UI and not allow the user to input anything.

Related

return from an update text field asynchronously - iphone

I'm wondering the best approach to take for this.
The example app is:- I have a text field and button. click the button, initiates a task to update the text field. But the text field needs to be updated on a timer (or in background), say every 1 sec, but I only need the timer to run for say 5 secs, populating a random piece of text for example.
This should give the impression that the text box is displaying random words every sec, then after the 5 secs has completed, the timer can stop and the last value remains in the text box.
But I also want to detect the stop event and then pick up the value in the text field and perform another action.
Finally the question :-
Should I use Timer events, or operations and queues ? Not sure which approach is best.
Yes, use a timer in this way, repeats:YES is what you need:
[NSTimer scheduledTimerWithTimeInterval:1 target:self
selector:#selector(ChangeText) userInfo:nil repeats:YES];
- (void) ChangeText
{
_textfield.text = [nsstring stringwithformat:#"%#%#", _textfield.text, _yourstring];
}
Yes , above answer is correct, just need some small changes according to your question...
count = 0;
[NSTimer scheduledTimerWithTimeInterval:1 target:self
selector:#selector(ChangeText) userInfo:nil repeats:YES];
- (void) ChangeText
{
if (count < 5)
{
_textfield.text = [nsstring stringwithformat:#"%#%#", _textfield.text, _yourstring];
}
else
{
//Start your next event here;
}
NSTimer able to work only on main thread, then you don't need to expect that it will fire if it launched in background.
Also I think it's basically question of good architecture - don't you want try to handle events to update your textfield? For example, handle event when some task is finished or displayed value changed, then use delegate/block/NSNotification to receive event and update UI.

objective-c: Animate button before timer ends

I'm working on a very simple iPhone game that involves choosing the right colored button as many times in a row based on a randomized voice prompt. I have it set up so that if the button is one color and gets clicked, it always goes to a hard-coded color every time (e.g. if you click red, it always turns blue). The color change method is set up in an IBOutlet. I have a timer set up in a while loop, and when the timer ends it checks if the player made the right selection. The problem is that the button color change does not occur until after the timer runs out, and this causes a problem with the method used to check the correct answer. Is there a way to make this color change happen instantly? From what I've searched I know it has something to do with storyboard actions not occurring until after code executes, but I haven't found anything with using a timer. Here is a section of the method that calls the timer if the answer is correct:
BOOL rightChoice = true;
int colorNum;
NSDate *startTime;
NSTimeInterval elapsed;
colorNum = [self randomizeNum:middle];
[self setTextLabel:colorNum];
while (rightChoice){
elapsed = 0.0;
startTime = [NSDate date];
while (elapsed < 2.0){
elapsed = [startTime timeIntervalSinceNow] * -1.0;
NSLog(#"elapsed time%f", elapsed);
}
rightChoice = [self correctChoice:middleStatus :colorNum];
colorNum = [self randomizeNum:middle];
}
One of two things stood out
You're using a while loop as a timer, don't do this - the operation is synchronous.
If this is run on the main thread, and you code doesn't return, your UI will update. The mantra goes: 'when you're not returning you're blocking.'
Cocoa has NSTimer which runs asynchronously - it is ideal here.
So let's get to grips with NSTimer (alternatively you can use GCD and save a queue to an ivar, but NSTimer seems the right way to go).
Make an ivar called timer_:
// Top of the .m file or in the .h
#interface ViewController () {
NSTimer *timer_;
}
#end
Make some start and stop functions. How you call these is up to you.
- (void)startTimer {
// If there's an existing timer, let's cancel it
if (timer_)
[timer_ invalidate];
// Start the timer
timer_ = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:#selector(onTimerFinish:)
userInfo:nil
repeats:NO];
}
- (void)onTimerFinish:(id)sender {
NSLog(#"Timer finished!");
// Clean up the timer
[timer_ invalidate];
timer_ = nil;
}
- (void)stopTimer {
if (!timer_)
return;
// Clean up the timer
[timer_ invalidate];
timer_ = nil;
}
And now
Put your timer test code in the onTimerFinish function.
Make an ivar that stores the current choice. Update this ivar when a choice is made and make the relevant changes to the UI. Call stopTimer if the stop condition is met.
In the onTimerFinished you can conditionally call and startTimer again if you desire.
Hope this helps!

check for time duration during loop

I am using MBProgressHUD to display a progress indicator. The delegate that is called when the indicator is shown is:
- (void)myTask {
while (self.show_progress == NO){
}
}
basically when it goes out of the loop it dismisses the indicator. Now the issue is that I would like do something more in this method. I would like to check for how long has the indicator been spinning for, if it has been more than 5 seconds then I would like to re-load the request. The question is how do I check for this?
This is just to prevent the apps waiting for an infinite amount of time just in case the response never got back or got stuck somewhere.
I'm not familiar with MBProgressHUD , but on general terms you could do the following:
When you first make the request do:
NSDate *startTime = [NSDate date];
Then whenever you want to check how long has it been:
NSTimeInterval timePassed = -[startTime timeIntervalSinceNow];
timePassed will have the value, in seconds, of how long has it been since you made your request. May be you should consider using NSTimer for this: Schedule a timer that will fire 5 seconds after you performed your request, if it triggers cancel the request but if you receive a response before the timer triggers invalidate the timer.
If I understand correctly, your code just waits until the property show_progress becomes NO. I don't know why your code does this, it seems a little inelegant. If you want to keep it this way, at least use a condition lock to prevent the 100% CPU usage:
Prepare the condition lock like this:
NSConditionLock *progressLock = [[NSConditionLock alloc] initWithCondition:0];
In your second thread, once your loading stuff or whatever finishes, change the condition like this:
[progressLock lock];
[progressLock unlockWithCondition:1];
And in your method, do this:
- (void)myTask {
NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:5];
if ([progressLock lockWhenCondition: 1 beforeDate:timeoutDate) {
// we aquired the lock, processing has finished
[progressLock unlock];
} else {
// we didn't aquire the lock because the 5 seconds have passed
// reload the request or do whatever you want to do
}
}
This method waits 5 seconds, and then times out. It uses no CPU in those 5 seconds, because it waits for a signal at the lockWhenCondition:beforeDate: call.
The way I've gone about similar situations is to set up a timer. The basic concept would be to start the timer when the indicator starts spinning. Then invalidate it when the indicator stops. Else if it goes on for 5 seconds, execute your method.
So in your header, you'll want
NSTimer *myTimer;
then in the implementation, when you start the indicator spinning,
[indicator startAnimating];
if (myTimer != nil) {
[myTimer invalidate];
myTimer = nil;
}
myTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(reloadRequest) serInfo:nil repeats:NO];
when you stop the indicator from spinning, send [myTimer invalidate]; and myTimer = nil;. If the specified time is reached beforehand, reload the request in your reloadRequest method

How can I create an NSTimer that repeats twice and then stops

I'd like to create an NSTimer that repeats twice then stops and invalidates itself. I'd rather not use a loop if that's possible. Any thoughts on how I could do this?
Create a static int inside your timer delegate function that is initialized to 0.
Increment it each time the delegate is called.
When the counter reaches the value you wish invalidate the timer.
This is something your timer's target should handle, not something the timer itself should handle. You can either install a repeating timer and have the target invalidate it the second time it fires, or you can install a one-shot timer, reinstall it after the first time it fires, and then not set it up again the second time.
Basically, you need a state machine state variable that can be accessed both from the routine that initializes the timer, and from the timer's target.
Set the state variable to allow the first call to the timer task to restart the timer, but in that call also set that state variable so that subsequent calls do not restart.
Note that this kind of state variable can be used for any number of timer task repetitions, by simply decrementing it.
State machines are pretty much how all (synchronous) digital chips and logic works.
I very much disagree with the Jeremy that this is something that the target should handle. In fact I disagree so much that I have created my own Timer class, based on NSTimer, that you can configure in detail.
- (void) doSomething: (Timer*) timer
{
NSLog(#"This is iteration %d", timer.currentIteration);
}
- (void) startDoingSomething
{
Timer* timer = [Timer new];
timer.interval = 5.0; // Fire every 5 seconds
timer.delay = 2.5; // Start firing after 2.5 seconds
timer.iterations = 3; // Only fire three times
timer.target = self;
timer.selector = #selector(doSomething:);
[timer schedule];
// Don't forget to release timer somewhere - the above is just an example
}
See http://github.com/st3fan/ios-utils
One solution might look similar to this.
Launching the timer
[NSTimer scheduledTimerWithTimeInterval:3 target:self selector:#selector(timerMethod:) userInfo:nil repeats:YES];
Handling the timer and repetitions
int repetitions = 2; //EDIT: remove static declaration (see below)
- (void)timerMethod:(NSTimer*)theTimer{
NSLog(#"Timer fired");
repetitions--;
if(repetitions == 0){
[theTimer invalidate];
NSLog(#"Timer stopped");
}
}
EDIT:
I removed the static modifier above to make a more generic example. The original intent of the static was to persist the timer across objects of similar type, a request that the OP did not make.

connectionDidFinishLoading - how to force update UIView?

I am able to download a ZIP file from the internet. Post processing is done in connectionDidFinishLoading and works OK except no UIView elements are updated. For example, I set statusUpdate.text = #"Uncompressing file" but that change does not appear until after connectionDidFinishLoading has completed. Similarly, the UIProgressView and UIActivityIndicatorView objects are not updated until this method ends.
Is there any way to force an update of the UIView from within this method? I tried setting [self.view setNeedsDisplay] but that didn't work. It appears to be running in the main thread. All other commands here work just fine - the only problem is updating the UI.
Thanks!
Update: here is the code that is NOT updating the UIVIEW:
-(void)viewWillAppear:(BOOL)animated {
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(processUpdate:) userInfo:nil repeats:YES];
downloadComplete = NO;
statusText.text = #"";
}
-(void)processUpdate:(NSTimer *)theTimer {
if (! downloadComplete) {
return;
}
[timer invalidate];
statusText.text = #"Processing update file.";
progress.progress = 0.0;
totalFiles = [newFiles count];
for (id fileName in newFiles) {
count++;
progress.progress = (float)count / (float)totalFiles;
// ... process code goes here ...
}
}
At then end of processUpdate, I set downloadComplete = YES. This builds & runs without errors and works as intended except nothing updates in the UIVIEW until after processUpdate completes, then everything updates at once.
Thanks for your help so far!
As Niels said, you must return control to the run loop if you want to see views update. But don't start detaching new threads unless you really need to. I recommend this approach:
- (void)connectionDidFinishLoading:(NSConnection *)connection {
statusUpdate.text = #"Uncompressing file";
[self performSelector:#selector(doUncompress) withObject:nil afterDelay:0];
}
- (void)doUncompress {
// Do work in 100 ms chunks
BOOL isFinished = NO;
NSDate *breakTime = [NSDate dateWithTimeIntervalSinceNow:100];
while (!isFinished && [breakTime timeIntervalSinceNow] > 0) {
// do some work
}
if (! isFinished) {
statusUpdate.text = // here you could update with % complete
// better yet, update a progress bar
[self performSelector:#selector(doUncompress) withObject:nil afterDelay:0];
} else {
statusUpdate.text = #"Done!";
// clean up
}
}
The basic idea is that you do work in small chunks. You return from your method to allow the run loop to execute periodically. The calls to performSelector: will ensure that control eventually comes back to your object.
Note that a risk of doing this is that a user could press a button or interact with the UI in some way that you might not expect. It may be helpful to call UIApplication's beginIgnoringInteractionEvents to ignore input while you're working... unless you want to be really nice and offer a cancel button that sets a flag that you check in your doUncompress method...
You could also try running the run loop yourself, calling [[NSRunLoop currentRunLoop] runUntilDate:...] every so often, but I've never tried that in my own code.
While you are in connectionDidFinishLoading nothing else happens in the application run loop. Control needs to be passed back to the run loop so it can orchestrate the UI updating.
Just flag the data transfer as complete and the views for updating. Defer any heavy processing of the downloaded data to it's own thread.
The application will call your views back letting them refresh their contents later in the run loop. Implement drawRect on your own custom views as appropriate.
If you're receiving connectionDidFinishLoading in the main thread, you're out of luck. Unless you return from this method, nothing will be refreshed in the UI.
On the other hand, if you run the connection in a separate thread, then you can safely update the UI using the following code:
UIProgressView *prog = ... <your progress view reference> ...
[prog performSelectorOnMainThread:#selector(setProgress:)
withObject:[NSNumber numberWithFloat:0.5f]
waitUntilDone:NO];
Be careful not to update the UI from a non-main thread - always use the performSelectorOnMainThread method!
Do exactly what you're doing with the timer, just dispatch your processing code to a new thread with ConnectionDidFinish:. Timers can update the UI since they're run from the main thread.
The problem turned out to that the UI isn't updated in a for() loop. See the answer in this thread for a simple solution!