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];
}
Related
Has anyone else noticed a problem with complication entries not updating properly. I've just added some initial support to my app, but noticed that they weren't displaying what i expected them to display. For example's sake, and for ease of testing this issue quickly I'd create a timeline
A -> B -> C
0s 10s 20s
Yet all I'd see was complication entry A staying around past the time that B and C should have displayed.
My normal app itself isn't set to create regularly spaced complications like this, it has many aspects of timers that it exposes that can be set by the user, but one such aspect just allows the user to start multiple timers at once, which all will finish after the user defined durations they choose. Unlike the iOS clock app's timer you're able to specify the timer durations in seconds, and so its perfectly possible that 2 timers would finish within seconds of each other, on the whole though its more likely that they'll be minutes apart. Also there shouldn't be too many complication entries being added, though other more complicated aspects of my app could easily add 10s or even ~100 complication entries depending on how complex a task the user has setup within it. For now though, this simpler example is easier to discuss and test.
I updated Xcode to the latest version (7.3.2) with no improvements, and sent a build to my actual phone and watch and again no improvement. Until once it did work. On further debugging I discovered that i could make the timeline behave itself by simply lowering my watch (to turn off the screen) and then wake it up again, all whilst it was mid executing my timeline. Having done this the timeline would then work properly from then on.
I've created a test app to demonstrate the problem, which does reproduce the problem fully, so I'm going to send it to apple on a bug report. Just thought i'd see if anyone else had noticed this issue.
Also when my test app executes i get the following logging output with an error that doesn't make sense
-[ExtensionDelegate session:didReceiveUserInfo:]:67 - complication.family=1 in activeComplications - calling reloadTimelineForComplication
-[ComplicationController getTimelineStartDateForComplication:withHandler:]:43 - calling handler for startDate=2016-06-15 22:08:26 +0000
-[ComplicationController getTimelineEndDateForComplication:withHandler:]:73 - calling handler for endDate=2016-06-15 22:08:46 +0000
-[ComplicationController getCurrentTimelineEntryForComplication:withHandler:]:148 - calling handler for entry at date=2016-06-15 22:08:26 +0000
-[ComplicationController getTimelineEntriesForComplication:afterDate:limit:withHandler:]:202 - adding entry at date=2016-06-15 22:08:36 +0000; with timerEndDate=2016-06-15 22:08:46 +0000 i=1
getTimelineEntriesForComplication:afterDate:limit:withHandler: -- invalid entries returned. (1 entries before start date 2016-06-15 22:08:46 +0000). Excess entries will be discarded.
The relevant information from this log is as follows
getTimelineStartDateForComplication - calling handler for startDate=22:08:26
getTimelineEndDateForComplication - calling handler for endDate=22:08:46
getCurrentTimelineEntryForComplication - calling handler for entry at date=22:08:26
getTimelineEntriesForComplication:afterDate - adding entry at date=22:08:36
getTimelineEntriesForComplication:afterDate:limit:withHandler: -- invalid entries returned. (1 entries before start date 22:08:46). Excess entries will be discarded.
Which you can see in the error from the system at the end that it is using the start date of 22:08:46, which was actually what I told Clockkit was my timeline's endDate, NOT the startDate. I'm not sure if this is related to the behaviour i'm seeing as I see the same error when it works after I hide/show the screen.
I've put a video of this behaviour in my test app online here. The details of this test app are as follows
Full code that should just run in the relevant simulators is available here, the relevant complication modules are also listed here for reference.
In my extension delegate, i receive userInfo from the iOS app and schedule a reload of my complication timeline
ExtensionDelegate.m
- (void)session:(WCSession *)session didReceiveUserInfo:(NSDictionary<NSString *, id> *)userInfo
{
DbgLog(#"");
WKExtension *extension = [WKExtension sharedExtension];
DbgLog(#"self=%p; wkExtension=%p; userInfo=%#", self, extension, userInfo);
self.lastReceivedUserInfo = userInfo;
CLKComplicationServer *complicationServer = [CLKComplicationServer sharedInstance];
for (CLKComplication *complication in complicationServer.activeComplications)
{
DbgLog(#"complication.family=%d in activeComplications - calling reloadTimelineForComplication", complication.family);
[complicationServer reloadTimelineForComplication:complication];
}
}
Then in my ComplicationController are the following methods to handle the complication side of things
ComplicationController.m
#define DbgLog(fmt, ...) NSLog((#"%s:%d - " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
#interface ComplicationController ()
#end
#implementation ComplicationController
#pragma mark - Timeline Configuration
- (void)getSupportedTimeTravelDirectionsForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationTimeTravelDirections directions))handler
{
handler(CLKComplicationTimeTravelDirectionForward|CLKComplicationTimeTravelDirectionBackward);
}
- (void)getTimelineStartDateForComplication:(CLKComplication *)complication withHandler:(void(^)(NSDate * __nullable date))handler
{
NSDate *startDate;
WKExtension *extension = [WKExtension sharedExtension];
assert(extension.delegate);
assert([extension.delegate isKindOfClass:[ExtensionDelegate class]]);
ExtensionDelegate *extensionDelegate = (ExtensionDelegate *)extension.delegate;
if (extensionDelegate.lastReceivedUserInfo)
{
NSDictionary *userInfo = extensionDelegate.lastReceivedUserInfo;
startDate = [userInfo objectForKey:#"date"];
}
DbgLog(#"calling handler for startDate=%#", startDate);
handler(startDate);
}
- (NSDate*)getTimelineEndDate
{
NSDate *endDate;
WKExtension *extension = [WKExtension sharedExtension];
assert(extension.delegate);
assert([extension.delegate isKindOfClass:[ExtensionDelegate class]]);
ExtensionDelegate *extensionDelegate = (ExtensionDelegate *)extension.delegate;
if (extensionDelegate.lastReceivedUserInfo)
{
NSDictionary *userInfo = extensionDelegate.lastReceivedUserInfo;
NSDate *startDate = [userInfo objectForKey:#"date"];
NSNumber *duration = [userInfo objectForKey:#"duration"];
NSNumber *count = [userInfo objectForKey:#"count"];
NSTimeInterval totalDuration = duration.floatValue * count.floatValue;
endDate = [startDate dateByAddingTimeInterval:totalDuration];
}
return endDate;
}
- (void)getTimelineEndDateForComplication:(CLKComplication *)complication withHandler:(void(^)(NSDate * __nullable date))handler
{
NSDate *endDate=[self getTimelineEndDate];
DbgLog(#"calling handler for endDate=%#", endDate);
handler(endDate);
}
- (void)getPrivacyBehaviorForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationPrivacyBehavior privacyBehavior))handler {
handler(CLKComplicationPrivacyBehaviorShowOnLockScreen);
}
#pragma mark - Timeline Population
- (CLKComplicationTemplate *)getComplicationTemplateForComplication:(CLKComplication *)complication
forEndDate:(NSDate *)endDate
orBodyText:(NSString *)bodyText
withHeaderText:(NSString *)headerText
{
assert(complication.family == CLKComplicationFamilyModularLarge);
CLKComplicationTemplateModularLargeStandardBody *template = [[CLKComplicationTemplateModularLargeStandardBody alloc] init];
template.headerTextProvider = [CLKSimpleTextProvider textProviderWithText:headerText];
if (endDate)
{
template.body1TextProvider = [CLKRelativeDateTextProvider textProviderWithDate:endDate style:CLKRelativeDateStyleTimer units:NSCalendarUnitHour|NSCalendarUnitMinute|NSCalendarUnitSecond];
}
else
{
assert(bodyText);
template.body1TextProvider = [CLKSimpleTextProvider textProviderWithText:bodyText];
}
return template;
}
- (CLKComplicationTimelineEntry *)getComplicationTimelineEntryForComplication:(CLKComplication *)complication
forStartDate:(NSDate *)startDate
endDate:(NSDate *)endDate
orBodyText:(NSString *)bodyText
withHeaderText:(NSString *)headerText
{
CLKComplicationTimelineEntry *entry = [[CLKComplicationTimelineEntry alloc] init];
entry.date = startDate;
entry.complicationTemplate = [self getComplicationTemplateForComplication:complication forEndDate:endDate orBodyText:bodyText withHeaderText:headerText];
return entry;
}
- (void)getCurrentTimelineEntryForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationTimelineEntry * __nullable))handler
{
// Call the handler with the current timeline entry
CLKComplicationTimelineEntry *entry;
assert(complication.family == CLKComplicationFamilyModularLarge);
WKExtension *extension = [WKExtension sharedExtension];
assert(extension.delegate);
assert([extension.delegate isKindOfClass:[ExtensionDelegate class]]);
ExtensionDelegate *extensionDelegate = (ExtensionDelegate *)extension.delegate;
if (extensionDelegate.lastReceivedUserInfo)
{
NSDictionary *userInfo = extensionDelegate.lastReceivedUserInfo;
NSDate *startDate = [userInfo objectForKey:#"date"];
NSNumber *duration = [userInfo objectForKey:#"duration"];
//NSNumber *count = [userInfo objectForKey:#"count"];
NSTimeInterval totalDuration = duration.floatValue;
NSDate *endDate = [startDate dateByAddingTimeInterval:totalDuration];
entry = [self getComplicationTimelineEntryForComplication:complication forStartDate:startDate endDate:endDate orBodyText:nil withHeaderText:#"current"];
}
if (!entry)
{
NSDate *currentDate = [NSDate date];
entry = [self getComplicationTimelineEntryForComplication:complication forStartDate:currentDate endDate:nil orBodyText:#"no user info" withHeaderText:#"current"];
}
DbgLog(#"calling handler for entry at date=%#", entry.date);
handler(entry);
}
- (void)getTimelineEntriesForComplication:(CLKComplication *)complication beforeDate:(NSDate *)date limit:(NSUInteger)limit withHandler:(void(^)(NSArray<CLKComplicationTimelineEntry *> * __nullable entries))handler
{
NSArray *retArray;
assert(complication.family == CLKComplicationFamilyModularLarge);
WKExtension *extension = [WKExtension sharedExtension];
assert(extension.delegate);
assert([extension.delegate isKindOfClass:[ExtensionDelegate class]]);
ExtensionDelegate *extensionDelegate = (ExtensionDelegate *)extension.delegate;
if (extensionDelegate.lastReceivedUserInfo)
{
NSDictionary *userInfo = extensionDelegate.lastReceivedUserInfo;
NSDate *startDate = [userInfo objectForKey:#"date"];
if ([startDate timeIntervalSinceDate:date] < 0.f)
{
assert(0);
// not expected to be asked about any date earlier than our startDate
}
}
// Call the handler with the timeline entries prior to the given date
handler(retArray);
}
- (void)getTimelineEntriesForComplication:(CLKComplication *)complication afterDate:(NSDate *)date limit:(NSUInteger)limit withHandler:(void(^)(NSArray<CLKComplicationTimelineEntry *> * __nullable entries))handler
{
NSMutableArray *timelineEntries = [[NSMutableArray alloc] init];
assert(complication.family == CLKComplicationFamilyModularLarge);
WKExtension *extension = [WKExtension sharedExtension];
assert(extension.delegate);
assert([extension.delegate isKindOfClass:[ExtensionDelegate class]]);
ExtensionDelegate *extensionDelegate = (ExtensionDelegate *)extension.delegate;
if (extensionDelegate.lastReceivedUserInfo)
{
NSDictionary *userInfo = extensionDelegate.lastReceivedUserInfo;
NSDate *startDate = [userInfo objectForKey:#"date"];
NSNumber *duration = [userInfo objectForKey:#"duration"];
NSNumber *count = [userInfo objectForKey:#"count"];
NSInteger i;
for (i=0; i<count.integerValue && timelineEntries.count < limit; ++i)
{
NSTimeInterval entryDateOffset = duration.floatValue * i;
NSDate *entryDate = [startDate dateByAddingTimeInterval:entryDateOffset];
if ([entryDate timeIntervalSinceDate:date] > 0)
{
NSDate *timerEndDate = [entryDate dateByAddingTimeInterval:duration.floatValue];
DbgLog(#"adding entry at date=%#; with timerEndDate=%# i=%d", entryDate, timerEndDate, i);
CLKComplicationTimelineEntry *entry = [self getComplicationTimelineEntryForComplication:complication forStartDate:entryDate endDate:timerEndDate orBodyText:nil withHeaderText:[NSString stringWithFormat:#"After %d", i]];
[timelineEntries addObject:entry];
}
}
if (i==count.integerValue && timelineEntries.count < limit)
{
NSDate *timelineEndDate = [self getTimelineEndDate];
CLKComplicationTimelineEntry *entry = [self getComplicationTimelineEntryForComplication:complication forStartDate:timelineEndDate endDate:nil orBodyText:#"Finished" withHeaderText:#"Test"];
[timelineEntries addObject:entry];
}
}
NSArray *retArray;
if (timelineEntries.count > 0)
{
retArray = timelineEntries;
}
// Call the handler with the timeline entries after to the given date
handler(retArray);
}
#pragma mark Update Scheduling
/*
// don't want any updates other than the ones we request directly
- (void)getNextRequestedUpdateDateWithHandler:(void(^)(NSDate * __nullable updateDate))handler
{
// Call the handler with the date when you would next like to be given the opportunity to update your complication content
handler(nil);
}
*/
- (void)requestedUpdateBudgetExhausted
{
DbgLog(#"");
}
#pragma mark - Placeholder Templates
- (void)getPlaceholderTemplateForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationTemplate * __nullable complicationTemplate))handler
{
CLKComplicationTemplate *template = [self getComplicationTemplateForComplication:complication forEndDate:nil orBodyText:#"doing nothing" withHeaderText:#"placeholder"];
// This method will be called once per supported complication, and the results will be cached
handler(template);
}
#end
Perhaps you can see if you have the same problems with your own complications in your own apps.
I don't think I'm doing anything wrong, nothing that should cause this odd behaviour, just feels like a bug to me. Unfortunately its one that undermines my app which can work with quite small scale timeline entries in some cases and I'd rather not have them just not work if a user pays attention and keeps the watch screen on whilst testing it out.
Thanks for your time,
Cheers
The error about an entry before the start date is accurate, and the entry is correctly discarded.
The reason why is that getTimelineEntriesForComplication:afterDate: is meant to return future entries after the specified date. What you did was return an entry before the specified date.
The starting date for providing future entries. The dates for your timeline entries should occur after this date and be as close to the date as possible.
Without any posted code to examine, I'd guess that your beforeDate:/afterDate: conditional code is reversed.
Other issues regarding multiple timeline entries per minute:
Regarding the ten second time interval, I can point out several issues for you to consider:
The complication server will request a limited number of entries.
Providing your entries will always be ten seconds apart, and that the server requested 100 entries (either in the past or the future direction), that amounts to 1000 seconds worth (under 17 minutes). This introduces two problems in general:
Given the short span of the timeline, the timeline would need to be updated several times an hour. This is not energy efficient, and you may exhaust your daily complication budget if you extend or reload it too often.
With such a short duration, time travel is effectively useless, as rotating the digital crown could quickly travel more than 16 minutes into the (past or) future, where there would not (initially) be any further entries to display.
The complication server caches a limited number of entries.
Even if you extend your timeline, the server will eventually prune (discard) entries from an extended timeline. Again assuming a 10-second interval, the complication server would only be able to keep about 2 hours worth of entries cached. Furthermore, you have no control over which entries would be discarded.
From a time travel perspective, 5 of every 6 timeline entries would never be displayed while time traveling (since time travel changes by minutes).
This would certainly present some confusion to the user, as they would not be able to view every entry.
A note about update limits:
While you could add multiple entries per minute, you may want to rethink whether that is practical. Considering that most users won't observe most of the frequent changes, it's likely a lot of wasted effort for little gain.
Regarding energy efficiency, you should also consider the hard limits that Apple imposes for updating the complication. Even in watchOS 3 (where you're encouraged to keep the complication and dock snapshot regularly updated in the background), you'll run up against limits of 4 background updates per hour, and 50 complication updates per day. See watchOS - Show realtime departure data on complication for specifics.
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.
}
I have a WKInterfaceController for a watchkit app, InterfaceController.m. It creates paginated content like this:
- (void)willActivate {
[super willActivate];
NSDate *now = [[NSDate alloc] init];
NSDictionary *time = #{#"time":[NSString stringWithFormat:#"%f",[now timeIntervalSince1970]]};
NSArray *contexts = [[NSArray alloc] initWithObjects:time,time,nil];
NSArray *names = [[NSArray alloc] initWithObjects:#"page",#"page",nil];
[WKInterfaceController reloadRootControllersWithNames:names contexts:contexts];
}
The WKInterfaceController with "page" as the identifier is called PageInterfaceController.m and just displays the time sent by InterfaceController.m
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
[label setText:[context objectForKey:#"time"]];
}
This works great the first time it's loaded, but after exiting and relaunching the app, it shows the old times. How can I get the app to go back to InterfaceController.m and update the times?
Yes, I know I could just put the time in PageInterfaceController, but in my real app, the InterfaceController is getting location data and sending it to the pages, even determining the number of pages to be created.
You can download the whole sample project here:
https://github.com/tomkincaid/WatchInterfaceTest
Workaround is to use a timer and make it go back to the first interface controller after a certain time. Not ideal as I wish there was a way to have it start from the first interface controller every time the watch app is launched.
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
time = [context objectForKey:#"time"];
}
- (void)willActivate {
[super willActivate];
NSDate *now = [[NSDate alloc] init];
double diff = [now timeIntervalSince1970] - [time doubleValue];
if (diff > 30) {
NSArray *contexts = [[NSArray alloc] initWithObjects:#"",nil];
NSArray *names = [[NSArray alloc] initWithObjects:#"main",nil];
[WKInterfaceController reloadRootControllersWithNames:names contexts:contexts];
} else {
[label setText:[NSString stringWithFormat:#"%#",time]];
}
awakeWithContext will not be called a second time if the watch keeps the controller loaded, which is probably why it doesn't get updated after the initial load.
Instead, use viewWillActivate in your PageInterfaceController just like you do in your main InterfaceController, like this:
- (void)viewWillActivate {
[super viewWillActivate];
[label setText:[context objectForKey:#"time"]];
}
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 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