execute task with a time sequence in iOS - ios

I want to execute some codes with a time sequence like [5s, 10s, 10s, 20s], which means that it executes the code after 5 seconds, and executes it the second time after 10s. I want to use NSTimer, but I can not figure out how can I do.

My method is a bit simpler to implement.
- (void)startExecute
{
intervals=#[#(5),#(10),#(10),#(20)]; // class member
isExecuting=YES; // class member
[self executeTaskAtIndex:0]; // start first task
}
- (void)executeTaskAtIndex:(NSUInteger)index
{
if (index>=intervals.count || !isExecuting) // no intervals left or reset execution
return;
NSNumber *intervalNumber=intervals[index];
NSTimeInterval interval=intervalNumber.doubleValue;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(interval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if(!isExecuting)
return;
// execute your task here
//...
index++;
[self executeTaskAtIndex:index]; // another iteration
});
}

Create a data structure to hold the time intervals, the target and the action to execute:
(untested)
typedef struct {
NSTimeInterval interval;
id target;
SEL selector;
} ScheduledItem;
Then create an array of these items:
static ScheduledItem _schedule[] = {
{ 5.0, someTarget, #selector(someMethod) },
{ 10.0, someTarget, #selector(someMethod) },
{ 15.0, someTarget, #selector(someMethod) }
};
#define NUM_SCHEDULED_ITEMS (sizeof(schedule) / sizeof(schedule[0]))
and then create a timer somewhere to dispatch the work:
#interface MyClass ()
{
NSTimer *_timer;
unsigned _scheduledItem;
}
- (void)_setupTimer;
- (void)_timerFired:(NSTimer *)timer;
#end
#interface MyClass
- (instancetype)init
{
self = [super init];
if (self) {
_scheduledItem = 0;
[self _setupTimer];
}
return self;
}
- (void)_setupTimer
{
_timer = nil;
if (_scheduledItem < NUM_SCHEDULED_ITEMS) {
NSTimeInterval interval = _schedule[_scheduledItem].interval
_timer = [NSTimer scheduledTimerWithTimeInterval:interval
target:self
selector:#selector(_timerFired:)
userInfo:nil
repeats:NO];
}
}
- (void)_timerFired:(NSTimer *)timer
{
id target = _schedule[_scheduledItem].target;
SEL action = _schedule[_scheduleItem].action;
[target performSelector:action withObject:nil];
_scheduledItem++;
[self _setupTimer];
}
You will most probably have to set-up the _schedule array at runtime, as the target won't be available at compile time. If it's always self then you can leave it out of the schedule altogether and if you always call the same selector then you can leave that out too, leaving just an array of time intervals.

Related

NSTimer is not firing from Singleton Class

I have a singleton class that I want to run in the background and check for photos to upload. The singleton is initialized from another viewcontroller via
[[EXOImageUploader sharedPhotoUploader] startPhotoUploadCheck];
If I NSLog everything, it appears the singleton is working. I can do other things in the singleton that aren't shown below just fine. The NSTimer just never fires. I have even commented out the line that checks to see if the timer isValid but that doesn't work either.
Any idea on why my Timer is working?
Here is the order that the NSLog's spit out.
sharedPhotoUploader init
doSetup
Timer interval: 1.000000
sharedPhotoUploader singleton
startPhotoUploadCheck
Here is the code.
.h
#interface EXOImageUploader : NSObject
#property (assign) NSTimeInterval timerCheckInterval;
+ (EXOImageUploader *) sharedPhotoUploader;
- (void) startPhotoUploadCheck;
- (void) stopPhotoUploadCheck;
.m
#interface EXOImageUploader ()
#property (nonatomic, strong) NSTimer* timerUpload;
#end
#implementation EXOImageUploader
static EXOImageUploader* _sharedPhotoUploader;
#pragma mark - SINGLETON SETUP
+ (EXOImageUploader *) sharedPhotoUploader {
static EXOImageUploader *sharedPhotoUploader = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedPhotoUploader = [[self alloc] init];
NSLog(#"sharedPhotoUploader singleton");
});
return sharedPhotoUploader;
}
- (id) init {
if (self = [super init]){
NSLog(#"sharedPhotoUploader init");
[self doSetup];
}
return self;
}
- (void) doSetup {
NSLog(#"doSetup");
if (!self.timerCheckInterval) {
self.timerCheckInterval = 1.0f;
}
NSLog(#"Timer interval: %f", self.timerCheckInterval);
}
#pragma mark Public Methods
- (void) startPhotoUploadCheck {
NSLog(#"startPhotoUploadCheck");
//Don't start a new one if this one is running
if (!_timerUpload) {
_timerUpload = [NSTimer timerWithTimeInterval:_timerCheckInterval target:self selector:#selector(checkForPhotosToUpload) userInfo:nil repeats:YES];
}
}
- (void) stopPhotoUploadCheck {
NSLog(#"stopPhotoUploadCheck");
[_timerUpload invalidate];
_timerUpload = nil;
}
Use scheduledTimerWithTimeInterval instead of timerWithTimeInterval.
The docs for timerWithTimeInterval say: "You must add the new timer to a run loop, using addTimer:forMode:".
With the "scheduled" version, that's already done for you.
You are trying to use your timer without initializing it, thus it will be nil and won't fire. Activate your timer like this:
[timerUpload scheduledTimerWithTimeInterval:2.0
target:self
selector:#selector(targetMethod:)
userInfo:nil
repeats:NO];

Animate text Content in iOS - Equivalent for Android ValueAnimator

I am working on an iOS 7+ app and would like to animated change the content of an UILabel. I do not want do do any graphical animation like fade out old content / fade in new content. Thus all the standard animation features iOS offers like Layer animations or animation blocks cannot be uses (at least I think so).
Assume the UILabel shows some meter values like "200 V" and this text should be changed to "400 V". The text should not just jump from "200 V" to "400 V" but should be counted up using some easing function: "200 V", "220 V", "240 V"... "390 V", "395 V" ... "400 V"
In Android could easily be solved using a ValueAnimator:
ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);
animation.setInterpolation(new EaseOutInterpolator());
animation.setDuration(2500);
animation.setStartDelay(500);
animation.addUpdateListener(new AnimatorUpdateListener() {
#Override
public void onAnimationUpate(ValueAnimator animator) {
float currentValue = animator.getAnimatedValue.floatValue();
label1.setText(String.format("%.2", fromValue1 + ((toValue1 - fromValue1) * currentValue)));
label2.setText(String.format("%.2", fromValue2 + ((toValue2 - fromValue2) * currentValue)));
...
}
});
animation.start();
Is there such thing in iOS as well? I found different solution for this but they are all pretty old (2010/11) and all end up implementing this behavior manually using NSTimer and own easing functions.
It is out of question that one can implement this on his own, but this would be quite cumbersome and not very elegant. So: Is there something build in iOS to solve this or are there at least convenient third party implementation available?
Thank you very much!
Since I found no tailored solution for this I created my own: A simple Animator Class which handles the Easing:
// MyValueAnimation.h
typedef void (^MyAnimationBlock)(double animationValue);
#interface MyValueAnimation : NSObject
- (void)startAnimation:(MyAnimationBlock)animationBlock runtime:(NSUInteger)runtime delay:(NSUInteger)delay;
#end
// MyValueAnimation.m
#import "MyValueAnimation.h"
// Number of seconds between each animation step
#define kStepSize 0.05
#interface MyValueAnimation () {
NSTimer *timer;
NSUInteger totalRunTime; // Total duration of the animation (delay not included)
NSUInteger currentRuntime; // Time the animation is already running
MyAnimationBlock animationBlock;
}
#end
#implementation MyValueAnimation
- (void)startAnimation:(MyAnimationBlock)block runtime:(NSUInteger)runtime delay:(NSUInteger)delay {
if (timer != nil)
[timer invalidate];
timer = nil;
totalRunTime = runtime;
animationBlock = block;
currentRuntime = 0;
if (block != nil) {
if (delay > 0) {
// Wait to delay the start. Convert delay from millis to seconds
double delaySeconds = (double)delay / 1000.0;
timer = [NSTimer scheduledTimerWithTimeInterval:delaySeconds target:self selector:#selector(delayTick:) userInfo:nil repeats:false];
} else {
// Run the animation
timer = [NSTimer scheduledTimerWithTimeInterval:kStepSize target:self selector:#selector(animationTick:) userInfo:nil repeats:true];
}
}
}
- (void)delayTick:(NSTimer *)delayTimer {
// End of delay -> run animation
[delayTimer invalidate];
timer = [NSTimer scheduledTimerWithTimeInterval:kStepSize target:self selector:#selector(animationTick:) userInfo:nil repeats:true];
}
- (void)animationTick:(NSTimer *)animationTimer {
NSUInteger step = 1000 * kStepSize; // step size/length in milli seconds
currentRuntime += step;
double progress = MIN((double)currentRuntime / (double)totalRunTime, 1.0);
// Progress is a value between 0 and 1. The easing function maps this
// to the animationValue which is than used inside the animationBlock
// to calculate the current value of the animiation
double animationValue = [self customEaseOut:progress];
if (animationBlock != nil)
animationBlock(animationValue);
if (progress >= 1.0) {
// Animation complete
[timer invalidate];
timer = nil;
}
}
- (double)customEaseOut:(double)t {
// Use any easing function you like to animate your values...
// http://rechneronline.de/function-graphs/
// http://sol.gfxile.net/interpolation/
return (1 - pow(1-t, 2));
}
#end
// =============================================================
// Some code using the animation
- (void)animateValueFrom:(double)fromValue to:(double)toValue {
if (valueAnimation == nil)
valueAnimation = [[MyValueAnimation alloc] init];
MyAnimationBlock animationBlock = ^(double animationValue) {
double currentValue = fromValue + ((toValue - fromValue) * animationValue);
someLabel.text = [NSString stringWithFormat:"%dV", currentValue];
};
[valueAnimation startAnimation:animationBlock runtime:1500 delay:500];
}
Maybe not the prettiest solution but it works :-)
I know a solution but its for fast iteration because final iterations can jump through value (looks not beautifully if there is a slow search), but the decision simple and short, can not the most beautiful from a architecture(but it can be corrected if it is necessary) realisation.
- (void)someAction
{
[self animateValue:0 toValue:1111 withStep:7 andIntervalSpeed:5];
}
-(void)animateValue:(int)value toValue:(int)toValue withStep:(int)step andIntervalSpeed:(int64_t)intervalSpeed
{
self.currentValue = value; // #property (nonatomic) int currentValue;
NSUInteger numberofIteration = (toValue - value)/step;
int64_t interval = 0.0;
for (NSUInteger i = 0; i < numberofIteration; i++) {
dispatch_time_t start = DISPATCH_TIME_NOW;
interval += intervalSpeed;
dispatch_after(dispatch_time(start, interval * USEC_PER_SEC), dispatch_get_main_queue(), ^{
if (((toValue - value)%step != 0) && (i == (numberofIteration-1)))
{
self.currentValue = toValue;
}
else
{
self.currentValue+= step;
}
NSLog(#"%d",self.currentValue);
self.someLabel.text = [NSString stringWithFormat:#"%d",self.currentValue];
});
}
}

Creating multiple NSTimers countdowns

I'm trying to create a simple countdown timer app for myself. So far I've figured out how to create the countdown timers with a stop/reset action on them for a single button I've got attached to the timer.
However, I would like to add multiple timers to the same page and I'm not really sure how to do about making extra calls for the timers. Each of the timers would have it's own number to count down from (7 minutes for one, 3 minutes for the other, etc). These are set intervals that the user is not able to change. Google hasn't really worked out for me on how to do this so I'm hoping someone can at least guide me in the right direction. Below is my code snippets:
ViewController.h
#interface ViewController : UIViewController {
IBOutlet UILabel *firstCountdownLabel;
NSTimer *firstCountdownTimer;
bool timerActive;
int secondsCount;
}
- (IBAction)start:(id)sender;
- (void)timerRun;
#end
ViewController.m
#interface ViewController ()
#end
#implementation ViewController
- (void) timerRun {
secondsCount = secondsCount - 1;
int minutes = secondsCount / 60;
int seconds = secondsCount - (minutes * 60);
NSString *timerOutput = [NSString stringWithFormat:#"%2d:%.2d", minutes, seconds];
firstCountdownLabel.text = timerOutput;
if (secondsCount == 0) {
[firstCountdownTimer invalidate];
firstCountdownTimer = nil;
}
}
//- (void) setTimer {
- (IBAction)start:(id)sender {
secondsCount = 420;
if (timerActive == NO) {
timerActive = YES;
self->firstCountdownTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerRun) userInfo:nil repeats:YES];
}
else {
timerActive=NO;
[self->firstCountdownTimer invalidate];
self->firstCountdownTimer = nil;
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
// [self setTimer];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Google doesn't help in showing you how to implement original application ideas.
If you want multiple timers, define multiple timer instance variables:
#interface ViewController : UIViewController
{
IBOutlet UILabel *timer1Label;
IBOutlet UILabel *timer2Label;
IBOutlet UILabel *timer3Label;
NSTimer *timer1;
NSTimer *timer2;
NSTimer *timer3;
int timer1Count;
int timer2Count;
int timer3Count;
bool timer1Active;
bool timer2Active;
bool timer3Active;
}
Then create a separate IBAction for each button that starts each of the timers:
- (IBAction)startTimer1:(id)sender
{
timer1Count = 420;
if (timer1Active == NO)
{
timer1Active = YES;
timer1 = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(timer1Run:)
userInfo:nil
repeats:YES];
}
else
{
timer1Active=NO;
[timer1 invalidate];
timer1 = nil;
}
}
- (void) timer1Run: (NSTimer*) timer
{
timer1Count -= 1;
int minutes = timer1Count / 60;
int seconds = timer1Count - (minutes * 60);
NSString *timerOutput = [NSString stringWithFormat:#"%2d:%.2d", minutes, seconds];
timer1Label = timerOutput;
if (timer1Count == 0) {
[timer2 invalidate];
timer2 = nil;
}
}
Duplicate the above code for each timer, using "timer2" and "timer3" in place of "timer1". Change the time counts for each one to the desired values. (I changed the names from "firstTimer" to "timer1" because it's easier to edit the code to support multiple timers that way.
I did not write 3 versions of each method for you because you need to figure this out rather than copy & pasting in code that you don't understand.
It would be possible, and require less code, to use the same IBAction method for all your start timer buttons, and have the code check the tag on the button to decide which timer to start.
The code might look like this:
- (IBAction)startTimer1:(id)sender
{
int tag = [sender tag];
switch (tag)
{
case 1: //timer 1
//Put code to start timer 1 here
break;
case 2: //timer 2
//put code to start timer 2 here
break;
}
}
But that might be a bit over your head at the moment.
By the way, forget you ever saw the "self->variable" syntax. it is slower and more error-prone than just referring to the instance variable directly. using object->variable syntax also allows you to access the instance variables of other objects, which is bad practice. You should always use properties to access the instance variables of objects other than yourself.
Also, the timer method should take a single parameter, a timer. I corrected the timer method in the above code.
Create a class as YourTimer with few properties like
NSString *timerLabel;
NSTimer *timer;
NSInteger timerCounter;
Now create an array of YourTimer objects. Then you can access it easily.
This will be modular, maintainable and reusable code, as may be later on you need one more identifier to be with all timers, hence wrap them in one class and use it.

Auto Saving the content of the screen to the database

I am developing an iPAD application and I want to auto save the contents of the form into SQLITE in every 10 sec intervals. Right now if I press the save button then it saves to the database. Is there any way to auto save the whatever I am writing in the form in every 10-15 seconds. Help me out with this.
Use NSTimer and perform the save every x minutes. The code will look something like this. It is a modified version of the code here.
#interface MyController : UIViewController
{
#private
NSTimer * countdownTimer;
NSUInteger remainingTicks;
}
-(IBAction)doCountdown: (id)sender;
-(void)handleTimerTick;
-(void) saveData;
#end
#implementation MyController
// { your own lifecycle code here.... }
-(IBAction)doCountdown: (id)sender
{
if (countdownTimer)
return;
remainingTicks = 60;
[self saveData];
countdownTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target: self selector: #selector(handleTimerTick) userInfo: nil repeats: YES];
}
-(void)handleTimerTick
{
remainingTicks--;
[self updateLabel];
if (remainingTicks <= 0) {
[countdownTimer invalidate];
countdownTimer = nil;
}
}
-(void) saveData
{
//Save your data here
}
#end

Delay before apply object changes (similar like setNeedsDisplay)

I have object with properties and when I change some of properties, I do a lot of calculations. If I change one property, then another, calculations execute 2 times. I want do all calculations 1 time. I see 3 solution:
Make method commit. But I don't wanna do excess methods.
Make method for change all properties. The same cause why I don't wanna do this. And what to do if I will have much more properties in future.
Make delay after delay commit changes. I really do not know what consequences this may turn into. Something like this:
.
#interface TestObject : NSObject
#property (nonatomic, assign) float x;
#property (nonatomic, assign) float y;
#end
#implementation TestObject
{
BOOL flag;
}
- (id)init
{
self = [super init];
if(self){
flag = NO;
}
return self;
}
- (void)setX:(float)x
{
_x = x;
[self delayCommit];
}
- (void)setY:(float)y
{
_y = y;
[self delayCommit];
}
- (void)delayCommit
{
if(flag == NO){
flag = YES;
[self performSelector:#selector(commit) withObject:nil afterDelay:0];
}
}
- (void)commit
{
flag = NO;
NSLog(#"Do a lot of calculations....");
}
#end
Is third solution good practice? I want simple interface, I don't want excess methods.
If you really want a behavior like that, you should then have a timer that every N seconds, checks if any of the properties are changed, and does the computations. Something like:
- (void)setX:(float)x
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(_mySem, DISPATCH_TIME_FOREVER);
_x = x;
flag = YES;
dispatch_semaphore_signal(_mySem);
});
}
- (void)setY:(float)y
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(_mySem, DISPATCH_TIME_FOREVER);
_y = y;
flag = YES;
dispatch_semaphore_signal(_mySem);
});
}
- (void)updateCycle
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(_mySem, DISPATCH_TIME_FOREVER);
if(flag){
flag = NO;
NSLog(#"Do a lot of calculations....");
}
dispatch_semaphore_signal(_mySem);
});
}
In your init method you would have to initialise the timer and the semaphore with
_mySem = dispatch_semaphore_create(1);
[NSTimer scheduledTimerWithTimeInterval:N
target:self
selector:#selector(updateCycle)
userInfo:nil
repeats:YES];
The way to use it would then just be:
[object setX:4.5f];
[object setY:0.3f];
Depending on how many properties you have, this may work for you instead:
- (void)setX:(float)x y:(float)y
{
_x = x;
_y = y;
[self commit];
}
Here, you set more than one property with one method call. You know you have all the values you need, so you can call commit directly.
I would suggest you not to try to be too fancy with delayed commits (unless you really have to) and to use commit as a separate call and perform your long calculation in a separate thread, e.g.:
- (void)setX:(float)x
{
_x = x;
}
- (void)setY:(float)y
{
_y = y;
}
- (void)commitChanges
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// ... Long calculations go here ...
dispatch_async(dispatch_get_main_queue(), ^{
// ... Once you done with your long calculation, put your UI code here
// ... (e.g. display results of your calculation in text box)
});
});
}
then from your main code will look like:
[obj setX:123.0f];
[obj setY:345.0f];
[obj commitChanges];

Resources