I have a label that displays the time; however, the time is not updated. The time is displayed, but it does not count up. The time at which the button was pressed is displayed and that time does not change. Here is my code
- (IBAction)startCamera:(id)sender
{
[self.videoCamera start];
NSDate *today = [NSDate date];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"HH:mm:ss"];
NSString *currentTime = [dateFormatter stringFromDate:today];
[dateFormatter setDateFormat:#"dd.MM.yyyy"];
NSString *currentDate = [dateFormatter stringFromDate:today];
for (int i = 1; i <= 10; i--) {
Label1.text = [NSString stringWithFormat:#"%#", currentTime];
Label2.text = [NSString stringWithFormat:#"%#", currentDate];
}
}
I tried a for loop but that does not update the time. Any suggestions?
UI updates are performed using an event loop that runs on the main thread. Your for-loop is hogging the main thread and never returns from you start function. Whatever you set in labelx.text never gets refreshed on screen because the run loop is waiting for your start function to finish.
You should read up on NSTimer to implement this using best practices.
There is also a way to do this using a delayed dispatch:
(sorry that this is in Swift, I don't know objective-C, but I'm sure you'll get the idea)
// add this function and call it in your start function
func updateTime()
{
// update label1 and label2 here
// also add an exit condition to eventually stop
let waitTime = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC ) // one second wait duration
dispatch_after(waitTime, dispatch_get_main_queue(), {self.updateTime() }) // updateTime() calls itself every 1 second
}
NSTimer works but its not very accurate.
When I need accurate timers I use CADisplaylink, especially when working with animations. This reduces visual stutter.
Using the display refresh is accurate and reliable. However you don't want to be doing heavy calculations using this method.
- (void)startUpdateLoop {
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(update:)];
displayLink.frameInterval = 60;
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)update {
// set you label text here.
}
Related
I try to use the Bluetooth communication synchronously. I send the data to the BTLE device and wait for the response to continue in the same method unless a timeout occurs.
I wanted to use NSRUNLOOP for the wait. However, I have the problem that only when the loop is completed, the data received in the meantime can be processed. So the loop seems to block the processing of the data.
The data is sent in a separate thread
... create command data
if (![self sendCommand:nd_commandData timeOutMesc:ui_timeOutMSec command:ui_command error:error])
{
// Timeout
return false;
}
... continue working with the received data
sending method with timeout:
-(BOOL)sendCommand:(NSData *)nd_sendData timeOutMesc:(uint)ui_timeOutMSec command:(UInt16)ui_command
{
b_exitConditionSleep = false;
b_timeOutOccurred = false;
self.ni_totalResponseLength = 0;
self.ni_expectedResponseLength = 0;
// Clear buffer
memset(&uia_receivedDataBytes[0], 0x00, sizeof(uia_receivedDataBytes));
[[BTLE_Communicator sharedInstance] setDelegate:self];
// send data
[[BTLE_Communicator sharedInstance] sendDataFromAppToBLEDevice:nd_sendData];
BOOL b_rechTimeOut = true;
NSDate *start = [NSDate date];
NSTimeInterval timeInterval;
uint ui_differenz;
NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.01];
while (!b_exitConditionSleep && [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate:loopUntil])
{
loopUntil = [NSDate dateWithTimeIntervalSinceNow:0.01];
timeInterval = [start timeIntervalSinceNow] * -1;
ui_differenz = round(timeInterval * 1000);
if (ui_differenz >= ui_timeOutMSec) {
NSLog(#"Command: 0x%hX - Timeout reached: %u", ui_command, ui_differenz);
b_rechTimeOut = false;
b_timeOutOccurred = true;
break;
}
}
return b_rechTimeOut;
}
receiving method:
-(void)communicatorPeripheralDidReceiveResponseUARTData:(NSData *)nd_data
{
NSLog(#"ResponseUARTData %#", nd_data);
if (!b_timeOutOccurred)
{
b_exitConditionSleep = true;
[self processResponseData:nd_data];
}
}
Maybe someone has an indication of what I'm doing wrong.
what I'm doing wrong
You're trying to make an asynchronous task synchronous. That's the core problem. As for the specific reason why this particular pattern won't work, it is quite likely that the nested run loop is preventing a callback from being processed properly. Or it might be some other implementation detail that is incompatible with the conversion from asynchronous to synchronous.
Beyond an intellectual curiosity, there isn't much reason to figure out why.
Instead, call some kind of an update method from your implementation of communicatorPeripheralDidReceiveResponseUARTData:. This will allow the processing to be asynchronous, as intended.
There are some games where certain actions are time related. For example, every 24 hours user gets one credit if he played a game ... After we turn off our phone and come back after few hours timer is correctly set.
How is this done ? There must be some method which writes current time into a persistent storage and then reads from it?
I am just guessing it's something like this:
NSDate * now = [NSDate date];
NSDateFormatter *outputFormatter = [[NSDateFormatter alloc] init];
[outputFormatter setDateFormat:#"HH:mm:ss"];
NSString *newDateString = [outputFormatter stringFromDate:now];
And then comes part where newDateString is written into persistent storage.
Also, if this is a method, what happens if user change time manually on his device? Can we rely on this or there are some other methods to track real time passed between certain moments in the game?
You could do this by writing the next award time to NSUserDefaults and comparing previous values.
// Fetch previous "next award" time.
NSNumber *previousTime = [[NSUserDefaults standardUserDefaults] objectForKey:#"next_award_time"];
NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
// If we haven't yet scheduled a "next award" time, or if we've passed it..
if (!previousTime || [previousTime doubleValue] < now) {
// We had a previous value, so we've passed the time.
if (previousTime) {
// Award user.
}
// Set next award time.
NSTimeInterval nextTime = [[NSDate date] timeIntervalSince1970] + 60*60*24;
[[NSUserDefaults standardUserDefaults] setObject:#(nextTime) forKey:#"next_award_time"];
}
As far as worrying about users changing their own device time, you'll need to make a network request to an unbiased third-party clock. This could be a server you control or a well-known time server. You might want to check out this answer for help on how to contact an NTP.
I have an app where content is displayed to the user. I now want to find out how many seconds a user actually views that content for. So in my header file, I've declared an
NSDate *startTime;
NSDate *endTime;
Then in my viewWillAppear
startTime = [NSDate date];
Then in my viewWillDisappear
endTime = [NSDate date];
NSTimeInterval secs = [endTime timeIntervalSinceDate:startTime];
NSLog(#"Seconds --------> %f", secs);
However, the app crashes, with different errors sometimes. Sometimes it's a memory leak, sometimes it's a problem with the NSTimeInterval, and sometimes it crashes after going back to the content for a second time.
Any ideas on to fix this?
since you are not using ARC, when you write
startTime = [NSDate date];
you do not retain startTime, so it is deallocated before -viewWillDisappear is called. Try
startTime = [[NSDate date] retain];
Also, I recommend to use ARC. There should be much less errors with memory management with it, than without it
You should declare a property with retain for the start date. Your date is getting released before you can calculate the time difference.
So declare
#property (nonatomic, retain) NSDate *startDate
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self setStartDate: [NSDate date]];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
NSLog(#"Seconds --------> %f",[[NSDate date] timeIntervalSinceDate: self.startDate]);
}
Don't forget to cleanup.
- (void)dealloc
{
[self.startDate release];
[super dealloc];
}
I have a game where is uses a countdown timer and when the timer is up, you are brougt to a Game over view. I want to add a feature were if they tap a button, it will add like 1, 2 or 3 more seconds to the timer. I already have the code for the timer (Below), but i just need to know how to add more time to the counter. I thought of it and i have to say when the views will switch and it would need to take into a count the added time.
Code:
-(IBAction)Ready:(id)sender {
[self performSelector:#selector(TimerDelay) withObject:nil afterDelay:1.0];
[self performSelector:#selector(delay) withObject:nil afterDelay:36.5];
}
-(void)TimerDelay {
MainInt = 36;
timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(countDownDuration)
userInfo:nil
repeats:YES];
if (timer == 0)
{
[timer invalidate];
}
}
-(void)countDownDuration {
MainInt -= 1;
seconds.text = [NSString stringWithFormat:#"%i", MainInt];
}
-(void)delay {
GameOverView1_4inch *second= [self.storyboard instantiateViewControllerWithIdentifier:#"GameOverView1"];
second.finalScore = self.currentScore;
[self presentViewController:second animated:YES completion:nil];
}
If you're using a timer to manage the game, using a perform selector at the same time (to end the game or anything else) kind of defeats the point and makes the management very complex. Choose one route and stick with it.
When the timer is running, you can change it's fire date using setFireDate:. So, you could get the current fire date, add your time to it and then set the new fire date:
- (void)extendByTime:(NSInteger)seconds
{
NSDate *newFireDate = [[self.timer fireDate] dateByAddingTimeInterval:seconds];
[self.timer setFireDate:newFireDate];
}
Then, your button callbacks are something like:
- (void)buttonOnePressed:(id)sender
{
[self extendByTime:1];
}
- (void)buttonFivePressed:(id)sender
{
[self extendByTime:5];
}
Once you've removed the performSelector which calls delay your game end will be defined by
MainInt reaching zero.
As an aside, don't do this:
if (timer == 0)
The correct approach is:
if (timer == nil)
And if the timer is nil, there's no point in trying to invalidate it...
Also a good idea to take a look at the Objective-C naming guidelines.
Based on your recent comment, it seems that you actually want the timer to continue counting at a second interval, but to add time only to the number of seconds remaining. That's even easier and doesn't require any change to the timer fire date.
- (void)extendByTime:(NSInteger)seconds {
MainInt += seconds;
seconds.text = [NSString stringWithFormat:#"%i", MainInt];
}
And you need to add a check in 'countDownDuration':
if (MainInt <= 0) {
[timer invalidate];
[self delay];
}
To determine when you're done.
You can keep reference of the time you start the timer. Then, when you want to add extra time, calculate how much time has passed since the timer started, invalidate the timer and create a new one passing as time interval the difference between the time left of the previous timer and the extra seconds.
Hy, try to use this:
Put this in -(void)viewDidLoad method
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self
selector:#selector(countDownTimer) userInfo:nil repeats:YES];
then create the -(void)countDownTimer method
- (void)countDownTimer {
// my method which returns the differences between two dates in my case
double diff = [self getDateDifference];
double days = trunc(diff / (60 * 60 * 24));
double seconds = fmod(diff, 60.0);
double minutes = fmod(trunc(diff / 60.0), 60.0);
double hours = fmodf(trunc(diff / 3600.0), 24);
if(diff > 0) {
NSString *countDownString = [NSString stringWithFormat:#"%02.0f day(s)\n%02.0f:%02.0f:%02.0f",
days, hours, minutes, seconds];
// IBOutlet label, added in .h
self.labelCountDown.text= countDownString;
} else {
// stoping the timer
[timer invalidate];
timer = nil;
// do something after countdown ...
}
}
and you can add a - (double)getDateDifference method which returns the difference between two dates in my case
- (double)getDateDifference {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
NSDate *dateFromString = [[NSDate alloc] init];
NSDate *now = [NSDate date];
NSString *myDateString = #"2013-10-10 10:10:10"; // my initial date with time
// if you want to use only time, than delete the
// date in myDateString and setDateFormat:#"HH:mm:ss"
// this line is not required, I used it, because I need GMT+2
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:+2]];
[dateFormatter setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
// get the date
dateFromString = [dateFormatter dateFromString:myDateString];
// this line is also not required, I used it because I need GMT+2
// so I added two hours in seconds to 'now'
now = [now dateByAddingTimeInterval:60*60*2];
// getting the difference
double diff = [dateFromString timeIntervalSinceDate:now];
NSLog(#"dateString: %#", dateString);
NSLog(#"now: %#", now);
NSLog(#"targetDate: %#", dateFromString);
NSLog(#"diff: %f", diff);
return diff;
}
the output is similar to this
dateString: 2013-10-10 00:20:00
now: 2013-10-10 00:20:00 +0000
target: 2013-10-10 00:20:28 +0000
diff: 28.382786
I hope it was helpfull
Here's a library that you can use.
https://github.com/akmarinov/AMClock
Trying to follow the best practices of ReactiveCocoa to update my UI on the hour, every hour. This is what I've got:
NSDateComponents *components = [[[NSCalendar sharedCalendar] calendar] components:NSMinuteCalendarUnit fromDate:[NSDate date]];
// Generalization, I know (not every hour has 60 minutes, but bear with me).
NSInteger minutesToNextHour = 60 - components.minute;
RACSubject *updateEventSignal = [RACSubject subject];
[updateEventSignal subscribeNext:^(NSDate *now) {
// Update some UI
}];
[[[RACSignal interval:(60 * minutesToNextHour)] take:1] subscribeNext:^(id x) {
[updateEventSignal sendNext:x];
[[RACSignal interval:3600] subscribeNext:^(id x) {
[updateEventSignal sendNext:x];
}];
}];
This has some obvious flaws: manual subscription and sending, and it just "feels wrong." Any ideas on how to make this more "reactive"?
You can do this using completely vanilla operators. It's just a matter of chaining the two intervals together while still passing through both of their values, which is exactly what -concat: does.
I would rewrite the subject as follows:
RACSignal *updateEventSignal = [[[RACSignal
interval:(60 * minutesToNextHour)]
take:1]
concat:[RACSignal interval:3600]];
This may not give you super ultra exact precision (because there might be a minuscule hiccup between the two signals), but it should be Good Enough™ for any UI work.
Sounds like you need something like +interval:startingIn:.
With that thought, you could make your own version of +interval:startingIn: by slightly tweaking the implementation of +interval:.
+ (RACSignal *)interval:(NSTimeInterval)interval startingIn:(NSTimeInterval)delay {
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
int64_t intervalInNanoSecs = (int64_t)(interval * NSEC_PER_SEC);
int64_t delayInNanoSecs = (int64_t)(delay * NSEC_PER_SEC);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, delayInNanoSecs), (uint64_t)intervalInNanoSecs, (uint64_t)0);
dispatch_source_set_event_handler(timer, ^{
[subscriber sendNext:[NSDate date]];
});
dispatch_resume(timer);
return [RACDisposable disposableWithBlock:^{
dispatch_source_cancel(timer);
dispatch_release(timer);
}];
}] setNameWithFormat:#"+interval: %f startingIn: %f", (double)interval, (double)delay];
}
With this in place, your code could be refactored to:
NSDateComponents *components = [[[NSCalendar sharedCalendar] calendar] components:NSMinuteCalendarUnit fromDate:[NSDate date]];
// Generalization, I know (not every hour has 60 minutes, but bear with me).
NSInteger minutesToNextHour = 60 - components.minute;
RACSubject *updateEventSignal = [[RACSignal interval:3600 startingIn:(minutesToNextHour * 60)];
[updateEventSignal subscribeNext:^(NSDate *now) {
// Update some UI
}];