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.
Related
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];
});
I've written a method, sortAndSlideHandCards(), that moves 6 UIButtons. Each UIButton is moved to the same position. This is done via a for-each loop and the animateWithDuration method being called on each UIButton.
This method is called for a number of players at the same time. Currently the behaviour results in UIButtons from each player moving but only one at a time. No more than one UIButton can move at any time, as if each animation is waiting for whatever animation that is currently running to stop before attempting it's own animation, essentially the code is executed sequentially for each player/UIButton. I hoped threading would help me fix this.
when I added the threading code:
backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
sortAndSlideHandCardsGroup = dispatch_group_create();
for(Player* player in _playersArray) {
dispatch_group_async(sortAndSlideHandCardsGroup, backgroundQueue, ^(void) {
[player sortAndSlideHandCards];
});
dispatch_group_wait(sortAndSlideHandCardsGroup,DISPATCH_TIME_FOREVER);
I found that only the first UIButton animation is triggered for each player and that the code gets held up in the runloop "while" because "_animationEnd" never gets set as it would appear the second animation never gets going.
I can see the method launching in its own thread
- (void) sortAndSlideHandCards {
NSLog(#"PLAYER:sortAndSlideHandCards");
CGPoint newCenter;
Card* tempCard = nil;
int count = 1;
float duration = 0.2 / _speedMultiplyer;
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
for(Card *card in _handCards) { //move cards in hand to one postion in hand
if(count == 1) {
tempCard = [[Card alloc] init:_screenWidth:_screenHeight :[card getNumber] :[card getCardWeight] :[card getSuit] :[card getIsSpecial]];
[tempCard setImageSrc: _playerNumber :!_isPlayerOnPhone :count : true :_view: _isAI: [_handCards count]];
newCenter = [tempCard getButton].center;
}
_animationStillRunning = true;
if(![[DealViewController getCardsInPlayArray] containsObject:card] ) {
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionLayoutSubviews animations:^{[card getButton].center = newCenter;} completion:^(BOOL finished){[self animationEnd];}];
while (_animationStillRunning){ //endAnimation will set _animationStillRunning to false when called
//stuck in here after first UIButton when threading code is in play
[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
} //endAnimation will set _animationStillRunning to false when called
}
count++;
}
}
When i comment out the threading code each UIButton (Card) will animate one after another.
With the threading code is in play the first UIButton will animate but during the second run through the for-loop, the code will be stuck in the while-loop, waiting for the animation to end. I'm guessing the second animation doesn't even start.
I also tried this for the threading code:
[player performSelectorInBackground:#selector(sortAndSlideHandCards) withObject:nil];
Same outcome
Anyone have any ideas why animateWithDuration doesn't like getting called in a loop when in a thread other than the main one?
You should just be able to kick off the animations you want from whatever UI action is performed. The UIView animateWith... methods return immediately, so you don't need to worry about waiting for them to complete.
If you have an unknown number of animations to kick off sequentially, use the delay parameter. Pseudocode:
NSTimeInterval delay = 0;
NSTimeInterval duration = 0.25;
for (UIView *view in viewsToAnimate)
{
[UIView animateWithDuration:duration delay:delay animations:^{ ... animations ...}];
delay += duration;
}
This will increase the delay for each successive animation, so it starts at the end of the previous one.
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];
});
I am using Xcode to create a Cocoa app for Mac OSX written in Objective-C.
Here is my code:
-(IBAction)clickToLaunchAppButtonClicked:(id)sender; {
//I want to hide the click to load app button
[clickToLoadButton setEnabled:NO];
[clickToLoadButton setHidden:YES];
//***I want to make the app wait for 1 second (This is where I'm stuck!)***
//I want to show the loading label
[loadingLabel setEnabled:YES];
[loadingLabel setHidden:NO];
//I want to show the loading progress bar and initialize it
[loadingProgressBar setHidden:NO];
[loadingProgressBar startAnimation:self];
//***I want to make the app wait for 3 seconds (Again, I don't know how to do this!)***
//I want to stop the loading progress bar animation
[loadingProgressBar stopAnimation:self];
//I want to hide the loading progress bar and loading label
[loadingLabel setHidden:YES];
[loadingLabel setEnabled:NO];
[loadingLabel setHidden:YES];
}
How can I make the app wait/pause for a few seconds? I tried the wait(), delay() or pause() functions, but they freeze the app, which is not what I want. Should I use an NSTimer? If so, please give me a simple way to implement it.
I recommend using the new Grand Central Dispatch API instead.
For example:
-(IBAction)clickToLaunchAppButtonClicked:(id)sender {
//I want to hide the click to load app button
[clickToLoadButton setEnabled:NO];
[clickToLoadButton setHidden:YES];
// wait one second
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^{
//I want to show the loading label
[loadingLabel setEnabled:YES];
[loadingLabel setHidden:NO];
//I want to show the loading progress bar and initialize it
[loadingProgressBar setHidden:NO];
[loadingProgressBar startAnimation:self];
// execute the rest after 3 seconds
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 3.0 * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^{
//I want to stop the loading progress bar animation
[loadingProgressBar stopAnimation:self];
//I want to hide the loading progress bar and loading label
[loadingLabel setHidden:YES];
[loadingLabel setEnabled:NO];
[loadingLabel setHidden:YES];
});
});
}
There are three advantages:
It's less code, and cleaner. You don't need to define extra methods and all your code is in one place.
The GCD api is increadibly fast, though in your example it probably doesn't matter much, sometimes this is important, especially on an iPhone where "fast" also means "longer battery life"
The code to be executed shares the scope with the code that calls it. Any variables in the outer code, are available in the code to be executed later
You can place the code you want to run on the 3 second delay in another method (delayedLoad) and use performSelector: withObject: afterDelay:
[self performSelector:#selector(delayedLoad) withObject:nil afterDelay:3.0];
EDIT - spelling it out more in response to your comment
to create the other method, include this code in the same .m file (AppDelegate.m or similar)
-(void)delayedLoad
{
//I want to stop the loading progress bar animation
[loadingProgressBar stopAnimation:self];
//I want to hide the loading progress bar and loading label
[loadingLabel setHidden:YES];
[loadingLabel setEnabled:NO];
[loadingLabel setHidden:YES];
}
And your existing method becomes
-(IBAction)clickToLaunchAppButtonClicked:(id)sender; {
//I want to hide the click to load app button
[clickToLoadButton setEnabled:NO];
[clickToLoadButton setHidden:YES];
//***I want to make the app wait for 1 second (This is where I'm stuck!)***
//I want to show the loading label
[loadingLabel setEnabled:YES];
[loadingLabel setHidden:NO];
//I want to show the loading progress bar and initialize it
[loadingProgressBar setHidden:NO];
[loadingProgressBar startAnimation:self];
[self performSelector:#selector(delayedLoad) withObject:nil afterDelay:3.0];
}
This is how you implement the 3 second delay. Hopefully you can extend this to implement the 1 second delay
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.