I have been trying for 2 days, searching high and low and cannot get the milliseconds to work. The hours, mins & seconds work fine, but milliseconds won't.
I've made a lapCounter, which counts UP and has no issues with the milliseconds.
Here is the working code for the lapCounter, which counts UP and the milliseconds work:
int hours = (UInt8)(elapsedTime /(60*60));
int mins = (UInt8)(elapsedTime / 60.0);
elapsedTime -= (mins * 60);
int secs = (UInt8)(elapsedTime);
elapsedTime -= (secs);
int mms = (UInt8)(elapsedTime * 100);
But I can't make the TimePicker Count DOWN, work.
This is what I have for the TimePicker Count DOWN:
int afterRemainder;
int remainder;
NSTimeInterval countDownTime;
NSTimer *countDownTimer;
bool startCountDown;
- (IBAction)startCountDownButton:(id)sender {
if (startCountDown == false) {
countDownTime = (NSTimeInterval)_datePicker.countDownDuration;
remainder = countDownTime;
afterRemainder = countDownTime - remainder%60;
countDownTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(updateCountDown) userInfo:nil repeats:YES];
startCountDown = true;
}
-(void)updateCountDown {
afterRemainder --;
int hours = (int)(afterRemainder / (60 * 60));
int mins = (int)(afterRemainder / 60) - (60 * hours);
int secs = (int)(afterRemainder - (60 * mins) - (60 * hours * 60));
int mms = (int)(afterRemainder - (3600 * secs) - (mins * 60));
self.displayCountDown.text = [[NSString alloc] initWithFormat:#"%02d", hours];
self.displayCountDownMins.text = [[NSString alloc] initWithFormat:#": %02d", mins];
self.displayCountDownSecs.text = [[NSString alloc] initWithFormat:#"%02d", secs];
self.displayCountDownMMs.text = [[NSString alloc] initWithFormat:#":%2d", mms];
}
Try this:
#interface ViewController ()
#property (nonatomic, weak) IBOutlet UILabel *hours;
#property (nonatomic, weak) IBOutlet UILabel *minutes;
#property (nonatomic, weak) IBOutlet UILabel *seconds;
#property (nonatomic, weak) IBOutlet UILabel *millisecs;
#property (nonatomic, readwrite) NSTimeInterval counter;
#property (nonatomic, strong) NSTimer *timer;
#property (nonatomic, readwrite) int hoursInt;
#property (nonatomic, readwrite) int minutesInt;
#property (nonatomic, readwrite) int secondsInt;
#property (nonatomic, readwrite) int millisecsInt;
#end
#implementation ViewController
#define MS_COUNT_STEP 20.0 // Approx 50 fps
- (IBAction)countUpPressed:(id)sender {
[self.timer invalidate];
self.timer = [NSTimer scheduledTimerWithTimeInterval:MS_COUNT_STEP / 1000.0 target:self selector:#selector(countUp) userInfo:nil repeats:YES];
}
- (IBAction)countDownPressed:(id)sender {
[self.timer invalidate];
self.timer = [NSTimer scheduledTimerWithTimeInterval:MS_COUNT_STEP / 1000.0 target:self selector:#selector(countDown) userInfo:nil repeats:YES];
}
- (void)countUp {
self.counter += (MS_COUNT_STEP / 1000.0);
}
- (void)countDown {
self.counter -= (MS_COUNT_STEP / 1000.0);
}
- (void)setCounter:(NSTimeInterval)counter {
_counter = counter;
[self updateUI];
}
- (void)updateUI {
NSTimeInterval counter = _counter;
self.hoursInt = counter / 3600;
counter -= ((int)self.hoursInt * 3600);
self.minutesInt = counter / 60;
counter -= ((int)self.minutesInt * 60);
self.secondsInt = (int)counter;
counter -= self.secondsInt;
self.millisecsInt = counter * 1000;
}
- (void)setHoursInt:(int)value {
if (value != _hoursInt) {
_hoursInt = value;
self.hours.text = [NSString stringWithFormat:#"%i", value];
}
}
- (void)setMinutesInt:(int)value {
if (value != _minutesInt) {
_minutesInt = value;
self.minutes.text = [NSString stringWithFormat:#"%i", value];
}
}
- (void)setSecondsInt:(int)value {
if (value != _secondsInt) {
_secondsInt = value;
self.seconds.text = [NSString stringWithFormat:#"%i", value];
}
}
- (void)setMillisecsInt:(int)value {
if (value != _millisecsInt) {
_millisecsInt = value;
self.millisecs.text = [NSString stringWithFormat:#"%i", value];
}
}
#end
Use CADisplayLink, instead NSTimer.
A CADisplayLink object is a timer object that allows your application to synchronize its drawing to the refresh rate of the display.
With [NSTimer scheduledTimerWithTimeInterval:1 ...] the system can't draw the labels at that speed.
Related
I am creating a game application in which I need to set timer of 2 minutes throughout the app screens in objective c.
I am creating it in viewDidLoad but it is creating a new instance every-time the view loads.
Here is the code which I am using :
#interface SomeViewController ()
{
int timerCounter;
NSTimer *timer;
}
#property (strong, nonatomic) IBOutlet UILabel *timerLbl;
#end
#implementation SomeViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self startCountdown];
}
- (void)startCountdown
{
timer = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:#selector(countdownTimer:)
userInfo:nil
repeats:YES];
}
- (void)countdownTimer:(NSTimer *)timer
{
timerCounter--;
int minutes = timerCounter / 60;
int seconds = timerCounter % 60;
NSString *string = [NSString stringWithFormat:#"%02d", minutes];
NSString *string2 = [NSString stringWithFormat:#"%02d", seconds];
NSString *timeTotal = [string stringByAppendingString:#":"];
NSString *timeTotal2 = [timeTotal stringByAppendingString:string2];
_timerLbl.text = timeTotal2;
if (timerCounter <= 0) {
[timer invalidate];
}
}
You need to invalidate it whenever the VC deallocates
- (void)dealloc {
[timer invalidate];
}
//
The second VC may look like this
#import "ggViewController.h"
NSInteger timerCounter = 120; // declared global to hold the value
#interface ggViewController ()
{
NSTimer*timer;
}
#end
#implementation ggViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
// instead of using selector use this inline callback
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:true block:^(NSTimer * timer) {
timerCounter--;
int minutes = timerCounter / 60;
int seconds = timerCounter % 60;
NSString *string = [NSString stringWithFormat:#"%02d", minutes];
NSString *string2 = [NSString stringWithFormat:#"%02d", seconds];
NSString *timeTotal = [string stringByAppendingString:#":"];
NSString *timeTotal2 = [timeTotal stringByAppendingString:string2];
_timerLbl.text = timeTotal2;
if (timerCounter <= 0) {
[timer invalidate];
}
}];
}
-(void)viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
[timer invalidate];
}
- (IBAction)gg:(id)sender {
[self dismissViewControllerAnimated:true completion:nil];
}
#end
I am trying to do the difference between time and display the countdown timer to the user in a ui label
declaration of NSTimer
#property (strong, nonatomic)NSTimer *timer;
this is my timer in view did load
_timer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target:self selector:#selector(updateCountdown:) userInfo:nil repeats: YES];
and this is my updateCountdown method
-(void) updateCountdown:(int)secondsLeft {
int hours, minutes, seconds;
secondsLeft--;
hours = secondsLeft / 3600;
minutes = (secondsLeft % 3600) / 60;
seconds = (secondsLeft %3600) % 60;
_countDownlabel.text = [self timeFormatted:secondsLeft];///[NSString stringWithFormat:#"%02d:%02d:%02d", hours, minutes, seconds];
NSLog(#"%#",[NSString stringWithFormat:#"%02d:%02d:%02d", hours, minutes, seconds]);
if ( secondsLeft == 0 ) {
[_timer invalidate];
}
}
and my log details are
2017-06-30 09:49:34.070 Barebones[845:56963] requestReply cust liqour category: {
date = "30-6-2017";
"end_time" = "11:41";
"market_crash" = true;
"start_time" = "09:41";
}
2017-06-30 09:49:34.070 Barebones[845:56963] true
2017-06-30 09:49:34.070 Barebones[845:56963] 09:41
2017-06-30 09:49:34.070 Barebones[845:56963] 11:41
2017-06-30 09:49:34.070 Barebones[845:56963] 30-6-2017
2017-06-30 09:49:34.073 Barebones[845:56963] 2016-12-25 08:58:00 +0000
2017-06-30 09:49:34.073 Barebones[845:56963] 2016-12-25 12:15:00 +0000
2017-06-30 09:49:34.073 Barebones[845:56963] 197.000000 is the time difference
2017-06-30 09:49:34.073 Barebones[845:56963] 00:03:17
2017-06-30 09:49:35.075 Barebones[845:56963] 991:05:35
2017-06-30 09:49:36.075 Barebones[845:56963] 991:05:35
2017-06-30 09:49:37.075 Barebones[845:56963] 991:05:35
2017-06-30 09:49:38.075 Barebones[845:56963] 991:05:35
and this value goes on executing
Aim:- to countdown till zero and stop the timer actually I ll be hiding the label once the countdown is over
Update:-
int secondsLeft=[self timeFormatted:[date2 timeIntervalSinceDate:date1]/60];
have initialised this above timer
this is my updated timer:-
int secondsLeft=[date2 timeIntervalSinceDate:date1]/60;
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:secondsLeft], #"cID", nil];
_timer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target:self selector:#selector(updateCountdown:) userInfo:userInfo repeats: YES];
and this is my updated timer method :-
- (void)updateCountdown:(NSTimer *)timer{
int hours, minutes, seconds;
NSDictionary *userInfo = [timer userInfo];
int secondsLeft = [[userInfo objectForKey:#"cID"] intValue];
secondsLeft--;
hours = secondsLeft / 3600;
minutes = (secondsLeft % 3600) / 60;
seconds = (secondsLeft %3600) % 60;
_countDownlabel.text = [self timeFormatted:secondsLeft];///[NSString stringWithFormat:#"%02d:%02d:%02d", hours, minutes, seconds];
NSLog(#"%#",[NSString stringWithFormat:#"%02d:%02d:%02d", hours, minutes, seconds]);
if ( secondsLeft == 0 ) {
[_timer invalidate];
}
}
A couple of things.
As vadian said, if you use the selector-based timer, the timer handler function takes a single parameter which is a reference to the timer itself. If you want to keep track of the count down, you can have define properties to keep track of that:
#interface ViewController ()
#property (nonatomic, weak) IBOutlet UILabel *label;
#property (nonatomic, weak) NSTimer *timer;
#property (nonatomic, strong) NSDateComponentsFormatter *formatter;
#property (nonatomic, strong) NSDate *stopTime;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.formatter = [[NSDateComponentsFormatter alloc] init];
self.formatter.allowedUnits = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
self.formatter.unitsStyle = NSDateComponentsFormatterUnitsStylePositional;
self.formatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorPad;
self.stopTime = [[NSDate date] dateByAddingTimeInterval:5 * 60]; // in 5 minutes, for example
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(handleTimer:) userInfo:nil repeats:true];
[self.timer fire]; // don't wait one second before firing the first time; fire now
}
- (void)handleTimer:(NSTimer *)timer {
NSDate *now = [NSDate date];
if ([now compare:self.stopTime] == NSOrderedDescending) {
// do whatever you want when timer stops
[timer invalidate];
return;
}
self.label.text = [self.formatter stringFromDate:now toDate:self.stopTime];
}
// Note, when the view disappears, invalidate the timer so the timer doesn't
// keep strong reference to the view controller. Note that in this selector-based
// pattern, I can't attempt to do this in `dealloc`, because the scheduled timer
// will keep a strong reference, preventing `dealloc` from getting called. So do
// this in `viewDidDisappear`.
- (void)viewDidDisappear:(BOOL)animated {
[self.timer invalidate];
}
#end
If only supporting iOS 10 and later, I'd suggest using the completion block timer, as it simplifies the process even further:
#interface ViewController ()
#property (nonatomic, weak) IBOutlet UILabel *label;
#property (nonatomic, weak) NSTimer *timer;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSDateComponentsFormatter *formatter = [[NSDateComponentsFormatter alloc] init];
formatter.allowedUnits = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
formatter.unitsStyle = NSDateComponentsFormatterUnitsStylePositional;
formatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorPad;
NSDate *stopTime = [[NSDate date] dateByAddingTimeInterval:5 * 60]; // in 5 minutes, for example
typeof(self) __weak weakSelf = self; // make sure to not reference `self` in block below, but only reference `weakSelf` to avoid timer from maintaining strong reference
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:true block:^(NSTimer * _Nonnull timer) {
NSDate *now = [NSDate date];
if ([now compare:stopTime] == NSOrderedDescending) {
// do whatever you want when timer stops
[timer invalidate];
return;
}
weakSelf.label.text = [formatter stringFromDate:now toDate:stopTime];
}];
[self.timer fire]; // don't wait one second before firing the first time; fire now
}
// Because I was careful to not reference `self` inside the above block,
// this block-based timer will NOT keep a strong reference to the view
// controller. Nonetheless, when the view controller is dismissed, I want
// to stop the timer, to avoid wasting CPU cycles on a timer that isn't
// needed anymore.
- (void)dealloc {
[self.timer invalidate];
}
#end
Clearly, if you need to support iOS 9 and earlier, too, then you have to use the aforementioned selector-based solution.
I'd suggest not relying on the timer to adjust the time, as you're not assured that the timer will be called with the frequency you want. In both of the above examples, I capture to what time I'm counting down, and just display the amount of time between "now" and that scheduled "stop time".
Note, I'm also suggesting that you get out of the business of building the format string yourself. There's a convenient NSDateComponentsFormatter which can format this for you. Use it, if you can.
You made your timer reference strong. You can make it weak because a scheduled timer is not deallocated until it's invalidated. And once it's invalidated, it's convenient to have it be deallocated automatically for you.
Your action method cannot work.
If a parameter is passed it must be a NSTimer instance
- (void)updateCountdown:(NSTimer *)timer
To pass custom parameters use the userInfo argument.
Nevertheless a more suitable solution is to use an instance variable or property because the value of userInfo must be an object like NSNumber
Create property
#property (nonatomic) int secondsLeft;
in viewDidLoad
self.secondsLeft = 5 * 60; // 5 minutes
_timer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target:self selector:#selector(updateCountdown) userInfo:nil repeats: YES];
Selector:
-(void)updateCountdown{
int secondsLeft = self.secondsLeft;
if (secondsLeft >= 0) {
int minutes, seconds;
int hours;
self.secondsLeft--;
hours = secondsLeft / 3600;
minutes = (secondsLeft % 3600) / 60;
seconds = (secondsLeft %3600) % 60;
NSString *time = [NSString stringWithFormat:#"%02d:%02d:%02d", hours, minutes, seconds];
_countDownlabel.text = [self timeFormatted:secondsLeft];
}
if (secondsLeft <= 0) {
NSLog(#"TIME ENDS");
if ([self.timer isValid]) {
[self.timer invalidate];
self.timer = nil;
}
}
}
It is a really odd situation and I have never had a problem like this, and I would really appreciate any help that I get on this post. I have made a simple game. I will give .h and .m below.
Game30s.h :
//
// Game30s.h
// Speedy
//
// Created by Ajay Venkat on 15/10/2014.
// Copyright (c) 2014 AJTech. All rights reserved.
//
#import <UIKit/UIKit.h>
#interface Game30s : UIViewController {
IBOutlet UILabel *scorelabel;
IBOutlet UIButton *bar1;
IBOutlet UIButton *bar2;
BOOL isCracked;
BOOL isCracked2;
NSTimer *go;
NSTimer *collision;
IBOutlet UIImageView *wall;
IBOutlet UIButton *start;
IBOutlet UIButton *contin;
NSTimer *time;
NSTimer *timestart;
int timem;
IBOutlet UILabel *timemanage;
int score;
}
-(void)tensecs;
-(void)timetwo;
-(void)goRight;
-(void)col;
-(IBAction)Statt;
-(IBAction)Tap1;
-(IBAction)Tap2;
#property (nonatomic) BOOL gameCenterEnabled;
#property (nonatomic, strong) NSString *leaderboardIdentifier;
#end
Game30s.m :
//
// Game30s.m
// Speedy
//
// Created by Ajay Venkat on 15/10/2014.
// Copyright (c) 2014 AJTech. All rights reserved.
//
#import "Game30s.h"
#import GameKit;
#interface Game30s()
#end
#implementation Game30s {
int cracknum;
float speed;
int score2;
int totscore;
}
- (void)viewDidLoad {
[super viewDidLoad];
timestart = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(timetwo) userInfo:nil repeats:YES];
contin.hidden = YES;
start.hidden = NO;
bar1.hidden = YES;
bar2.hidden = YES;
[self authenticateLocalPlayer];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
-(IBAction)Tap1 {
int w = ([[UIScreen mainScreen] bounds].size.width);
int h = ([[UIScreen mainScreen] bounds].size.height);
w = w / 2;
int r1x = (arc4random() %240) + w ;
int r1y = (arc4random() %h);
bar1.center = CGPointMake(r1x, r1y);
speed = speed + 0.5;
score = score + 1;
}
-(IBAction)Tap2 {
int w = ([[UIScreen mainScreen] bounds].size.width);
int h = ([[UIScreen mainScreen] bounds].size.height);
w = w / 2;
int r2x = (arc4random() %240) + w ;
int r2y = (arc4random() %h);
speed = speed + 0.7;
bar2.center = CGPointMake(r2x, r2y);
score = score + 1;
}
-(void)goRight {
bar1.center = CGPointMake(bar1.center.x - speed, bar1.center.y);
bar2.center = CGPointMake(bar2.center.x - speed, bar2.center.y);
}
-(void)tensecs {
if (timem > 0) {
timem = timem - 1;
NSString* myString = [#(timem) stringValue];
timemanage.text = myString;
} else if (timem == 0) {
[timestart invalidate];
NSString* myString = [#(score) stringValue];
scorelabel.text = myString;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setInteger:score forKey:#"thirtysecond"];
bar1.hidden = YES;
bar2.hidden = YES;
start.hidden = NO;
start.enabled = YES;
contin.hidden = NO;
contin.enabled = YES;
[go invalidate];
[time invalidate];
}
}
-(void)col {
if (CGRectIntersectsRect(bar1.frame, wall.frame)) {
int r2x = (arc4random() %300) + 300 ;
int r2y = (arc4random() %375);
bar2.center = CGPointMake(r2x, r2y);
int r1x = (arc4random() %300) + 300 ;
int r1y = (arc4random() %375);
bar1.center = CGPointMake(r1x, r1y);
}
if (CGRectIntersectsRect(bar2.frame, wall.frame)) {
int r2x = (arc4random() %300) + 300 ;
int r2y = (arc4random() %375);
bar2.center = CGPointMake(r2x, r2y);
int r1x = (arc4random() %300) + 300 ;
int r1y = (arc4random() %375);
bar1.center = CGPointMake(r1x, r1y);
}
}
-(IBAction)Statt {
timem = 30;
score = 0;
contin.hidden = YES;
contin.enabled = NO;
time = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(tensecs) userInfo:nil repeats:YES];
bar1.hidden = NO;
bar2.hidden = NO;
int r2x = (arc4random() %300) + 300 ;
int r2y = (arc4random() %375);
bar2.center = CGPointMake(r2x, r2y);
int r1x = (arc4random() %300) + 300 ;
int r1y = (arc4random() %375);
bar1.center = CGPointMake(r1x, r1y);
cracknum = 0;
go = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(goRight) userInfo:nil repeats:YES];
collision = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:#selector(col) userInfo:nil repeats:YES];
speed = 3;
start.hidden = YES;
start.enabled = NO;
}
-(void)authenticateLocalPlayer{
// Instantiate a GKLocalPlayer object to use for authenticating a player.
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
localPlayer.authenticateHandler = ^(UIViewController *viewController, NSError *error) {
if (viewController != nil) {
// If it's needed display the login view controller.
[self presentViewController:viewController animated:YES completion:nil];
} else {
if ([GKLocalPlayer localPlayer].authenticated) {
// If the player is already authenticated then indicate that the Game Center features can be used.
_gameCenterEnabled = YES;
// Get the default leaderboard identifier.
[[GKLocalPlayer localPlayer] loadDefaultLeaderboardIdentifierWithCompletionHandler:^(NSString *leaderboardIdentifier, NSError *error) {
if (error != nil) {
NSLog(#"%#", [error localizedDescription]);
} else{
_leaderboardIdentifier = #"thirtysecondtappers";
}
}];
} else {
_gameCenterEnabled = NO;
}
}
};
}
#end
So this is the code. In the method -(void)tensecs I have said:
NSString* myString = [#(timem) stringValue];
timemanage.text = myString;
And I linked up everything properly! Then what happens in the simulator or on any device is that the time label counts down properly from 30 down to 0. But every time a number goes down for example 29 to 28 the UIImageViews reset to their home location on the screen.
See I show screen shots here.
I tried deleting that code
NSString* myString = [#(timem) stringValue];
timemanage.text = myString;
and then it did not do this anymore. I don't know why this is happening!
Summary:
The UIImageView is going back to its home location when the timer changes a number on label! Also I have added constraints to the image on the actual designer. Is this why?
This is because of auto layout. If you haven't turned off auto layout, and move views by setting their frames, then when the view needs to redraw (like when your score label changes), the view you moved will revert to the position defined by its constraints. To fix this, you either need to reposition your view by adjusting the constraints, or turn off auto layout.
Ok, so I've based my stopwatch app code from this tutorial right here http://iphonedev.tv/blog/2013/7/7/getting-started-part-3-adding-a-stopwatch-with-nstimer-and-our-first-class
I like the way it is set up, but I can't figure out how to add hundredths of a second to it, anyone know how to do this?
My ViewController.m file
#import "ViewController.h"
#import "Foundation/Foundation.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (NSTimer *)createTimer
{
return [NSTimer scheduledTimerWithTimeInterval:0.01
target:self
selector:#selector(timerTicked:)
userInfo:nil
repeats:YES];
}
- (void)timerTicked:(NSTimer *)timer
{
_currentTimeInSeconds++;
self.timeLabel.text = [self formattedTime:_currentTimeInSeconds];
}
- (NSString *)formattedTime:(int)totalSeconds
{
int hundredths = totalSeconds % 60;
int seconds = totalSeconds % 60;
int minutes = (totalSeconds / 60) % 60;
int hours = totalSeconds / 3600;
return [NSString stringWithFormat:#"%02d:%02d:%02d.%02d", hours, minutes, seconds, hundredths];
}
- (IBAction)startButtonPressed:(id)sender
{
if (!_currentTimeInSeconds)
{
_currentTimeInSeconds = 0 ;
}
if (!_theTimer)
{
_theTimer = [self createTimer];
}
}
- (IBAction)stopButtonPressed:(id)sender
{
[_theTimer invalidate];
}
- (IBAction)resetButtonPressed:(id)sender
{
if (_theTimer)
{
[_theTimer invalidate];
_theTimer = [self createTimer];
}
_currentTimeInSeconds = 0;
self.timeLabel.text = [self formattedTime:_currentTimeInSeconds];
}
#end
Thanks again for anybody who can help!
First, you should change the name of your variable from _currentTimeInSeconds to _currentTimeInHundredths (or something shorter if you want).
Next, you need to update the logic in your - (NSString *)formattedTime:(int)totalSeconds method. Try something like this (changing totalSeconds to totalHundredths for the same reason as before).
int hours = totalHundredths / 360000;
int minutes = (totalHundredths - (hours * 360000)) / 6000;
int seconds = (totalHundredths - (hours * 360000) - (minutes * 6000)) / 100;
int hundredths = totalHundredths - (hours * 360000) - (minutes * 6000) - (seconds * 100);
I haven't tested the math on the numbers, but they should be right.
I have both a UILabel and UIProgressView that I'm giving multiple updates to, but nothing is rendered until my communication object is finished.
I've got this ViewController:
#interface UpdateServerViewController : UIViewController <TaskController> {
AgentAssetManager * agentAssetManager;
}
#property (strong, nonatomic) IBOutlet UIButton * updateNowButton;
#property (strong, nonatomic) IBOutlet UILabel * statusLabel;
#property (strong, nonatomic) IBOutlet UILabel * lastUpdateSuccessTimeLabel;
#property (strong, nonatomic) IBOutlet UILabel * lastUpdateAttemptTimeLabel;
#property (strong, nonatomic) IBOutlet UILabel * lastUpdateResultLabel;
#property (strong, nonatomic) IBOutlet UIProgressView * progressView;
- (IBAction) updateNow:(id) sender;
- (void) updateLabels;
- (void) scheduleInNextRunloopSelector:(SEL)selector;
- (void) taskSetStatus:(NSString *)status;
- (void) taskFinishedSuccessfully;
- (void) taskFinishedUnSuccessfully;
#end
The updateNow method calls AgentAssetManager:
[self scheduleInNextRunloopSelector:#selector(updateTime:)];
McDbg(m, d+1, #"Calling AgentAssetManager sendToServer");
// [statusLabel setText:#"Contacting Server"];
[agentAssetManager sendToServer:true taskController:self];
The AgentAssetManager sendToServer is performs a series of steps and sends notifications back to UpdateServerViewController via the TaskController protocol:
- (void) sendToServer:(Boolean) notifyUser
taskController:(id<TaskController>) taskCtlr
{
char * m = "AgentAssetManager.sendToServer";
int d = 0;
int steps = 6; // Total number of steps
McDbg(m, d, #"Send Asset data to server");
McDbg(m, d+1, #"Notify User about status/errors=%s",
(notifyUser) ? "YES" : "NO");
if (taskCtlr != nil) {
[taskCtlr taskSetProgress:(float)1/(float)steps];
[taskCtlr taskSetStatus:#"Checking if server is reachable"];
}
McDbg(m, d+1, #"SLEEPING");
sleep(5); // XXX tmp debug
ServerStatusManager *statusMgr = [[ServerStatusManager alloc] init];
Boolean ready = [statusMgr isServerReady];
McDbg(m, d+1, #"Server Status ready=%s", (ready) ? "YES" : "NO");
if (!ready) {
NSString *msg = #"Server could not be reached";
McLogWarn(m, #"Server Not ready %#", [statusMgr serverUrlBase]);
[self updateResult:false];
if (notifyUser)
[McUiError showMessage:msg];
if (taskCtlr) {
[taskCtlr taskSetStatus:msg];
[taskCtlr taskFinishedUnSuccessfully];
}
return;
}
McDbg(m, d+1, #"Getting Asset data");
if (taskCtlr != nil) {
[taskCtlr taskSetProgress:(float)2/(float)steps];
[taskCtlr taskSetStatus:#"Gathering data"];
}
... etc ....
Here are UpdateServerViewController taskSetProgress and taskSetStatus:
- (void) taskSetStatus:(NSString *) status
{
char * m = "UpdateServerViewController.taskSetStatus";
int d = 0;
McDbg(m, d, #"Status=<%#>", status);
[statusLabel setText:status];
McDbg(m, d+1, #"Finished");
}
- (void) taskSetProgress:(float) percent
{
[progressView setHidden:false];
[progressView setProgress:percent animated:YES];
}
The debug output clearly shows that UpdateServerViewController taskSetProgress and taskSetStatus are called when they are suppose to. The problem is that the new set values don't take effect until the entire agentAssetManager.updateNow method is complete.
Based on searches on this site for similar UILabel problems I added a timer method:
- (void) scheduleInNextRunloopSelector:(SEL)selector
{
char * m = "UpdateServerViewController.scheduleInNext";
int d = 0;
McDbg(m, d, #"Starting");
NSDate *fireDate = [[NSDate alloc] initWithTimeIntervalSinceNow:0.5]; // 500 ms
NSTimer *timer = [[NSTimer alloc]
initWithFireDate:fireDate interval:0.5 target:self
selector:selector userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}
- (void)updateTime:(NSTimer *)timer
{
char * m = "UpdateServerViewController.updateTime";
int d = 0;
McDbg(m, d, #"Starting");
[statusLabel setText:#"XXX Timer called"];
}
The problem is the same with or without the timer. With the timer, the debug shows that the timer is scheduled before agentAssetManager.updateNow is called, but the updateTime method is not called until agentAssetManager.updateNow finishes. This is even with a sleep(5) in updateNow to try to avoid a thread race condition.
Debugging shows that all of UpdateServerViewController and AssetAgentManager seems to run in thread 1. I don't do any NSRunLoop other than the above mentioned timer.
I must be missing something basic hear. Any help would really be appreciated!
Make sure to make any ui changes in the main thread.
You may try something like this:
dispatch_sync(dispatch_get_main_queue(), ^{
[statusLabel setText:#"XXX Timer called"];
}
);