How to delay while retaining a responsive GUI on Cocoa Touch? - iphone

Basically, I have an array of buttons I want to iterate and highlight (among other things) one after another, with a delay in-between. Seems like an easy task, but I can't seem to manage to get it to work cleanly while still being responsive.
I started out with this:
for MyButton *button in buttons {
[button highlight];
[button doStuff];
usleep(800000); // Wait 800 milliseconds.
}
But it is unresponsive, so I tried using the run loop instead.
void delayWithRunLoop(NSTimeInterval interval)
{
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:interval];
[[NSRunLoop currentRunLoop] runUntilDate:date];
}
for MyButton *button in buttons {
[button highlight];
[button doStuff];
delayWithRunLoop(0.8); // Wait 800 milliseconds.
}
However, it is also unresponsive.
Is there any reasonable way to do this? It seems cumbersome to use threads or NSTimers.

NSTimer will be perfect for this task.
The timer's action will fire ever x seconds, where x is what you specify.
The salient point is that this doesn't block the thread it runs on. As Peter said in the comments for this answer, I was incorrect in saying the timer waits on a separate thread. See the link in the comment for details.

Nevermind, Jasarien was right, NSTimer is perfectly suitable.
- (void)tapButtons:(NSArray *)buttons
{
const NSTimeInterval waitInterval = 0.5; // Wait 500 milliseconds between each button.
NSTimeInterval nextInterval = waitInterval;
for (MyButton *button in buttons) {
[NSTimer scheduledTimerWithTimeInterval:nextInterval
target:self
selector:#selector(tapButtonForTimer:)
userInfo:button
repeats:NO];
nextInterval += waitInterval;
}
}
- (void)tapButtonForTimer:(NSTimer *)timer
{
MyButton *button = [timer userInfo];
[button highlight];
[button doStuff];
}

Related

Scroll UISlider Automatically

So currently I have a UISlider in a UIViewcontroller that is meant to start animations within subviews when the user slides.. Basically when the user slides I have this battery with a filling in it that fills the empty battery image with a bar to indicate power within a cell, and the user can slide to see the energy the battery has at certain times of the day.
At the moment, when the View loads I would like the UISlider to AUTOMATICALLY start sliding from the beginning of the slider and scroll to the end within, lets say 5 seconds.
I implemented a loop that cycles through all the values of the uislider using this loop
for (int i = 0; i < [anObject count] - 2; i++)
{
sleep(.25);
NSUInteger index = (NSUInteger)(slider.value + 0.5); // Round the number.
[slider setValue:index animated:YES];
}
[anObject count] - 2 is equal to 62 at this time of day but will change and increment every 15 seconds because I'm fetching data from a server.
But that aside, why doesn't this work? The loop?
EDIT:
So heres what I did with NSTIMER
[NSTimer timerWithTimeInterval:0.25 target:self selector:#selector(animateSlider) userInfo:nil repeats:NO];
and animateSlider looks like this:
- (void)animateSlider:(NSTimer *)timer
{
NSLog(#"Animating");
NSUInteger index = (NSUInteger)(slider.value + 0.5); // Round the number.
[slider setValue:index animated:YES];
}
But no luck... Why isn't NSTimer "firing"..... I remmeber vaguely there was a method that FIRES an nstimer method but not sure if that's needed...
EDIT:
Ahh it does need "Fire"....
NSTimer *timer = [NSTimer timerWithTimeInterval:0.25 target:self selector:#selector(animateSlider) userInfo:nil repeats:NO];
[timer fire];
But for some reason it only fires once.... Any ideas ?
"for some reason it only fires once..."
If you changed the NSTimer set up to this:
NSTimer *timer =
[NSTimer scheduledTimerWithTimeInterval:0.25
target:self
selector:#selector(animateSlider:)
userInfo:nil
repeats:YES];
This would schedule the timer on the current run loop immediately.
And since the "repeats" parameter is "YES", you'd then repeat the timer every quarter second, until you invalidate the timer (which you should do when the ending condition is reached, like when the slider reaches its destination).
P.S. You'd need to change the selector method declaration of your timer's target slightly. According to Apple's documentation, "The selector must correspond to a method that returns void and takes a single argument. The timer passes itself as the argument to this method."
So declare "animateSlider" like this instead:
- (void)animateSlider: (NSTimer *) theTimer;

While button is being pressed

How do i set up a button (IBAction and UIButton attached) to continue to run the IBAction or a function while the button is being pressed, running a functioning continuously until the button is let up.
Should i attach a value changed receiver?
Simple question, but I can't find the answer.
[myButton addTarget:self action:#selector(buttonIsDown) forControlEvents:UIControlEventTouchDown];
[myButton addTarget:self action:#selector(buttonWasReleased) forControlEvents:UIControlEventTouchUpInside];
- (void)buttonIsDown
{
//myTimer should be declared in your header file so it can be used in both of these actions.
NSTimer *myTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(myRepeatingAction) userInfo:nil repeats:YES];
}
- (void)buttonWasReleased
{
[myTimer invalidate];
myTimer = nil;
}
Add a dispatch source iVar to your controller...
dispatch_source_t _timer;
Then, in your touchDown action, create the timer that fires every so many seconds. You will do your repeating work in there.
If all your work happens in UI, then set queue to be
dispatch_queue_t queue = dispatch_get_main_queue();
and then the timer will run on the main thread.
- (IBAction)touchDown:(id)sender {
if (!_timer) {
dispatch_queue_t queue = dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// This is the number of seconds between each firing of the timer
float timeoutInSeconds = 0.25;
dispatch_source_set_timer(
_timer,
dispatch_time(DISPATCH_TIME_NOW, timeoutInSeconds * NSEC_PER_SEC),
timeoutInSeconds * NSEC_PER_SEC,
0.10 * NSEC_PER_SEC);
dispatch_source_set_event_handler(_timer, ^{
// ***** LOOK HERE *****
// This block will execute every time the timer fires.
// Do any non-UI related work here so as not to block the main thread
dispatch_async(dispatch_get_main_queue(), ^{
// Do UI work on main thread
NSLog(#"Look, Mom, I'm doing some work");
});
});
}
dispatch_resume(_timer);
}
Now, make sure to register for both touch-up-inside and touch-up-outside
- (IBAction)touchUp:(id)sender {
if (_timer) {
dispatch_suspend(_timer);
}
}
Make sure you destroy the timer
- (void)dealloc {
if (_timer) {
dispatch_source_cancel(_timer);
dispatch_release(_timer);
_timer = NULL;
}
}
UIButton should call a starting method with touchDown event and call ending method with touchUpInside event

Boost-button delay

I am making a game with a boost button. Of course, just leaving it as enabled would allow the player to tap it constantly. So, I need a 20 second delay before it is possible to push the button again. Also, would it be possible to show this progression on the button, preferably on the button itself?
In the future please try to show what you have tried to solve your problem. However, since you are new I'm going to let it slide once!
This code uses a NSTimer that is called once per second. It will fire what ever code you specify for "Boost". It will also disable user interaction on your button until it has been 20 seconds from when the button was pressed at which point it will allow the user to press the button again, and finally, this code displays how many seconds remain until "Boost" can be used again on the titleLabel property of your button.
- (IBAction)buttonPressed:(id)sender
{
myTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(activateBoost) userInfo:nil repeats:YES];
}
- (void)activateBoost
{
if (myButton.userInteractionEnabled == YES) {
//Put your Boost code here!
}
if ([myButton.titleLabel.text intValue] == 0) {
[myTimer invalidate];
[myButton setTitle:#"20" forState:UIControlStateNormal];
myButton.userInteractionEnabled = YES;
}else{
myButton.userInteractionEnabled = NO;
int currentTime = [myButton.titleLabel.text intValue];
int newTime = currentTime - 1;
myButton.titleLabel.text = [NSString stringWithFormat:#"%d",newTime];
}
}
In order for the above code to work, you will need the declare a NSTimer named "myTimer" and a UIButton "myButton". You will also need to set the buttons initial text to "20".

Time elapsed button in the iphone sdk

How do you create a time elapsed button in objective-c iphone SDK. To be a little more specific, this button will show in text how much time has elapsed since you've been holding the button. So for the time to elapse you must still have a finger on the button, not letting go. Once you let go the timer should restart. Note: For the iphone, not mac.
Use these two methods for buttons events. touchDown is called when you press the button and touchUp will be called when you lift your finger from the button. Calculate the time difference between these two methods. Also you can start timer in touchDown and stop/restart it in touchUp.
//connect this action with Touch up inside
- (IBAction)touchUp:(id)sender {
NSLog(#"up");
}
//connect this to tocuh down
- (IBAction)touchDown:(id)sender{
NSLog(#"down");
}
first, set an int variable at your header file
#property int timerCount;
#property (nonatomic, strong)NSTimer *yourTimer;
dont forget to synthesize it at the implementation file
(if you're still on lower SDK, you can change "strong" to "retain")
and then make the button and it's function
UIButton *yourButton = [[UIButton alloc]initWithFrame:CGRectMake(0, 0, 100, 100)];
[yourButton setTag:1];
[yourButton setBackgroundColor:[UIColor redColor]];
[yourButton addTarget:self action:#selector(buttonHoldDown) forControlEvents:UIControlEventTouchDown];
[yourButton addTarget:self action:#selector(buttonRelease) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:yourButton];
this way, you've add a button at x:0 y:0 on your view with red color, containing two action target which is touch down and up inside
when you touch the button, the buttonHoldDown function is triggered, and when u release the button, the buttonRelease function is triggered
and then, fill the function
-(void)buttonHoldDown
{
yourTimer = [NSTimer timerWithTimeInterval:1 target:self selector:#selector(timerStart) userInfo:nil repeats:NO];
timerCount = 0;
}
-(void)buttonHoldUp
{
NSLog(#"the timer stops at %d seconds", timerCount);
timerCount = 0;
[yourTimer invalidate];
}
-(void)timerStart
{
timerCount++;
}
this way, when you touch the button, the program creates a timer and revalue the int timerCount to 0, which will be increased as the timer ticks in the "timerStart" function.
as you release the button, the function will track your current timerCount record and print it on the system, and then stop the timer

UILabel only displaying odd objects in NSArray

I have an int which defines the objectAtIndex, which gives it to the string, which then displays it in the UILabel, HOWEVER, when I modify the array (add a word to it in this case) when I have the UILabel go through the array it only displays the odd ones. I have done an NSLog and found out that it DOES PASS EVERY SINGLE THING to the string, but the Label isn't displaying every single one, it only does it the first time, then I modify the array, and it only does odd
edit: the code:
- (void) stuff {
NSArray *indivwords = [sentence componentsSeparatedByString:#" "];
count = [indivwords count]; //count=int
if (i < count) {
NSString *chunk = [indivwords objectAtIndex:i];
[word setText:chunk];
NSLog(chunk);
i++;
} else {
word.textColor = [UIColor greenColor];
[word setText:#"DONE"];
}
}
All of this is being controlled by an NSTimer, which sets it off based on a slider value. Also i is set to 0 in the IBAction
The IBAction code:
word.textColor = [UIColor whiteColor];
[timer invalidate];
i = 0;
speedd = (1/speed.value)*60;
timer = [NSTimer scheduledTimerWithTimeInterval:(speedd) target:self selector:#selector(stuff) userInfo:nil repeats:YES];
EDIT:
I fixed it! What I did was call this after i reached the count
-(void)stoptimer
{
[timer invalidate];
timer = [NSTimer scheduledTimerWithTimeInterval:(.01) target:self selector:#selector(empty) userInfo:nil repeats:YES];
}
empty is just a void with nothing in it, well, all I did was add a comment,
//don't look over here, nothing to see here, oh look at that
Somebody can close this if they want
You said the time interval of your NSTimer is controlled by a slider. Perhaps you are starting a new timer instance every time you change the slider value, so after a change you would have two timers running. The first one updates the label and increments i. The second one comes right after the first and finds i's value incremented, so it displays the next chunk instead of the current one.
Count the instances of your timer - NSLog the timer in the timer callback and compare the results - it may be that you're firing more than one.
My guess is that you have two different actions calling this method, when you think there should be one. I'd put a break point in there, and see if it stops where you think it should.