Implementing persistent countdown timer - ios

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.

Related

Apple Watch ClockKit Complications don't update their timeline entries if the clock face isn't hidden during execution

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.

How to Create events using Google Calendar API iOS Swift

I want to develop one demo app which will create events using my Application, store it using Google Calendar API and then fetch all the data and gives reminder. I have referred this link to fetch data and for setup, but I am not getting how to create events. Can anyone guide me how can I do this? I searched a lot for creating events using iOS, but I don't get anything useful for Google Calendar API, I found all the stuff using EventKit framework.
Please help me.
Thank You.
I've found a tutorial: "iOS SDK: Working with Google Calendars", in this tutorial they provide insights on downloading the calendars, and creating a new event with a description and a date/time.
Regarding our app structure, the basic view is going to be a table view that will contain three sections for setting the following data:
An event description
An event date/time
A target calendar
A code example for adding event(Objective C):
// Create the URL string of API needed to quick-add the event into the Google calendar.
// Note that we specify the id of the selected calendar.
NSString *apiURLString = [NSString stringWithFormat:#"https://www.googleapis.com/calendar/v3/calendars/%#/events/quickAdd",
[_dictCurrentCalendar objectForKey:#"id"]];
// Build the event text string, composed by the event description and the date (and time) that should happen.
// Break the selected date into its components.
NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
dateComponents = [[NSCalendar currentCalendar] components:NSDayCalendarUnit|NSMonthCalendarUnit|NSYearCalendarUnit|NSHourCalendarUnit|NSMinuteCalendarUnit
fromDate:_dtEvent];
if (_isFullDayEvent) {
// If a full-day event was selected (meaning without specific time), then add at the end of the string just the date.
_strEventTextToPost = [NSString stringWithFormat:#"%# %d/%d/%d", _strEvent, [dateComponents month], [dateComponents day], [dateComponents year]];
}
else{
// Otherwise, append both the date and the time that the event should happen.
_strEventTextToPost = [NSString stringWithFormat:#"%# %d/%d/%d at %d.%d", _strEvent, [dateComponents month], [dateComponents day], [dateComponents year], [dateComponents hour], [dateComponents minute]];
}
// Show the activity indicator view.
[self showOrHideActivityIndicatorView];
// Call the API and post the event on the selected Google calendar.
// Visit https://developers.google.com/google-apps/calendar/v3/reference/events/quickAdd for more information about the quick-add event API call.
[_googleOAuth callAPI:apiURLString
withHttpMethod:httpMethod_POST
postParameterNames:[NSArray arrayWithObjects:#"calendarId", #"text", nil]
postParameterValues:[NSArray arrayWithObjects:[_dictCurrentCalendar objectForKey:#"id"], _strEventTextToPost, nil]];
I think you can implement this from what you have started in the iOS Quickstart Google.
I hope this helps. Goodluck :)
first you have to create a public API and get is key. and set its url using the following code
let url = NSURL(string: "https://www.googleapis.com/calendar/v3/calendars/email.gmail.com/events?maxResults=15&key=APIKey-here")
It works for you

How can I make the current time update every second?

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.
}

Difference between 2 dates in seconds ios

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];
}

How to disable (user interaction OFF) past dates in the tapku library calendar in iOS

I am using tapku calendar library in iOS. Works perfectly fine! I want to disable (user interaction disabled) all the previous dates from the current date.
So, the user should be able to click the current date and future dates only.
I looked over all places but can't find a solution.
Any help would be appreciated.
Thank you
There will be tile tapped method in tapkucalendar view class. You need to check selected KLDate is later or ealrier depending upon your requirement and can block by returning nil param.
Check the method tile when selected and do changes. I have followed and blocked for future dates and also dates beyond a year.
Instead of Disabling past dates here I am throwing an alert like 'Invalid date' using below code.
- (void)calendarMonthView:(TKCalendarMonthView *)monthView didSelectDate:(NSDate *)selectedDate
{
if([selectedDate compare:[NSDate date]] == NSOrderedAscending)
{
NSString *today=[NSString stringWithFormat:#"%#",[NSDate date]];
NSString *chooseday=[NSString stringWithFormat:#"%#",selectedDate];
NSArray *date1=[today componentsSeparatedByString:#" "];
NSArray *date2=[chooseday componentsSeparatedByString:#" "];
if([[date1 objectAtIndex:0] isEqualToString:[date2 objectAtIndex:0]])
{
NSLog(#"Today date clicked");
}
else
{
NSLog(#"Past date clicked");
}
}
}

Resources