I am using Tapku library for calendar implementation. I could see that there is a way to add Markers for predefined start and end date but I want to allow users to select/unselect any number of dates from current month only, and want to generate event for each action.
Moreover, I have switched off the month navigation functionality by returning nil for Left and Right arrow to display only current month but not able to remove events for few previous and next months Date tiles that gets displayed on current month. I can still select previous month's day 31st to navigate to previous month or select 1st on next month to navigate to next month. Can I restrict the date selection to only current month please?
Thanks.
The touches are handled in TKCalendarMonthView.m in the following method:
- (void) reactToTouch:(UITouch*)touch down:(BOOL)down
look at the block at row 563:
if(portion == 1)
{
selectedDay = day;
selectedPortion = portion;
[target performSelector:action withObject:[NSArray arrayWithObject:[NSNumber numberWithInt:day]]];
}
else if(down)
{
// this is the important part for you.
// ignore it by adding a return here (or remove the following three lines)
return;
[target performSelector:action withObject:[NSArray arrayWithObjects:[NSNumber numberWithInt:day],[NSNumber numberWithInt:portion],nil]];
selectedDay = day;
selectedPortion = portion;
}
The selecting/deselecting perhaps doesn't work as you expect. It's not like setDateSelected and setDateDeselected.. instead there is a single UIImageView*, which represents the selected state. And that view is moved around to the current position. You can search for self.selectedImageView in the code to see, what is happening.
So its not that easy to introduce multiple-date-selection. The architecture isn't built for that.
In TKCalendarMonthView there is a method name
-(void) reactToTouch:(UITouch*)touch down:(BOOL)down
in that method comment this line
[target performSelector:action withObject:[NSArray arrayWithObjects:[NSNumber numberWithInt:day],[NSNumber numberWithInt:portion],nil]];
this wont allow you to change month.
You can store all selected date in an array and pass all the values in
- (NSArray*)calendarMonthView:(TKCalendarMonthView *)monthView marksFromDate:(NSDate *)startDate toDate:(NSDate *)lastDate
The above method is used to put tiles but if u want selection image then u can replace it with tile image
You can also try this code:
You can do this by first entering the dates in to an array. code for this is.
- (void)calendarMonthView:(TKCalendarMonthView *)monthView didSelectDate:(NSDate *)d {
NSLog(#"selected Date IS - %#",inDate);
[myArray addObject:d];
for (id entry in myArray)
{
if (inDate == nil && outDate == nil)
{
inDate = d;
outDate = d;
}
if ([d compare:inDate] == NSOrderedAscending)
{
inDate = d;
}
if ([d compare:outDate] == NSOrderedDescending)
{
outDate = d;
}
d = nil;
}
}
After this you have to use a button click action by which you can make the dates selected between these two dates. Code for it is:
- (IBAction)goBtn:(id)sender
{
NSLog(#"startDate is: %#",inDate);
NSLog(#"endDate is: %#",outDate);
[calendar reload];
inDate = nil;
outDate = nil;
}
}
Then in one delegate method you just have to make an array containing all the dates between these two dates. It will be called just after the button click. Code for it is:
- (NSArray*)calendarMonthView:(TKCalendarMonthView *)monthView marksFromDate:(NSDate *)startDate toDate:(NSDate *)lastDate {
//***********
NSMutableArray *tempData = [[NSMutableArray alloc] init];
NSDate *nextDate;
for ( nextDate = inDate ; [nextDate compare:outDate] < 0 ; nextDate = [nextDate addTimeInterval:24*60*60] ) {
// use date
NSLog(#"%#",nextDate);
[tempData addObject:[NSString stringWithFormat:#"%#",nextDate]];
}
[tempData addObject:[NSString stringWithFormat:#"%#",outDate]];
//***********
NSMutableArray *marks = [NSMutableArray array];
NSCalendar *cal = [NSCalendar currentCalendar];
[cal setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
NSDateComponents *comp = [cal components:(NSMonthCalendarUnit | NSMinuteCalendarUnit | NSYearCalendarUnit |
NSDayCalendarUnit | NSWeekdayCalendarUnit | NSHourCalendarUnit | NSSecondCalendarUnit)
fromDate:startDate];
NSDate *d = [cal dateFromComponents:comp];
NSDateComponents *offsetComponents = [[NSDateComponents alloc] init];
[offsetComponents setDay:1];
while (YES) {
if ([d compare:lastDate] == NSOrderedDescending) {
break;
}
if ([tempData containsObject:[d description]]) {
[marks addObject:[NSNumber numberWithBool:YES]];
} else {
[marks addObject:[NSNumber numberWithBool:NO]];
}
d = [cal dateByAddingComponents:offsetComponents toDate:d options:0];
}
return [NSArray arrayWithArray:marks];
}
I hope, this helped you. Please let me know if you face any problem.
Related
Using Parse, I'm trying to reduce the amount of network calls to parse in an effort to limit my API requests of nonessential data. To do this I thought it would be best to call a function only once a week on the same day and upload any differences from the previous week to the installation class (for syncing push notification channels in my circumstance)
So how do you call a function once a week every week on a particular day? And what do you if the day is already passed? So say you want something to happen every Thursday, but a user doesn't open the app until Sunday after that Thursday you were supposed to sync data?
In practice, I find that a fixed interval makes more sense in more cases than a calendar milestone. With that, and very little NSDate logic, I can have my model guarantee that its data is at most N seconds out of date.
To do this, I have the model singleton keep track only of the date of the last update:
// initialize this to [NSDate distantPast]
#property(nonatomic,strong) NSDate *lastUpdate;
The interface also provides an asynch update method, like:
- (void)updateWithCompletion:(void (^)(BOOL, NSError *))completion;
I override the synthesized getter/setter of lastUpdate to wrap persistence:
// user defaults in this case, but there are several ways to persist a date
- (NSDate *)lastUpdate {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
return [defaults valueForKey:#"lastUpdate"];
}
- (void)setLastUpdate:(NSDate *)lastUpdate {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setValue:lastUpdate forKey:#"lastUpdate"];
[defaults synchronize];
}
Finally, the asynch update opaquely decides whether the current data is good enough, or whether we should invoke the parse.com api...
- (void)updateWithCompletion:(void (^)(BOOL, NSError *))completion {
NSTimeInterval sinceLastUpdate = -[self.lastUpdate timeIntervalSinceNow];
NSTimeInterval updatePeriod = [self updatePeriod];
if (sinceLastUpdate < updatePeriod) {
// our current data is new enough, so
return completion(YES, nil);
} else {
// our current data is stale, so call parse.com to update...
[...inBackgroundWithBlock:(NSArray *objects, NSError *error) {
if (!error) {
// finish the update here and persist the objects, then...
self.lastUpdate = [NSDate date];
completion(YES, nil);
} else {
completion(NO, error);
}
}];
}
}
The method updatePeriod answers whatever NSTimeInterval the app thinks is an acceptable age of the data. Usually, I get this from parse.config at some reasonably high frequency (like daily). This way, I can tune the frequency of model updates as I see fit for clients in the wild.
So with very little NSDate logic, I use this to keep the clients "up-to-date-enough", where even the "enough" part can be dynamically decided.
EDIT - we can still remain concise and set our model expiration to be a calendar day. I'd do that as follows:
- (NSDate *)lastSaturday {
NSDate *now = [NSDate date];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDate *startOfWeek;
[calendar rangeOfUnit:NSCalendarUnitWeekOfMonth startDate:&startOfWeek interval:NULL forDate:now];
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setDay:-1];
return [calendar dateByAddingComponents:components toDate:startOfWeek options:0];
}
Now, instead of updating when the interval expires, update if the last update was before last Saturday (or whatever weekday, you want... adjust by adjusting setDay:-n)
// change the date condition in updateWithCompletion
if (self.lastUpdate == [self.lastUpdate earlierDate:[self lastSaturday]) {
// do the update
} else {
// no need to update
}
The first thing you want to do is check if today is the day you want the data to sync. So for instance, I want every Thursday a function to be called to sync an array of objects to the 'channels' column.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if ([self isTodayThursday]) {
} else {
}
}
- (BOOL) isTodayThursday {
NSDateFormatter *nameOfDayOfWeekFormatter = [[NSDateFormatter alloc] init];
[nameOfDayOfWeekFormatter setDateFormat:#"EEEE"];
if ([[nameOfDayOfWeekFormatter stringFromDate:[NSDate date]] isEqualToString:#"Thursday"]) {
return YES;
}
return NO;
}
Simple, here we are checking if the name of the day of the week is Thursday. Now we want to make sure we aren't stacking syncs and ensure we have or have not synced already today, if it is a Thursday. We do that by validating against an object in NSUserDefaults :
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if ([self isTodayThursday]) {
if ([[[NSUserDefaults standardUserDefaults] objectForKey:#"lastSynced"] isEqualToString:[[self dateFormatter] stringFromDate:[NSDate date]]]) {
//So we check against a custom date formatter that we want of the last sync date we performed, and if it's already been synced today, then do nothing
} else {
//if not, sync plist (or whatever we want) to channels array
[self syncPlistToParse];
}
} else {
}
}
- (NSDateFormatter *)dateFormatter {
NSDateFormatter *formatedDate = [[NSDateFormatter alloc] init];
[formatedDate setDateFormat:#"yyyyMMdd"];
NSLog(#"Today is %#", [formatedDate stringFromDate:[NSDate date]]);
return formatedDate;
}
Sync plist to PFInstallation channels array
-(void)syncPlistToParse {
//First obtain whatever data you have from where ever you store it for the week. I chose to save all the data in a plist until the next sync date to reduce API calls.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [paths objectAtIndex:0];
NSString *plistPath = [path stringByAppendingPathComponent:#"AlertSubscriptions.plist"];
NSMutableArray *array = [[NSMutableArray alloc] initWithContentsOfFile:plistPath];
PFInstallation *currentInstallation = [PFInstallation currentInstallation];
[currentInstallation addUniqueObjectsFromArray:array forKey:#"channels"];
[currentInstallation saveInBackgroundWithBlock:^(BOOL success, NSError *error) {
if (success) {
//if successful save lastSynced date to NSUserDefaults to today
[[NSUserDefaults standardUserDefaults] setObject:[[self dateFormatter] stringFromDate:[NSDate date]] forKey:#"lastSynced"];
} else {
//It wasn't a successful save so you can do whatever you want on an unsuccessful save, but I chose to use `saveEventually` as a back-up for connectivity issues etc
PFInstallation *currentInstallation = [PFInstallation currentInstallation];
[currentInstallation addUniqueObjectsFromArray:array forKey:#"channels"];
[currentInstallation saveEventually];
}
}];
}
So if today is NOT Thursday how do we determine if it's before or after the lastSynced date and how do we determine how to set the next sync date?
Lets use these dates as an example:
June 2015
---------------------------------------------
|SUN | MON | TUES | WED | THURS | FRI | SAT |
---------------------------------------------
| | | | | 18 | 19 | 20 |
---------------------------------------------
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
---------------------------------------------
| 28 | 29 | 30 | 1 | 02 | 03 | 04 |
---------------------------------------------
So lets say the last synced Thursday was 18th of June and the user doesn't open their app until 26 of June (Friday) we need to validate against that. We already checked if today was a Thursday and we know it's not since it's Friday the 26th. So there are two descrepencies we need to do checks and balances on, most import, we need to check if that Friday was within the same week of the current sync cycle (the 19th Friday), but in this case since the current week cycle already passed because we didn't open it up until past the next cycle (which was supposed to be the 25th of June)
if ([self isTodayThursday]) {
if ([[[NSUserDefaults standardUserDefaults] objectForKey:#"lastSynced"] isEqualToString:[[self dateFormatter] stringFromDate:[NSDate date]]]) {
//So we check against a custom date formatter that we want of the last sync date we performed, and if it's already been synced today, then do nothing
} else {
//if not, sync plist (or whatever we want) to channels array
[self syncPlistToParse];
}
} else {
//Not Thursday. So we need to check if its within the same week of the sync cycle or passed that date
int lastSyncDate = [[self lastSyncDate] intValue];
NSLog(#"lastSyncDate int %d", lastSyncDate);
int nextSyncDate = [[self nextSyncDate] intValue];
NSLog(#"nextSyncDate int %d", nextSyncDate);
if (lastSyncDate < nextSyncDate) {
NSLog(#"next sync date is this coming thursday");
} else {
// if not before thursday (already passed Thursday), sync and save today as lastSynced
[self syncPlistToParse];
}
}
}
- (NSString *)lastSyncDate {
NSString *date = [[NSUserDefaults standardUserDefaults] objectForKey:#"lastSynced"];
return date;
}
- (NSString *)nextSyncDate {
NSCalendar *cal = [NSCalendar currentCalendar];
NSDate *lastSync = [[self dateFormatter] dateFromString:[self lastSyncDate]];
NSDateComponents *components = [cal components:(NSCalendarUnitDay) fromDate:lastSync];
[components setDay: +[self daysUntilNearestThursday]];
NSDate *nextSync = [cal dateByAddingComponents:components toDate:lastSync options:0];
return [[self dateFormatter] stringFromDate:nextSync];
}
-(int)daysUntilNearestThursday {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"EEEE"];
//Note the day of the week is including today thats why it's always +1
if ([[dateFormatter stringFromDate:[NSDate date]] isEqualToString:#"Monday"]) {
return 4;
}
if ([[dateFormatter stringFromDate:[NSDate date]] isEqualToString:#"Tuesday"]) {
return 3;
}
if ([[dateFormatter stringFromDate:[NSDate date]] isEqualToString:#"Wednesday"]) {
return 2;
}
if ([[dateFormatter stringFromDate:[NSDate date]] isEqualToString:#"Friday"]) {
return 7;
}
if ([[dateFormatter stringFromDate:[NSDate date]] isEqualToString:#"Saturday"]) {
return 6;
}
if ([[dateFormatter stringFromDate:[NSDate date]] isEqualToString:#"Sunday"]) {
return 5;
}
return 0;
}
NOTE It doesn't matter what the nextSyncDate is because we always check if it's Thursday first and then act accordingly
This is my personal opinion, someone asked me about this and this is what I came up with. I feel like theres an easier solution out there that accurately resets the nextSync to the nearest Thursday instead of [components setDay: +[self daysUntilNearestThursday]]; So by all means, please add input/suggestions and comments to make this a better opportunity for future question seekers. I think Parse is fantastic, but at the same time it's a business, and they have to act accordingly, but certain things like API requests for everything ever for a scaleable app (2,000+ users) is kind of bad practice if you intend on keeping developers. This is an alternative I thought of to help my friend out, it needs work, not saying its the only way, just the way I thought would work for their project. Please, don't be shy, add edits or corrections.
I have a foursquare hours array (Foursquare API) that stores segments of hours when a specific venue is open. It looks something like this:
[{
"days":[1,2,3,4,7],
"includesToday":true,
"open":[
{"end":"+0200","start":"1000"}],
"segments":[]},
{
"days":[5,6]
,"open":[
{"end":"+0300","start":"1000"}],
"segments":[]}
]
How do I find out if the venue is opened or closed at current time?
I handle it like this: 4sq hours API gist
-(NSDictionary*)isVenueOpenDictionaryForHours:(NSArray*)hours{
// defaults and inits
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDictionary *lastSegmentYesterday = [[NSDictionary alloc] init];
NSDate *dateNow = [NSDate date];
NSString *venueOpenText = [[NSString alloc] init];
NSString *venueOpen = #"no";
// get components for today
NSDateComponents *compsNow = [gregorian components:NSWeekdayCalendarUnit|NSHourCalendarUnit|NSMinuteCalendarUnit|NSDayCalendarUnit|NSMonthCalendarUnit|NSYearCalendarUnit fromDate:dateNow];
// get weekday for today and yesterday so we can lookup 4sq API
NSInteger weekday = [compsNow weekday];
NSInteger weekdayYesterday = (weekday>1)?weekday-1:7;
// look for todays' segment
NSMutableArray *venueOpenSegments = [[NSMutableArray alloc] init]; // stores all the segments when the venue is open
for (NSDictionary *segment in hours){
// get today's segment (if it exists)
if ([segment[#"days"] containsObject:[NSNumber numberWithInteger:weekday]]){
for (NSDictionary *dictOpen in segment[#"open"])
[venueOpenSegments insertObject:#{#"end": [dictOpen[#"end"] mutableCopy], #"start":[dictOpen[#"start"] mutableCopy]}.mutableCopy atIndex:venueOpenSegments.count];
}
// check the day before if the venue is open past midnight
if (([segment[#"days"] containsObject:[NSNumber numberWithInteger:weekdayYesterday]] && [segment[#"open"] count])){
// get the last segment (that should be the one passing midnight)
NSDictionary *tempSegment = [segment[#"open"] lastObject];
// if it has more than 4 characters it's after midnight ("+02:00"), also, ignore if it closes at midnight
if ([tempSegment[#"end"] length] > 4 && ![tempSegment[#"end"]isEqualToString:#"+0000"]){
// create a new segment that starts at midnight and lasts till the time it closes (early AMs usually)
lastSegmentYesterday = #{#"start":#"0000", #"end":[tempSegment[#"end"] substringFromIndex:1]};
}
}
}
// add last night segment that passes midnight as the first segment of today
if (lastSegmentYesterday.count){
[venueOpenSegments insertObject:lastSegmentYesterday atIndex:0];
}
// go through all the segments and find out if the venue is closed or open
if (venueOpenSegments.count){
NSDateComponents *comps = [[NSDateComponents alloc] init];
NSDateFormatter *timeFormatter = [[NSDateFormatter alloc]init];
timeFormatter.dateFormat = #"HH:mm"; // set time output format
int segmentNumber = 0;
for (NSMutableDictionary *segment in venueOpenSegments){
segmentNumber++;
// confing start date
[comps setDay:compsNow.day];
[comps setMonth:compsNow.month];
[comps setYear:compsNow.year];
[comps setHour:[[segment[#"start"] substringToIndex:2] intValue]];
[comps setMinute:[[segment[#"start"] substringFromIndex:2] intValue]];
NSDate *dateStart = [[[NSCalendar currentCalendar] dateFromComponents:comps] copy];
// config end date
// check if the segment goes to next day
BOOL closesTomorrow = NO;
if ( [segment[#"end"] length]==5 ){
segment[#"end"] = [segment[#"end"] substringFromIndex:1];
closesTomorrow = YES;
}
[comps setHour:[[segment[#"end"] substringToIndex:2] intValue]];
[comps setMinute:[[segment[#"end"] substringFromIndex:2] intValue]];
NSDate *dateEnd = [[[NSCalendar currentCalendar] dateFromComponents:comps] copy];
// add a day if it closes tomorrow
if (closesTomorrow){
NSDateComponents *nextDayComponent = [[NSDateComponents alloc] init];
nextDayComponent.day = 1;
dateEnd = [gregorian dateByAddingComponents:nextDayComponent toDate:dateEnd options:0];
}
// start checking if it's open or closed
// now < segment start
if ([dateNow compare:dateStart] == NSOrderedAscending){
venueOpenText = [NSString stringWithFormat:#"opens at %#",[timeFormatter stringFromDate: dateStart]];
venueOpen = #"later";
break;
}
// segment end < now
else if ([dateEnd compare:dateNow] == NSOrderedAscending){
if (segmentNumber == venueOpenSegments.count){
venueOpenText = [NSString stringWithFormat:#"closed since %#",[timeFormatter stringFromDate: dateEnd]];
break;
}
continue;
}
// segment start < now < segment end
else if ([dateStart compare:dateNow] == NSOrderedAscending && [dateNow compare:dateEnd] == NSOrderedAscending){
venueOpenText = [NSString stringWithFormat:#"open till %#",[timeFormatter stringFromDate: dateEnd]];
venueOpen = #"yes";
break;
}
// rare but possible... last minute of the venue being open (I treat it as closed)
else {
venueOpenText = #"closing right now";
}
}
}
else venueOpen = #"closed today"; // no segments for today, so it's closed for the dayæ
// return results
return #{#"open":venueOpen, #"string":venueOpenText};
}
and I update my UILabel like this:
NSDictionary *venueOpen = [self isVenueOpenDictionaryForHours:_arrayVenues[indexPath.row][#"hours"]];
label.text = venueOpen[#"string"];
if ([venueOpen[#"open"] isEqualToString:#"no"]){
label.textColor = [UIColor colorWithHexString:#"b91d47" alpha:1]; // red
} else if ([venueOpen[#"open"] isEqualToString:#"yes"]) {
label.textColor = [UIColor colorWithHexString:#"1e7145" alpha:1]; // green
} else if ([venueOpen[#"open"] isEqualToString:#"later"]) {
label.textColor = [UIColor colorWithHexString:#"e3a21a" alpha:1]; // yellow
}
BTW, I use pod 'HexColors' for colorWithHexString methods
I am trying to have some text aligned to the left of the section heading and different text to the right of the same heading.
While using toolBars I have used a flexible button to push both other buttons to the left and right and I was wondering if there was a similar thing for section headings?
A little bit more background is that I have a different section in my tableview for every piece of information entered on each different day. On the left of the section heading I want the date it was entered (Wed 20th Nov) and on the right I want how many days ago that was (20 days ago).
Here is the code I am currently using:
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
if (section == 0) {
return nil;
}
else {
// This gets the day the data was entered
NSDate * date = [_sectionDates objectAtIndex:(section-1)];
NSDateFormatter * formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"EEE"];
NSString * dateDay = [formatter stringFromDate:date];
[formatter setDateFormat:#"MMM"];
NSString * dateMonth = [formatter stringFromDate:date];
NSDateComponents * components = [[NSCalendar currentCalendar] components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear fromDate:date];
NSInteger dateOfDay = [components day];
NSTimeInterval diff = [[NSDate date] timeIntervalSinceDate:date];
NSInteger days = diff / (60.0 * 60.0 * 24);
NSString * dateScript;
if (dateOfDay < 21 && dateOfDay > 4) {
dateScript = #"th";
}
else if (dateOfDay % 10 == 1) {
dateScript = #"st";
}
else if (dateOfDay % 10 == 2) {
dateScript = #"nd";
}
else if (dateOfDay % 10 == 3) {
dateScript = #"rd";
}
else {
dateScript = #"th";
}
NSString * header;
if (days < 2) {
header = [NSString stringWithFormat:#"%# %i%# %# - %#", dateDay, dateOfDay, dateScript, dateMonth, days == 0 ? bToday : bYesterday];
}
else {
header = [NSString stringWithFormat:#"%# %i%# %# - %i %#", dateDay, dateOfDay, dateScript, dateMonth, days, bDaysAgo];
}
return header;
}
}
At the moment this outputs it like this:
|Wed 4th Dec - Today________|
I want it to be like this:
|Wed 4th Dec_________Today|
You can't do it with titleForHeaderInSection. You would need to implement viewForHeaderInSection instead. Return a view with two labels - one to the left and one to the right. Of course you view would also need to replicate the default look of a standard table section header if that's what you want.
I'm trying to add an event into calendar with recurrence rule RRULE:FREQ=YEARLY;BYMONTH=6,7;BYDAY=1TH
So according to this rule the event should be added yearly, each 1st thursday of june and july until expire date, which I've set in my project.
In my project, events are created but not according to the recurrence rule. With the following code the events added only on each 1st thursday of june. Why the events are not added on 1st thursday of each july also?
Here is .m file code
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self createEvent];
}
- (void)createEvent
{
EKEventStore *eventStore = [[EKEventStore alloc] init];
EKEvent *event = [EKEvent eventWithEventStore:eventStore];
event.title = #"testRecurrenceRule";
event.location = #"Dhaka";
[event setCalendar:[eventStore defaultCalendarForNewEvents]];
event.startDate = [self dateFromString:#"2013-06-18T21:00:00+06:00"];
event.endDate = [self dateFromString:#"2013-06-18T22:00:00+06:00"];
id recurrenceRule = [self recurrenceRuleForEvent];
if(recurrenceRule != nil)
[event addRecurrenceRule:recurrenceRule];
if ([eventStore respondsToSelector:#selector(requestAccessToEntityType:completion:)])
{
// iOS 6 and later
[eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
if (granted)
{
dispatch_async(dispatch_get_main_queue(), ^{
[self saveTheEvent:event eventStore:eventStore];
//[eventStore saveEvent:event span:EKSpanThisEvent error:error];
});
}
else
{
dispatch_async(dispatch_get_main_queue(), ^{
//do nothing
});
}
}];
}
else
{
[self saveTheEvent:event eventStore:eventStore];
}
textView.text = [NSString stringWithFormat:#"Event has been added with recurrence rule %#",recurrenceRule];
}
- (void)saveTheEvent:(EKEvent *)aEvent eventStore:(EKEventStore *)aStore
{
[aStore saveEvent:aEvent span:EKSpanThisEvent error:NULL];
}
- (EKRecurrenceRule *)recurrenceRuleForEvent
{
//just creating a recurrence rule for RRULE:FREQ=YEARLY;BYMONTH=6,7;BYDAY=1TH
// setting the values directly for testing purpose.
//FREQ=YEARLY
EKRecurrenceFrequency recurrenceFrequency = EKRecurrenceFrequencyYearly;
NSInteger recurrenceInterval = 1;
EKRecurrenceEnd *endRecurrence = nil;
NSMutableArray *monthsOfTheYearArray = [NSMutableArray array];
NSMutableArray *daysOfTheWeekArray = [NSMutableArray array];
NSMutableArray *daysOfTheMonthArray = [NSMutableArray array];
NSMutableArray *weeksOfTheYearArray = [NSMutableArray array];
NSMutableArray *daysOfTheYearArray = [NSMutableArray array];
NSMutableArray *setPositionsArray = [NSMutableArray array];
//BYMONTH=6,7
[monthsOfTheYearArray addObject:[NSNumber numberWithInt:6]];
[monthsOfTheYearArray addObject:[NSNumber numberWithInt:7]];
//BYDAY=1TH
[daysOfTheWeekArray addObject:[EKRecurrenceDayOfWeek dayOfWeek:5 weekNumber:1]];
endRecurrence = [EKRecurrenceEnd recurrenceEndWithEndDate:[self dateFromString:#"2018-12-15T22:30+06:00"]];
EKRecurrenceRule *recurrence = [[EKRecurrenceRule alloc] initRecurrenceWithFrequency:recurrenceFrequency
interval:recurrenceInterval
daysOfTheWeek:daysOfTheWeekArray
daysOfTheMonth:daysOfTheMonthArray
monthsOfTheYear:monthsOfTheYearArray
weeksOfTheYear:weeksOfTheYearArray
daysOfTheYear:daysOfTheYearArray
setPositions:setPositionsArray
end:endRecurrence];
return recurrence;
}
- (NSDate *)dateFromString:(NSString *)string
{
//check if the date string in null
if ([string length] == 0)
return nil;
NSString *dateString = nil;
NSString *modifiedString = nil;
BOOL secSpotMissing = false;
NSRange range = [string rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:#"T"]];
if (range.location != NSNotFound)
{
dateString = [string substringFromIndex:range.location];
range = [dateString rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:#"+-Z"]];
if (range.location != NSNotFound)
{
//seperate the time portion of date string and checking second field is missing or not. like is it HH:mm or HH:mm:ss?
if ([[[dateString substringToIndex:range.location] componentsSeparatedByString:#":"] count] < 3)
secSpotMissing = true;
//seperate the time zone portion and checking is there any extra ':' on it. It should like -0600 not -06:00. If it has that extra ':', just replacing it here.
dateString = [dateString substringFromIndex:range.location];
if([dateString hasSuffix:#"Z"])
modifiedString = [dateString stringByReplacingOccurrencesOfString:#"Z" withString:#"+0000"];
else
modifiedString = [dateString stringByReplacingOccurrencesOfString:#":" withString:#""];
string = [string stringByReplacingOccurrencesOfString:dateString withString:modifiedString];
}
}
else
return nil;
// converting the date string according to it's format.
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
if (secSpotMissing)
[dateFormatter setDateFormat:#"yyyy-MM-dd'T'HH:mmZZZ"];
else
[dateFormatter setDateFormat:#"yyyy-MM-dd'T'HH:mm:ssZZZ"];
return [dateFormatter dateFromString:string];
}
Can somebody please help me regarding this?
This seems a repeat of another question. Basically, according to "BYDAY" rule, the 1st for YEARLY frequency means the first week in the year - instead of 1st week each month.
#Shuvo, I did not read rfc. But here is Apple document EKRecurrenceDayOfWeek.
The EKRecurrenceDayOfWeek class represents a day of the week for use with an EKRecurrenceRule object. A day of the week can optionally have a week number, indicating a specific day in the recurrence rule’s frequency. For example, a day of the week with a day value of Tuesday and a week number of 2 would represent the second Tuesday of every month in a monthly recurrence rule, and the second Tuesday of every year in a yearly recurrence rule.
When you say "1st Thursday", that is correct - except in the context of yearly, it is 1st thursday of the year.
The bug was confirmed by Apple, at least until iOS 7.1.3 (which is the latest available version at this moment).
Can any one help me with the issue that I am experiencing?
I am using the Tapku Calendar Library Day View in order to display events. Events are getting displayed properly when the calendar loads for the first time. If I change the date lets say either to yesterday or tomorrow, the calendar is not displaying the events.
I have implemented the following events.
- (void)calendarDayTimelineView:(TKCalendarDayView *)calendarDay didMoveToDate:(NSDate *)date
{
//Here is my logic to pull the data from db server.
//After this I am calling the method below.
[self.dayView reloadData];
}
- (NSArray *) calendarDayTimelineView:(TKCalendarDayView*)calendarDayTimeline eventsForDate:(NSDate *)eventDate{
self.myAppointments = nil;
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Tasks" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSError *error;
self.myAppointments = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if([eventDate compare:[NSDate dateWithTimeIntervalSinceNow:-24*60*60]] == NSOrderedAscending) return #[];
if([eventDate compare:[NSDate dateWithTimeIntervalSinceNow:24*60*60]] == NSOrderedDescending) return #[];
NSDateComponents *info = [[NSDate date] dateComponentsWithTimeZone:calendarDayTimeline.timeZone];
info.second = 0;
NSMutableArray *ret = [NSMutableArray array];
for(Tasks *apt in self.myAppointments){
TKCalendarDayEventView *event = [calendarDayTimeline dequeueReusableEventView];
if(event == nil) event = [TKCalendarDayEventView eventView];
event.identifier = nil;
event.titleLabel.text = apt.task_subject;
if ( [allTrim(apt.location) length] != 0 )
{
event.locationLabel.text = apt.location;
}
NSDate *startDate = apt.task_start;
NSDate *endDate = apt.task_end;
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *components = [calendar components:(NSHourCalendarUnit | NSMinuteCalendarUnit) fromDate:startDate];
NSInteger hour = [components hour];
NSInteger minute = [components minute];
info.hour = hour;
info.minute = minute;
event.startDate = [NSDate dateWithDateComponents:info];
components = [calendar components:(NSHourCalendarUnit | NSMinuteCalendarUnit) fromDate:endDate];
hour = [components hour];
minute = [components minute];
info.hour = hour;
info.minute = minute;
event.endDate = [NSDate dateWithDateComponents:info];
[ret addObject:event];
}
return ret;
}
I have debugged the code, the data is getting assigned to the events, however I am not able to view any thing on the calendar.
Pls.. help in fixing up this issue.
Regards,
g.v.n.sandeep
There has another way to solve this problem.
As you see, in the methods below:
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = NSLocalizedString(#"Day View", #"");
self.data = #[
#[#"Meeting with five random dudes", #"Five Guys", #960, #0, #1000, #30],
#[#"Unlimited bread rolls got me sprung", #"Olive Garden", #7, #0, #12, #0],
#[#"Appointment", #"Dennys", #15, #0, #18, #0],
#[#"Hamburger Bliss", #"Wendys", #15, #0, #18, #0],
#[#"Fishy Fishy Fishfelayyyyyyyy", #"McDonalds", #5, #30, #6, #0],
#[#"Turkey Time...... oh wait", #"Chick-fela", #14, #0, #19, #0],
#[#"Greet the king at the castle", #"Burger King", #19, #30, #100, #0]];
}
self.data is loaded.
We can move the loading process to "viewWillAppear" methods to update data if you do some writing.
Note that, there has no method to add an event to a specific date. But we can add an event like this: #[#"Fishy Fishy Fishfelayyyyyyyy", #"McDonalds", #48, #00, #50, #0].
#48 means the event at the day after tomorrow.
So, first you should calculate the interval between the specific date and self.dayview.date(today).
Following codes may help:
- (NSArray *)getTimeFromNow:(NSDate *)startDate endDateTime:(NSDate *)endDate
{
NSDate *todayDate = [NSDate date];
NSTimeInterval startFromNowSeconds = [startDate timeIntervalSinceDate:todayDate];
NSTimeInterval endFromNowSeconds = [endDate timeIntervalSinceDate:todayDate];
NSNumber *startHour = [NSNumber numberWithInt:startFromNowSeconds / (60 * 60)];
NSNumber *startMinute = [NSNumber numberWithInt:startFromNowSeconds / 60 - startHour.intValue * 60];
NSNumber *endHour = [NSNumber numberWithInt:endFromNowSeconds / (60 * 60)];
NSNumber *endMinute = [NSNumber numberWithInt:endFromNowSeconds / 60 - endHour.intValue * 60];
return #[startHour, startMinute, endHour, endMinute];
}
Below should be commented:
if([eventDate compare:[NSDate dateWithTimeIntervalSinceNow:-24*60*60]] == NSOrderedAscending) return #[];
if([eventDate compare:[NSDate dateWithTimeIntervalSinceNow:24*60*60]] == NSOrderedDescending) return #[];
But pay attention to the timezone. I think it's not very easy to return a right time array calculated above.
More over, i think it is essential necessary to add the method that add event to a specific day in DayViewController.
Note: as i have do some coding, if your time is #900 hours from now, the event will not be showed in dayview... but #500 hours it works well.
What a terrible designs here!
Waiting a more complete dayviewcontroller ...