** I show count down timer on cell when i long press on cell.but the problem is that when i click on row timer started but suddenly when i click on second cell timer stop of previous cell and start timer new cell which i click ,but the main functionality which i need is that i have to show running previous timer and current timer . please any one give me right way for that.**
-(void)handleLongPress:(UILongPressGestureRecognizer *)gestureRecognizer
{ AppDelegate *app=(AppDelegate *)[UIApplication sharedApplication].delegate;
CGPoint p = [gestureRecognizer locationInView:ChatTable];
NSIndexPath *indexPath = [ChatTable indexPathForRowAtPoint:p];
if ([[[userArray valueForKey:#"sender_id"] objectAtIndex:indexPath.row]isEqualToString: app.userId]) {
}
else{
if (gestureRecognizer.state == UIGestureRecognizerStateBegan)
{
if ([imageCache objectForKey:[NSString stringWithFormat:#"%d",indexPath.row]]==NULL) {
}
else
{
self.picId=[[userArray valueForKey:#"pic_id"] objectAtIndex:indexPath.row];
self.imageName=[[userArray valueForKey:#"imagename"] objectAtIndex:indexPath.row];
ChatTable.userInteractionEnabled=NO;
// self.navigationController.navigationBarHidden=YES;
image=[[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 320, 480)];
self.pickerValue=[[userArray valueForKey:#"seconds"] objectAtIndex:indexPath.row];
image.image=[imageCache objectForKey:[NSString stringWithFormat:#"%d",indexPath.row]];
[self.view addSubview:image];
[self showTableTime:indexPath];
}
}
else if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
UITableViewCell * myCell = [ChatTable cellForRowAtIndexPath:indexpathForTimer];
UILabel * myLabel = (UILabel *)[myCell viewWithTag:101];
myLabel.text=#"";
// self.navigationController.navigationBarHidden=NO;
[image removeFromSuperview];
}
}
}
-(void)showTableTime:(NSIndexPath *)indexPath
{
secondsLeft=[self.pickerValue integerValue];
indexpathForTimer=indexPath;
[self countDownTimer];
}
- (void)countDownTimer {
timer =[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateCounter:) userInfo:nil repeats:YES];
}
- (void)updateCounter:(NSTimer *)theTimer {
UITableViewCell * myCell = [ChatTable cellForRowAtIndexPath:indexpathForTimer];
UILabel * myLabel = (UILabel *)[myCell viewWithTag:101];
if (secondsLeft >0) {
secondsLeft --;
UILabel * myLabel = (UILabel *)[myCell viewWithTag:101];
myLabel.text=[NSString stringWithFormat:#"0%d",secondsLeft];
}
else if(secondsLeft==0)
{
[self.image removeFromSuperview];
[timer invalidate];
ChatTable.userInteractionEnabled=YES;
myLabel.text=#"";
[self SendRequsetToServerOfSeenImage];
}
else
{
}
}
The problem is that you only store one timer/indexpath pair (you only use the variables "timer" and "indexPathForTimer"). As soon as you activate a second cell, the variable indexPathForTimer will be overwritten with the indexPath for the second cell, so you only update the second cell.
I would suggest you convert the variable timer into an NSMutableArray and indexPathForTimer into an NSMapTable and then do
// code to init the map table, perform this where you initialize your variables
// (you may want to use weakToStrong or weakToWeak depending on your memory
// management)
indexPathForTimer = [NSMapTable strongToStrongObjectsMapTable];
// use this code to create your timer instance with reference to the index path
// for which it was instantiated
NSTimer* newTimer = [NSTimer timerWithTimerInterval:1.0 target:self selector:#selector(updateCounter:) userInfo:nil repeats:YES];
[timer addObject:newTimer];
[indexPathForTimer addObject:indexPath forKey:newTimer];
[NSRunLoop mainRunLoop] addTimer:newTimer forModes:NSRunLoopCommonModes];
In your updateCounter: method, you can then get the real index path like so
currentIndexPath = [indexPathForTimer objectForKey:theTimer];
Try this
- (void)countDownTimer
{
timer =[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateCounter:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
}
I think you want to run multiple timer on view or on each cell. but it become complicated to handle multiple timers that running on different cells.
Related
So I have a view controller with a timer. It has one button. When the view loads for the very first time, the button should say "start."
When it is tapped, "start"->"pause".
Another tap, "pause"->"resume".
Another tap, "resume"->"pause".
Since I want the timer to be accurate, I detached it from the main thread (I think I chose the right method, I would appreciate some clarification). But it seems like detaching it from the thread actually calls the method...which makes the button start with "pause" instead of start. How do I fix this?
By the way, default value (loads with) for testTask.showButtonValue is 1.
- (void)viewDidLoad {
[super viewDidLoad];
[NSThread detachNewThreadSelector:#selector(startTimer:) toTarget:self withObject:nil];
if (testTask.showButtonValue == 1) {
[startButton setTitle:#"Start" forState:UIControlStateNormal];
} else if (testTask.showButtonValue == 2) {
[startButton setTitle:#"Pause" forState:UIControlStateNormal];
} else if (testTask.showButtonValue == 3){
[startButton setTitle:#"Resume" forState:UIControlStateNormal];
}
}
-(IBAction)startTimer:(id)sender{
if (testTask.showButtonValue == 1) {
[startButton setTitle:#"Pause" forState:UIControlStateNormal];
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerAction:) userInfo:nil repeats:YES];
testTask.showButtonValue = 2;
} else if (testTask.showButtonValue == 2) {
[startButton setTitle:#"Resume" forState:UIControlStateNormal];
[timer invalidate];
timer = nil;
testTask.showButtonValue = 3;
} else if (testTask.showButtonValue == 3){
[startButton setTitle:#"Pause" forState:UIControlStateNormal];
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerAction:) userInfo:nil repeats:YES];
testTask.showButtonValue = 2;
}
}
-(void)timerAction:(NSTimer *)t
{
if(testTask.timeInterval == 0)
{
if (self.timer)
{
[self timerExpired];
[self.timer invalidate];
self.timer = nil;
}
}
else
{
testTask.timeInterval--;
}
NSUInteger seconds = (NSUInteger)round(testTask.timeInterval);
NSString *string = [NSString stringWithFormat:#"%02u:%02u:%02u",
seconds / 3600, (seconds / 60) % 60, seconds % 60];
timerLabel.text = string;
NSLog(#"%f", testTask.timeInterval);
}
I suggest going about this in a different way:
#interface SNDViewController ()
#property (weak, nonatomic) IBOutlet UIButton *startButton;
#property (weak, nonatomic) IBOutlet UILabel *timerLabel;
#property (nonatomic, strong) NSTimer *timer;
#property (nonatomic, assign) NSTimeInterval accumulativeTime;
#property (nonatomic, assign) NSTimeInterval currentReferenceTime;
#end
#implementation SNDViewController
EDIT: when the view loads, initialise self.accumulativeTime and update the timer label. The initialisation of accumulativeTime variable should really be done in the appropriate init* method, e.g. initWithNibName:bundle:. It is at this point that you would read the timer value from core data.
- (void)viewDidLoad
{
[super viewDidLoad];
self.accumulativeTime = 300;
[self updateTimerLabelWithTotalTime:self.accumulativeTime];
}
- (IBAction)changeTimerState:(UIButton *)sender
{
if (self.timer == nil) {
self.currentReferenceTime = [NSDate timeIntervalSinceReferenceDate];
[self.timer invalidate];
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:#selector(updateTimer:) userInfo:nil repeats:YES];
} else {
//Pause the timer and track accumulative time.
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
[self.timer invalidate];
self.timer = nil;
NSTimeInterval timeSinceCurrentReference = now - self.currentReferenceTime;
EDIT: subtract timeSinceCurrentReference from accumalitiveTime, because this is a countdown timer. Also added a comment for saving to core data if necessary.
self.accumulativeTime -= timeSinceCurrentReference;
[self updateTimerLabelWithTotalTime:self.accumulativeTime];
//Optionally save self.accumulativeTime to core data for future use.
}
NSString *buttonTitle = (self.timer != nil) ? #"Pause" : #"Resume";
[self.startButton setTitle:buttonTitle forState:UIControlStateNormal];
}
You don't need to store any unnecessary state, such as the showButtonValue variable. Instead you can base your decision of whether to pause or resume on whether or not self.timer == nil. If there is no timer running, then grab the current reference time and start a new timer. The timer is scheduled to fire every 0.01 seconds, which will hopefully make it accurate to 0.1 seconds. You never need to change the button's title to "Start". It is either "Pause" or "Resume".
When the timer is paused by the user, we dispose of self.timer and update the timer label with the most accurate time.
- (void)updateTimer:(id)sender
{
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
NSTimeInterval timeSinceCurrentReference = now - self.currentReferenceTime;
EDIT: subtract timeSinceCurrentReference from self.accumulativeTime to get the totalTime (i.e. totalTime decreases as time goes by).
NSTimeInterval totalTime = self.accumulativeTime - timeSinceCurrentReference;
if (totalTime <= 0) {
totalTime = 0;
[self.timer invalidate];
self.timer = nil;
//What to do when we reach zero? For example, we could reset timer to 5 minutes:
self.accumulativeTime = 300;
totalTime = self.accumulativeTime;
[self.startButton setTitle:#"Start" forState:UIControlStateNormal];
}
[self updateTimerLabelWithTotalTime:totalTime];
}
Each time the timer is fired, we get the total time by finding the difference between now and self.currentReferenceTime and add it to self.accumulativeTime.
- (void)updateTimerLabelWithTotalTime:(NSTimeInterval)totalTime
{
NSInteger hours = totalTime / 3600;
NSInteger minutes = totalTime / 60;
NSInteger seconds = totalTime;
NSInteger fractions = totalTime * 10;
self.timerLabel.text = [NSString stringWithFormat:#"%02u:%02u:%02u.%01u", hours, minutes % 60, seconds % 60, fractions % 10];
}
#end
The method - (IBAction)changeTimerState:(UIButton *)sender is called by the UIButton on the "Touch Up Inside" event (UIControlEventTouchUpInside).
You don't need to do anything in viewDidLoad.
Also, and importantly, this is all done on the main thread. If anything gets in the way of the main thread updating the timer label, then the text visible to the user may be inaccurate, but when it is updated, you can be sure that it will be accurate again. It depends on what else your app is doing. But since all UI updates must be done on the main thread there is really no avoiding this.
Hope this helps. Let me know if anything is unclear.
(Xcode project available here: https://github.com/sdods3782/TVTTableViewTest)
Calling detachNewThreadSelector will create a new thread and perform the selector mentioned, immediately afer the call.
For fixing your issue change your methods like:
- (void)viewDidLoad
{
[super viewDidLoad];
[NSThread detachNewThreadSelector:#selector(startTimer:) toTarget:self withObject:nil];
}
-(IBAction)startTimer:(id)sender
{
if (testTask.showButtonValue == 1) {
[startButton setTitle:#"Start" forState:UIControlStateNormal];
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerAction:) userInfo:nil repeats:YES];
testTask.showButtonValue = 3;
} else if (testTask.showButtonValue == 2) {
[startButton setTitle:#"Resume" forState:UIControlStateNormal];
[timer invalidate];
timer = nil;
testTask.showButtonValue = 3;
} else if (testTask.showButtonValue == 3){
[startButton setTitle:#"Pause" forState:UIControlStateNormal];
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerAction:) userInfo:nil repeats:YES];
testTask.showButtonValue = 2;
}
}
I want to set time count down like start with 60sec then elapsed 59,58,57 etc... this elapsed time show in bottom of the screen, How to set this?
Please help me
Thanks in Advance
You can use like:
Declare a NSTimer object on the #interface like NSTimer *aTimer;
-(void)viewDidLoad
{
aTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(elapsedTime) userInfo:nil repeats:YES];
}
-(void)elapsedTime
{
static int i = 60;
label.text = [NSString stringWithFormat:#"%d",i];
i--;
if(i<0)
{
[aTimer invalidate];
aTimer = nil;
}
}
Here label is a IBOutlet UILabel, that is set to interface.
You can do this with NSTimer see my example -
Declared
NSTimer *timer;
int count;
Globally in your ViewController.h class
Then you can start your count down in viewDidLoad: method.
- (void)viewDidLoad
{
//label is your **UILabel**
label.text = #"60";
count = 59;
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(countDown) userInfo:nil repeats:YES];
}
-(void)countDown
{
if(count > 0)
{
label.text = [[NSNumber numberWithInt:count] stringValue];
count --;
}
else
{
[timer invalidate];
}
}
I am trying to increase a variable's value while uibutton is kept pressing. But when user leaves button, increasing of variable's value will be stopped.
i have tried using threads with touch down and touchupinside but couldn't make it work.
-(void) changeValueOfDepthFields:(UIButton *)sender {
if (pressing)
pressing = NO;
else
pressing = YES;
pressingTag = 0;
while (pressing) {
[NSThread detachNewThreadSelector:#selector(increaseValue) toTarget:self withObject:nil];
}
}
- (void) stopValueChange:(UIButton *)sender {
pressing = NO;
}
[fStopUp addTarget:self action:#selector(changeValueOfDepthFields:) forControlEvents:UIControlEventTouchDown];
[fStopUp addTarget:self action:#selector(stopValueChange:) forControlEvents:UIControlEventTouchUpInside];
- (void) increaseValue {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
fstopVal = fstopVal + 0.1;
[self performSelectorOnMainThread:#selector(changeTextOfValues) withObject:nil waitUntilDone:YES];
[pool release];
}
- (void) changeTextOfValues {
fStopField.text = [NSString stringWithFormat:#"%.02f", fstopVal];
}
I wonder if there is an alternative way to do this or not. It seems very simple but couldn't think of any other solution than this one.
It is much easier to use an NSTimer.
- (void)changeValueOfDepthFields:(UIButton *)sender
{
if (!self.timer) {
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(increaseValue) userInfo:nil repeats:YES];
}
}
- (void)stopValueChange:(UIButton *)sender
{
if (self.timer) {
[self.timer invalidate];
self.timer = nil;
}
}
- (void)increaseValue
{
fstopVal = fstopVal + 0.1;
fStopField.text = [NSString stringWithFormat:#"%.02f", fstopVal];
}
Note: The previous code is just for reference, I didn't do any memory management for example.
I have this code in LongTapGestureRecognizer for autoscrolling a view:
-(void) longPressDetectedgesture:
(UILongPressGestureRecognizer*)recognizer
{
_btnautoscrollstop.hidden = NO;
_btnautoscroll.hidden = YES;
// if (autoscrollTimer == nil) {
autoscrollTimer = [NSTimer
scheduledTimerWithTimeInterval:(55.0/1000.0)
target:self
selector:#selector(autoscrollTimerFired:)
userInfo:nil
repeats:YES];
}
- (void)autoscrollTimerFired:(NSTimer*)timer {
CGPoint scrollPoint = self.table.contentOffset;
scrollPoint = CGPointMake(scrollPoint.x, scrollPoint.y + 1);
[self.table setContentOffset:scrollPoint animated:NO];
}
It works perfect for me, but my need is, the autoscrooling must stop when the user taps the screen for Longgesture for the second time and vise versa. How to stop this, when the user taps for a second time.
It looks like you're almost there. You probably want something like this:
if (recogniser.state == UIGestureRecognizerStateBegan) {
if (autoscrollTimer == nil) {
autoscrollTimer = [NSTimer scheduledTimerWithTimeInterval:(55.0/1000.0)
target:self
selector:#selector(autoscrollTimerFired:)
userInfo:nil
repeats:YES];
} else {
[autoscrollTimer invalidate];
autoscrollTimer = nil;
}
}
What i usually do is declare a global BOOL Alter; and initialize it Alter = NO; in viewDidLoad (or any other method) then
-(void) longPressDetectedgesture:(UILongPressGestureRecognizer*)recognizer
{
if(Alter)
{
Alter = NO;
[autoscrollTimer inValidate];
}
else
{
Alter = YES;
_btnautoscrollstop.hidden = NO;
_btnautoscroll.hidden = YES;
// if (autoscrollTimer == nil) {
autoscrollTimer = [NSTimer scheduledTimerWithTimeInterval:(55.0/1000.0)
target:self
selector:#selector(autoscrollTimerFired:)
userInfo:nil
repeats:YES];
}
}
create a BOOL called shouldFireTimer in viewDidLoad or similar and update it's value each time you detect a longpress
-(void) longPressDetectedgesture: (UILongPressGestureRecognizer*)recognizer {
_btnautoscrollstop.hidden = NO;
_btnautoscroll.hidden = YES;
if ( shouldFireTimer ) {
[autoscrollTimer invalidate];
autoscrollTimer = nil;
} else {
autoscrollTimer = [NSTimer
scheduledTimerWithTimeInterval:(55.0/1000.0)
target:self
selector:#selector(autoscrollTimerFired:)
userInfo:nil
repeats:YES];
}
shouldFireTimer = !shouldFireTimer;
}
- (void)autoscrollTimerFired:(NSTimer*)timer {
CGPoint scrollPoint = self.table.contentOffset;
scrollPoint = CGPointMake(scrollPoint.x, scrollPoint.y + 1);
[self.table setContentOffset:scrollPoint animated:NO];
}
or like Matt says above maybe just check nil status instead of using a BOOL. I suggest using a BOOL as you maybe firing autoscrollTimerFired in other circumstances too (from a button for example) i.e. it might not be nil when you want to call it.
I am having issue with invalidating a NSTimer.
I initialize a NSTimer with below function.
-(void) initLTTimer{
[self shutLTTimer];
[self setQuestion:questionCounter];
isQuestionAttempt=NO;
tmLeftTime=[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(updateLeftTime:) userInfo:nil repeats:YES];
NSLog(#"Timer Initialized");
}
further I called a function updateLeftTime in its selector.
- (void)updateLeftTime:(NSTimer *)theTimer {
NSLog(#"%d",timeCounter);
timeCounter+=1;
tfLeftTime.text=[NSString stringWithFormat:#"%d", (QUESTION_TIME_LIMIT-timeCounter)];
if (timeCounter>=QUESTION_TIME_LIMIT) {
if (isQuestionAttempt==NO) {
[self increaseDropShots];
}
[self setQuestionBg];
timeCounter=0;
[self shutLTTimer];
[self updateQuestion:nil];
}
}
This function [self increaseDropShots]; called in above function.
here is the code of this function
-(void)increaseDropShots
{
NSString *imgName = #"DactiveRetake";
if (IS_IPAD) {
imgName = [imgName stringByAppendingString:#"_ipad"];
}
wrongAttemp+=1;
for (int i =1; i<=wrongAttemp; i++ )
{
UIImageView *img=(UIImageView *)[self.view viewWithTag:i+50];
[img setImage:[UIImage imageNamed:imgName]];
}
NSLog(#"Question dropped counter: %d",wrongAttemp);
if (wrongAttemp == 3)
{
[self shutLTTimer];
[CommonFunctions initGlobalVars];
[Bgplayer stop];
[Bgplayer release];
OverPageViewController *opvc=[[OverPageViewController alloc] initWithNibName:[CommonFunctions getXIBFile:#"OverPageViewController"] bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:opvc animated:YES];
[opvc release];
}
}
In this function I am killing this timer but I am unable.
-(void) shutLTTimer{
if ([tmLeftTime isValid]) {
[tmLeftTime invalidate];
tmLeftTime=nil;
}
}
This is the whole scenario of my application
Please help me what is the issue.
Answered after conversation in comments
Does the code definitely get to the line, [tmLeftTime invalidate];? You can check this by using a breakpoint or an NSLog.