Please excuse my english, I'm not very fluent.
I have an application with 10 workouts, and i would like to give to each workout an amount of time, for ex :
1st workout : 30 seconds
2nd workout : 40 seconds
But for now, i only managed to give each workout the same amount of seconds for all workout..
This is what i used to work this :
#define EXERCISELENGTH 30
#define EXERCISELENGTH1 40
#define RESTLENGTH 10
- (void)tick
{
if (self.inCountdown && (countdown == 0)) {
self.inCountdown = NO;
self.inExercise = YES;
countdown = EXERCISELENGTH;
[self workoutUI];
[self updateUI];
workoutIndex++;
}
if (self.inExercise && (countdown == 0)) {
self.inExercise = NO;
self.inRest = YES;
countdown = RESTLENGTH;
[self restUI];
[self updateUI];
}
if (self.inRest && (countdown == 0)) {
self.inRest = NO;
self.inExercise = YES;
countdown = EXERCISELENGTH;
[self workoutUI];
[self updateUI];
workoutIndex++;
if (workoutIndex == 10) {
[self doneUI];
[self.timer invalidate];
return;
}
}
[self updateUI];
countdown--;
}
Any help would be appreciated.
Thank you
you have many options:
If you want to call a workout method every 30 seconds, you use this:
[NSTimer scheduledTimerWithTimeInterval:30.0
target:self
selector:#selector(workoutMethod)
userInfo:nil
repeats:YES];
If you want to call the workout method after 30 seconds, you use this:
[self performSelector:#selector(workoutMethod) withObject:nil afterDelay:30.0];
If you want to start a countdown timer after you start the workout method, you use this example : CountDown Timer ios tutorial?
If you want a countdown timer, you should set up a repeating NSTimer that triggers an action that updates the display every second or so. However, that action should calculate the time remaining using the system time. So, your code should:
get the current time and calculate the expected finish time according to the desired time period (20 sec, 30 sec, etc.)
start a repeating timer with a 1 sec interval
Your timer's action should:
get the current system time
compare to the expected finish time
update the display
invalidate itself if it has reached the finish time
What you don't want to do is to rely on the timer to trigger at exactly 1 second intervals. NSTimer's resolution is such that each 'tick' can be off by as much as 0.1 sec, which means that 30 ticks could be off by up to 3 seconds. Using the system time, e.g. CACurrentMediaTime(), will provide a much more accurate clock and avoid accumulation of error.
Related
I was wondering if there was a clean way to do a countdown timer with Grand Central Dispatch (then display for example in a UILabel) that's synchronized to the system clock... based on a reference date? — So if my reference date is an NSDate that's 20 minutes from now, I'd have a countdown displayed in seconds (don't worry about formatting) that's synced to the system clock.
Just doing a quick version of this skips seconds every once in a while in case the update method call doesn't arrive on schedule.
To me this seems like a pretty basic question, so I'm looking ideally for a basic/clean solution besides increasing the update interval to be 10x or something.
Also, the solution shouldn't use NSTimer.
I would use a recursive method like this:
- (void) updateTimer{
NSDate *now = [NSDate date];
NSTimeInterval secondsToDate = [now timeIntervalSinceDate:self.referenceDate];
_timerLabel.text = [NSString stringWithFormat:#"%.0f", secondsToDate];
if( secondsToDate < 0 ){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self updateTimer];
});
}else{
NSLog(#"Timer triggered!");
}
}
You just have to call it the first time, then it will update the countdown every 0.1 seconds.
I want to use NSTimer to increase the number which show on a label.
Here is my code:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.numberLabel = [[UILabel alloc]initWithFrame:CGRectMake(90, 90, 90, 30)];
[self.view addSubview:self.numberLabel];
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:#selector(refreshText) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop]addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)refreshText{
NSDate *beginDate = [NSDate date];
static NSInteger a = 0;
a ++;
self.numberLabel.text = [NSString stringWithFormat:#"%ld",a];
if (a == 1000) {
NSDate *endDate = [NSDate date];
NSTimeInterval durationTime = [endDate timeIntervalSinceDate:beginDate];
NSTimeInterval intervalTime = self.timer.timeInterval;
NSLog(#"durationTime = %f",durationTime);
NSLog(#"intervalTime = %f",intervalTime);
[self.timer invalidate];
self.timer = nil;
}
}
and the console showed:
then I changed the timer's timeInterval from 0.01 to 0.001,the console showed:
What confused me is that why the durationTime is not 0.0000056 when the timeInterval is 0.001.What's more,is there a min value for NSTimer's timeInterval we can set?
The time period of an NSTimer is a value of type NSTimeInterval, while this provides sub-millisecond precision that does not help you. From the start of the NSTimer documentation:
Timers work in conjunction with run loops. Run loops maintain strong references to their timers, so you don’t have to maintain your own strong reference to a timer after you have added it to a run loop.
To use a timer effectively, you should be aware of how run loops operate. See Threading Programming Guide for more information.
A timer is not a real-time mechanism. If a timer’s firing time occurs during a long run loop callout or while the run loop is in a mode that isn't monitoring the timer, the timer doesn't fire until the next time the run loop checks the timer. Therefore, the actual time at which a timer fires can be significantly later. See also Timer Tolerance.
So the minimum time interval for an NSTimer is tied to the the length of a run loop iteration. While internal optimisations, if they exist, could fire a timer as soon as it is set if the interval is really small in general the shortest period you'll get is dependent on the remaining execution time of the run loop iteration in which the timer is set, which is pretty much indeterminate for general purpose programming.
If you really need a high-resolution timer (see #bbum's comment on your question) then you'll need to research that topic - just search something like "high resolution timing macOS" as a starting point.
HTH
There is a better approach to your problem. Use CADisplayLink instead of NSTimer. CADisplayLink allows you to update a UI every time the screen refreshes - as quickly as possible. There is no point to updating the UI more often than the screen can refresh it, so NSTimer is not the best tool fast UI updates.
func beginUpdates() {
self.displayLink = CADisplayLink(target: self, selector: #selector(tick))
displaylink.add(to: .current, forMode: .defaultRunLoopMode)
}
func tick() {
// update label
}
func endUpdates(){
self.displayLink.invalidate()
self.displayLink = nil
}
I have created a drum sequencer using Objective C. I want the slider to control the tempo. At the moment, everything works, and the interval between each step is being controlled by:
while (self.running)
{
// sleep until the next step is due
[NSThread sleepUntilDate:time];
// update step
int step = self.step + 1;
// wrap around if we reached NUMSTEPS
if (step >= NUMSTEPS)
step = 0;
// store
self.step = step;
// time duration until next step
time = [time dateByAddingTimeInterval:0.2];
}
So the time between each step is 0.2 seconds. I have tried to implement a tempo slider like so in the view controller .m (the slider has a range of 0.3 to 1.0 so will output similar value to what is currently in time):
- (IBAction)sliderMoved:(UISlider *)sender
{
AppDelegate* app = [[UIApplication sharedApplication] delegate];
app.tempo = sender.value;
}
and by changing the line in the while(self.running) thread to:
time = [time dateByAddingTimeInterval: (NSTimeInterval) _tempo];
However, this causes the time between steps to be far too short (tempo is crazy fast) and when any control in the app is touched, it crashes.
I wonder if I need to set up a function like this, but I'm not sure what would go inside to enable the tempo slider to work:
- (void)setTempo:(float)tempo
{
}
I have tried to be as clear as I can, if anyone can help me I'd be very grateful, thanks in advance
-(void) startDrumTick{
[self.myDrumTimer invalidate]; // stop any current existing timer
// perform the call to the method 'drumMethodOperation:'
// every 0.2 sec. NB: drumMethodOperation will run on main thread.
// this means that if you expect to do long-blocking operation,
// you will need to move that op to an async thread, in order to avoid
// the UI blocking
self.myDrumTimer = [NSTimer scheduledTimerWithTimeInterval:0.2
target:self
selector:#selector(drumMethodOperation:)
userInfo:nil
repeats:YES];
}
-(void)drumMethodOperation:(id)sender
{
// update step
int step = self.step + 1;
// wrap around if we reached NUMSTEPS
if (step >= NUMSTEPS)
step = 0;
// store
self.step = step;
// any other needed operation to run every 0.2 secs
}
Below an example for an async thread management using GCD
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
// Long blocking operation ( DO NOT PERFORM ANY UI OPERATION, like changing a text label, setting an image to an UIImageView, etc. )
[self myLongDbQuery];
dispatch_async(dispatch_get_main_queue(), ^(void){
//Perform you UI Updates here
self.myLabel.text = #"Query done!!!";
});
});
Hope it helps
Luca is right about using GCD. If talk about your initial solution.
Did you set initial value for _tempo? Looks like your bug can be caused by _tempo = 0 initially. As you understand sliderMoved will be called only after some user action so you need to set initial value.
I am making an application that randomly selects a picture from a preset group of pictures and displays it to a image view. This should happen every second or so until it has gone through 20 cycles.
Hear is my header and implementation code:
#interface spinController : UIViewController
{
IBOutlet UIImageView *imageHolder;
NSTimer *MovementTimer;
}
-(IBAction)Button:(id)sender;
-(void)displayPic;
#end
#implementation spinController
-(IBAction)Button:(id)sender
{
int count = 0;
while (count <20)
{
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(displayPic) userInfo:nil repeats:NO];
count++;
}
}
-(void)displayPic
{
int r = arc4random() % 2;
if(r==0)
{
imageHolder.image = [UIImage imageNamed:#"puppy1.jpg"];
}
else
{
imageHolder.image = [UIImage imageNamed:#"puppy2.jpg"];
}
}
-(void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
#end
I have made this application in a much more advanced form in WPF and ran into similar issues where the pictures do no cycle through properly. If I hit spin it randomizes but does not go through the 20 cycles... just one. This is my first application in objective-c and realize the efficiency of the method I choose will determine how good my application will run in a more complex form. Any help would be greatly appreciated.
The problem is that you're calling the timer repeatedly within the while loop; and since that particular while loop will complete within a millisecond or so, you're creating 20 timers one after another in immediate succession. Because of this, only the final image will show up in the imageHolder view. Edit: Even if the loop were to take more than a millisecond per iteration, I believe the NSTimer wouldn't actually fire until the method exits anyway.
In order to show the images one after another as you're trying to do, (1) use the NSTimer without the while loop, (2) keep track of the iterations using a count class instance variable so as not to lose the value of the variable upon the completion of the various methods, and (3) pass along the NSTimer to the displayPic method so you can invalidate the timer from there upon the 20th iteration. For example:
// Declare the "count" instance variable.
int count;
-(void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
-(IBAction)Button:(id)sender {
// The count starts at 0, so initialize "count" to 0.
count = 0;
// Use an NSTimer to call displayPic: repeatedly every 1 second ("repeats" is set to "YES")
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(displayPic:) userInfo:nil repeats:YES];
}
// Pass along the NSTimer to the displayPic method so that it can be invalidated within this method upon the 20th iteration
-(void)displayPic:(NSTimer *)timer {
// Get the random number
int r = arc4random() % 2;
// If the number is 0, display puppy1.jpg, else display puppy2.jpg.
if(r == 0) {
imageHolder.image = [UIImage imageNamed:#"puppy1.jpg"];
}
else {
imageHolder.image = [UIImage imageNamed:#"puppy2.jpg"];
}
// Increment "count" to reflect the number of times the NSTimer has called this method since the button press
count ++;
// If the count == 20, stop the timer.
if (count == 20)
[timer invalidate];
}
#end
Change repeats to YES. This causes the timer to run again and again. Then instead of a while loop check the count in the method itself.
I want to make something different appear every 2 seconds say, 10 times. How would I go about achieving this in Objective-C?
I was thinking about using NSTimer and invalidating it after so many seconds, like in the above example 2 * 10 seconds after I start the timer. Or is there a way to measure the ticks?
Or I was considering a for loop and using the performSelector:withDelay: method.
Which would be preferable?
Use NSTimer and set the time interval as 2 seconds and repeats to YES.
Count the number of times that it triggered. Invalidate when it reaches 10.Thats it
Code:
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(trigger:) userInfo:yourObject repeats:YES];
- (void)trigger:(NSTimer *)sender{
id yourObject = sender.userInfo;
static int count = 1;
#try {
NSLog(#"triggred %d time",count);
if (count == 10){
[sender invalidate];
NSLog(#"invalidated");
}
}
#catch (NSException *exception)
{
NSLog(#"%s\n exception: Name- %# Reason->%#", __PRETTY_FUNCTION__,[exception name],[exception reason]);
}
#finally {
count ++;
}
}
i used the second option of you, no timer needed
for (int a=0; a<10; a++) {
[self performSelector:#selector(print) withObject:nil afterDelay:2.0*a];
}
-(void)print
{
NSLog(#"sth");
}
you may make the interval and repeat count flexible.