Xcode Objective-C | iOS: delay function / NSTimer help? - objective-c

So I'm developing my first iOS application and I need help..
Simple program for now, I have about 9 buttons and when I press the first button, or any button, I just want the first button to highlight for 60 milliseconds, unhighlight, second button highlights, wait 60 milliseconds, unhighlight and so on for the rest of the buttons so it looks like a moving LED.
I've looked tried sleep/usleep but once that sleep duration is done it seems like it skips the highlight/unhighlight all together.
For example:
- (void) button_circleBusy:(id)sender{
firstButton.enabled = NO;
sleep(1);
firstButton.enabled = YES;
and so on for the rest of the buttons. It DOES delay, but it doesn't delay the "firstButton.enabled = NO;". I have a picture for each button's "disabled state" and I never see it.
Any help's appreciated! I've looked into NSTimer but I was unsure on how to implement it.
Thanks.
-Paul

sleep doesn't work because the display can only be updated after your main thread returns to the system. NSTimer is the way to go. To do this, you need to implement methods which will be called by the timer to change the buttons. An example:
- (void)button_circleBusy:(id)sender {
firstButton.enabled = NO;
// 60 milliseconds is .06 seconds
[NSTimer scheduledTimerWithTimeInterval:.06 target:self selector:#selector(goToSecondButton:) userInfo:nil repeats:NO];
}
- (void)goToSecondButton:(id)sender {
firstButton.enabled = YES;
secondButton.enabled = NO;
[NSTimer scheduledTimerWithTimeInterval:.06 target:self selector:#selector(goToThirdButton:) userInfo:nil repeats:NO];
}
...

int64_t delayInSeconds = 0.6;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
do something to the button(s)
});

Less code is better code.
[NSThread sleepForTimeInterval:0.06];
Swift:
Thread.sleep(forTimeInterval: 0.06)

A slightly less verbose way is to use the performSelector: withObject: afterDelay:
which sets up the NSTimer object for you and can be easily cancelled
So continuing with the previous example this would be
[self performSelector:#selector(goToSecondButton) withObject:nil afterDelay:.06];
More info in the doc

Try
NSDate *future = [NSDate dateWithTimeIntervalSinceNow: 0.06 ];
[NSThread sleepUntilDate:future];

I would like to add a bit the answer by Avner Barr. When using int64, it appears that when we surpass the 1.0 value, the function seems to delay differently. So I think at this point, we should use NSTimeInterval.
So, the final code is:
NSTimeInterval delayInSeconds = 0.05;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
//do your tasks here
});

[NSTimer scheduledTimerWithTimeInterval:.06 target:self selector:#selector(goToSecondButton:) userInfo:nil repeats:NO];
Is the best one to use. Using sleep(15); will cause the user unable to perform any other actions. With the following function, you would replace goToSecondButton with the appropriate selector or command, which can also be from the frameworks.

Related

Multiple NSTimers issue

I have sub-classed a component (it looks like circle and there is a picture inside it (NSView subclass)).
I want to change the picture every X time (to make it look like an animation).
When in my main view controller I drawing 1 sub-class like this everything works Fine, but when I adding more the picture changes much faster with each.
(I am firing the picture change using an NSTimer)
I am assuming that the issue happens because I have multiple NSTimers on the same queue
However I tried to use
NSTimer *uiTimer = [NSTimer timerWithTimeInterval:(1.0 / 5.0) target:self selector:#selector(changePicture) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:uiTimer forMode:NSRunLoopCommonModes];
It didn't solve the issue (I assume because it is still on the main thread)
So I came up with NSThread Solution
[NSThread detachNewThreadSelector:#selector(updateModel) toTarget:self withObject:nil];
- (void) updateModel
{
[NSTimer scheduledTimerWithTimeInterval:secondsBetweenUpdates
target:self
selector:#selector(changePicture)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] run];
}
It has a bit different behaviour (but still no luck as more sub-classes I have as faster picture changes).
My last shoot was this solution:
// Update the UI 5 times per second on the main queue
// Keep a strong reference to _timer in ARC
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, (1.0 / 5.0) * NSEC_PER_SEC, 0.25 * NSEC_PER_SEC);
dispatch_source_set_event_handler(_timer, ^{
[self changePicture];
});
// Start the timer
dispatch_resume(_timer);
I am not usually giving up , but I am trying to resolve it for 3 days already... And I think I need and advice of how to do this so it will work as intended.
If you are using GCD I would recommend you use dispatch_after, for example:
float delayTime = 0.2f;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(delayTime * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
[self changePicture];
});

Using a timer to play audio samples

What I want to do is play an audio sample once with this button. Right now it just loops forever until I manually pause the sample.
- (IBAction)sampleButtonAction:(id)sender {
[self.audioManager play];
}
So when 5 seconds have elapsed I need to make this call:
[self.audioManager pause];
I was thinking it would be simple to just create a timer that would go to 5 seconds then just repeat the process everytime I initiated the sample.
You may want to use dispatch_after which in your concrete example would look something like this:
double delayInSeconds = 5.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self.audioManager pause];
});
Basically what it does is, it executes the given block on the main queue with a delay of 5.0 seconds and pauses your sound in time.

Wait, before iterating next loop in objective c (ios)

I want to loop through an array of images, subsequently setting them as the new image of a uiimageview, but because it's a for loop it obviously does it so fast you cannot see a difference.
I would like to add a pause of about 0.2s to the loop before it goes on to the next iteration.
Is there a way to do this?
And more importantly is there an easier way to accomplish what I want to do with some kind of inbuilt framework? thanks!
Alternative to what you want but will work better-Use this method of NSTimer with repeats as YES
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats
use some method changeImage like
-(void) changeImage:(NSTimer*)theTimer
{
//code to get the image
NSUInteger idx=[yourArrayOfImages indexOfObjectIdenticalTo:yourImageView.image];
if(idx<[yourArrayOfImages count])
yourImageView.image=[yourArrayOfImages objectAtIndex:++id];
[yourImageView setNeedsDisplay];
}
Do not invalidate the timer. You could use any way to index like use an instance variable, pass it as userInfo.
Adding a delay to your for loop won't make a difference, because redrawing won't happen until you return control to the main event loop. Set up a timer to send you a message every so often to advance to the next image you want to show.
More context would be useful, but it sounds like you should look into NSTimer.
Below is the logic is how the Timer to be call a function recursively in a delay, and once the iteration of the array is done , disable the timer
NSArray *arrayOfImages;
timer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:#selector(timerFired:) userInfo:nil repeats:NO];
int i = 0
int count = [arrayOfImages count];
- (UIIMage *)timerFired:(NSTimer *)aTimer{
if (i == count)
{
timer == nil;
return nil;
}
i++;
return [arrayOfImages objectAtIndex:i];
}
You'd better use UIImageView property - animationImages.
#property(nonatomic,copy) NSArray *animationImages;
For example you have an UIImageView* animationImageView :
// pass NSArray of UIImage's in the order of desired appearance
animationImageView.animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:#"image1"],
[UIImage imageNamed:#"image2"],
[UIImage imageNamed:#"image3"],
[UIImage imageNamed:#"image4"],
[UIImage imageNamed:#"image5"], nil];
animationImageView.animationDuration = 1.5; // duration (seconds) of ONE animation cycle
animationImageView.animationRepeatCount = 3; // number of repeats (cycles)
animationImageView startAnimating];
In case you want to start some other action when animation finishes, use dispatch_after :
dispatch_time_t animTime = dispatch_time(DISPATCH_TIME_NOW, animationImageView.animationDuration * NSEC_PER_SEC);
dispatch_after (popTime, dispatch_get_main_queue(), ^(void){
// do your stuff
[self animationDidFinish];
});

Show NSTimeInterval continously

new to the iOS SDK and Objective-C and just have a quick question regarding the NSTimeInterval.
I have a really simple game, and I want to show for how long the game have been played (so that I can save away a best time).
Now, of course if I set the label's text with something like this:
NSTimeInterval elapsedTime = [startDate timeIntervalSinceNow];
theLabel.text = [NSString stringWithFormat:#"%.2f", -elapsedTime];
The text of the label will be how long time it will have passed since the game started, which is 0 seconds.
In what way can I have the elapsedTime object "running in the background" so that the player can se for how long he/she have played at all times?
Have a look as the NSTimer class to schedule a timer for a preset time and update the label in the callback.
-(void)viewDidLoad {
[super viewDidLoad];
// Add timer at 60 frames a second
timer = [NSTimer scheduledTimerWithInterval:1.0/60.0 target:self selector:#selector(targetMethod:) userInfo:nil repeats: YES];
}
-(void) targetMethod: NSTimer * theTimer {
... update label
}

Cocoa Image Gallery Window

I have a HUD panel in an app and I want to be able to take a set of images and show each image on the panel for a few seconds before displaying the next image. I'm very new to Cocoa and am having trouble implementing this so some pointers would be welcomed. Here's what I'm currently trying:
[newShots enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop)
{
//get url
NSURL *imageUrl = [[NSURL alloc] initWithString:[obj objectForKey:#"image_url"]];;
//get image from url
NSImage *image = [[NSImage alloc] initWithContentsOfURL:imageUrl];
//set it to shot panel
[shotPanelImageView setImage:image];
//clean up
[imageUrl release];
[image release];
//set needs refresh
[shotPanelImageView setNeedsDisplay:YES];
//sleep a few before moving to next
[NSThread sleepForTimeInterval:3.0];
}];
As you can see I'm just looping the info for each image, grabbing it via URL, setting it to the view, and then calling thread sleep for a few seconds before moving on. The issue is that the view will not redraw with the new image when it is assigned. I thought that setNeedsDisplay:YES would force a redraw but only the first image in the collection is ever displayed. I've put in NSLog()'s and debugged and I know for sure the enumeration is working correctly as I can see the new image information being set as it should.
Is there something I'm missing or is this a completely wrong way of going about solving this problem?
Thanks,
Craig
You are sleeping the main thread, which I'm pretty sure is not a good idea. I suggest that the Cocoa way to do what you want is to use a timer. In place of your code above:
[NSTimer scheduledTimerWithTimeInterval:3.0
target:self
selector:#selector(showNextShot:)
userInfo:nil
repeats:YES];
(The userInfo parameter allows you to pass along an arbitrary object that you want to use when the timer fires, so you could potentially use this to keep track of the current index as an NSNumber, but it would have to be wrapped in a mutable container object, because you can't set it later.)
Then put the code from your block into the method called by the timer. You'll need to make an instance variable for the current index.
- (void)showNextShot:(NSTimer *)timer {
if( currentShotIdx >= [newShots count] ){
[timer invalidate]; // Stop the timer
return; // Also call another cleanup method if needed
}
NSDictionary * obj = [newShots objectAtIndex:currentShotIdx];
// Your code...
currentShotIdx++;
}
To avoid the initial 3 sec delay caused by using a timer, you can call the same method your timer uses, right before you set it up:
[self showNextShot:nil]
[NSTimer scheduled...
Or could also schedule a non-repeating timer to fire as soon as possible (if you really wanted to use userInfo):
[NSTimer scheduledTimerWithTimeInterval:0.0
...
repeats:NO];
EDIT: I forgot about -initWithFireDate:interval:target:selector:userInfo:repeats:!
NSTimer *tim = [[NSTimer alloc] initWithFireDate:[NSDate date]
interval:3.0
target:self
selector:#selector(showNextShot:)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:tim
forMode:NSDefaultRunLoopMode];
[tim release];