I am calling a function and have been trying to figure out how I can update my app to reflect the total steps for the day. Currently, the code I have only gives me the most recent quantity of steps that has been recorded. I am having trouble understanding how to use the HKStatisticsQuery but this is the function I have at the moment that is giving me the most recent results.
-(void)updateStepsLabel{
// Query to get the user's latest steps, if it exists.
HKQuantityType *stepsType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
[_healthStore aapl_mostRecentQuantitySampleOfType:stepsType predicate:nil completion:^(HKQuantity *mostRecentQuantity, NSError *error) {
if (!mostRecentQuantity) {
NSLog(#"Either an error occured fetching the user's steps information or none has been stored yet. In your app, try to handle this gracefully.");
dispatch_async(dispatch_get_main_queue(), ^{
self.todaysStepValueLabel.text = NSLocalizedString(#"Not available", nil);
});
}
else {
// Determine the steps in the required unit.
HKUnit *stepUnit = [HKUnit countUnit];
double usersWeight = [mostRecentQuantity doubleValueForUnit:stepUnit];
// Update the user interface.
dispatch_async(dispatch_get_main_queue(), ^{
self.todaysStepValueLabel.text = [NSNumberFormatter localizedStringFromNumber:#(usersWeight) numberStyle:NSNumberFormatterNoStyle];
self.todaysStepValueLabel.textColor = [UIColor blackColor];
self.todaysStepValueLabel.font = [UIFont systemFontOfSize:25];
});
}
}];
}
Here I am posting an answer with Swift code, I hope you would be able to convert it into Obj-c
I have written the below function in a class with definition like this
class MyHealthStore: HKHealthStore { }
.
func TodayTotalSteps(completion: (stepRetrieved: Double) -> Void) {
let type = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount) // The type of data we are requesting
let calendar = NSCalendar.currentCalendar()
let interval = NSDateComponents()
interval.day = 1
let anchorComponents = calendar.components([.Day , .Month , .Year], fromDate: NSDate())
anchorComponents.hour = 0
let anchorDate = calendar.dateFromComponents(anchorComponents)
let stepsQuery = HKStatisticsCollectionQuery(quantityType: type!, quantitySamplePredicate: nil, options: .CumulativeSum, anchorDate: anchorDate!, intervalComponents: interval)
stepsQuery.initialResultsHandler = {query, results, error in
let endDate = NSDate()
var totalSteps = 0.0
let startDate = calendar.dateByAddingUnit(.Day, value: 0, toDate: endDate, options: [])
if let myResults = results{ myResults.enumerateStatisticsFromDate(startDate!, toDate: endDate) { statistics, stop in
if let quantity = statistics.sumQuantity(){
//let date = statistics.startDate
totalSteps = quantity.doubleValueForUnit(HKUnit.countUnit())
// print("\(date): steps = \(steps)")
}
self.todayManuallyAddedSteps({ (manuallSteps, error) in
if error != nil {
print(error)
} else {
totalSteps = totalSteps - manuallSteps
completion(stepRetrieved: totalSteps)
return
}
})
//completion(stepRetrieved: totalSteps)
}
} else {
// mostly not executed
completion(stepRetrieved: totalSteps)
}
}
executeQuery(stepsQuery)
}
And if you want to truncate manually added steps use this function
func todayManuallyAddedSteps(completion: (Double, NSError?) -> () )
{
let type = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount) // The type of data we are requesting
let date = NSDate()
let cal = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
let newDate = cal.startOfDayForDate(date)
let predicate = HKQuery.predicateForSamplesWithStartDate(newDate, endDate: NSDate(), options: .None) // Our search predicate which will fetch all steps taken today
// The actual HealthKit Query which will fetch all of the steps and add them up for us.
let query = HKSampleQuery(sampleType: type!, predicate: predicate, limit: 0, sortDescriptors: nil) { query, results, error in
var steps: Double = 0
if results?.count > 0
{
for result in results as! [HKQuantitySample]
{
// checking and adding manually added steps
if result.sourceRevision.source.name == "Health" {
// these are manually added steps
steps += result.quantity.doubleValueForUnit(HKUnit.countUnit())
}
else{
// these are auto detected steps which we do not want from HKSampleQuery
}
}
completion(steps, error)
return
} else {
// mostly not executed
completion(steps, error)
}
}
executeQuery(query)
}
and here is how to call it
MyHealthStore.sharedHealthStore.TodayTotalSteps { (stepRetrieved) in
print(stepRetrieved)
}
I hope its helps, let me know if you find any difficulty.
Here is the code which is given in the document of HKStatisticsCollectionQuery
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *interval = [[NSDateComponents alloc] init];
interval.day = 7;
// Set the anchor date to Monday at 3:00 a.m.
NSDateComponents *anchorComponents =
[calendar components:NSCalendarUnitDay | NSCalendarUnitMonth |
NSCalendarUnitYear | NSCalendarUnitWeekday fromDate:[NSDate date]];
NSInteger offset = (7 + anchorComponents.weekday - 2) % 7;
anchorComponents.day -= offset;
anchorComponents.hour = 3;
NSDate *anchorDate = [calendar dateFromComponents:anchorComponents];
HKQuantityType *quantityType =
[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
// Create the query
HKStatisticsCollectionQuery *query =
[[HKStatisticsCollectionQuery alloc]
initWithQuantityType:quantityType
quantitySamplePredicate:nil
options:HKStatisticsOptionCumulativeSum
anchorDate:anchorDate
intervalComponents:interval];
// Set the results handler
query.initialResultsHandler =
^(HKStatisticsCollectionQuery *query, HKStatisticsCollection *results, NSError *error) {
if (error) {
// Perform proper error handling here
NSLog(#"*** An error occurred while calculating the statistics: %# ***",
error.localizedDescription);
abort();
}
NSDate *endDate = [NSDate date];
NSDate *startDate = [calendar
dateByAddingUnit:NSCalendarUnitMonth
value:-3
toDate:endDate
options:0];
// Plot the weekly step counts over the past 3 months
[results
enumerateStatisticsFromDate:startDate
toDate:endDate
withBlock:^(HKStatistics *result, BOOL *stop) {
HKQuantity *quantity = result.sumQuantity;
if (quantity) {
NSDate *date = result.startDate;
double value = [quantity doubleValueForUnit:[HKUnit countUnit]];
// Call a custom method to plot each data point.
[self plotWeeklyStepCount:value forDate:date];
}
}];
};
[self.healthStore executeQuery:query];
In the given code there are 2 dates mentioned
NSDate *endDate = [NSDate date];
NSDate *startDate = [calendar
dateByAddingUnit:NSCalendarUnitMonth
value:-3
toDate:endDate
options:0];
Change the startDate and endDate as per your requirement.
Related
I have three subscription plans in in-app-purchase 1 month,6 months,1 year so i want to check the users current subscription plan and date of expire so what i am doing right now is from receipt getting latest_receipt_info in that I am using the last object.
from the last object, I am taking product_id and using it as a current subscription plan and expires_date used to check active or not.
Below is the block of code I am using.
So is this the correct approach?
if([dict[#"status"] integerValue] == 0) {
if([dict.allKeys containsObject:#"latest_receipt_info"]) {
NSArray *iapReceipts = dict[#"latest_receipt_info"];
if(iapReceipts) {
if(iapReceipts.count > 0) {
NSDictionary *latestReceipt = [iapReceipts lastObject];
if(latestReceipt) {
_currentIAPID = latestReceipt[#"product_id"];
NSDate *expireDate = latestReceipt[#"expires_date"];
if(expireDate) {
NSInteger days = DaysBetween(NSDate.date, expireDate);
if(days >= 0) {
NSLog(#"days %ld",(long)days);
status = SubscriptionStatusActive;
} else {
NSLog(#"Subs :NEGATIVE DAYS");
status = SubscriptionStatusExpired;
}
}
}
}
}
}
}
Any suggestions would be more helpful.
No, it's not a correct approach. You shouldn't get last object because that array is not sorted. It contains all your transactions including all renewals. This example code doesn't take into account cancellation_date:
- (void)parseResponse:(NSDictionary*)dict{
if ([dict[#"status"] integerValue] != 0) {return;}
NSArray *iapReceipts = dict[#"latest_receipt_info"];
NSDateFormatter *formatter = [NSDateFormatter new];
formatter.dateFormat = #"yyyy-MM-dd HH:mm:ss VV";
for (NSDictionary *iapReceipt in iapReceipts){
NSString *productID = iapReceipt[#"product_id"];
NSDate *date = [formatter dateFromString:iapReceipt[#"expires_date"]?:#""];
if (date && [date timeIntervalSinceDate:[NSDate date]] > 0){
[[NSUserDefaults standardUserDefaults] setObject:date forKey:productID];
}
}
}
- (BOOL)isSubscriptionActive:(NSString*)productID {
NSDate *date = [[NSUserDefaults standardUserDefaults] objectForKey:productID];
if (date && [date timeIntervalSinceDate:[NSDate date]] > 0){
return YES;
} else {
return NO;
}
}
In my application I am integrating health kit framework. My project requirement is I want to push body fat percentage and Lean body mass values from my application to Health kit application. So I am writing like this.
-(void)viewDidLoad
{
NSString *bodymassIndex=#"60";
double index=[bodymassIndex doubleValue];
[[GSHealthKitManager sharedManager]saveBodyMassIndexintoHealthstore:index];
// For Body Mass
NSString *bodymass=#"40";
double mass=[bodymass doubleValue];
[[GSHealthKitManager sharedManager]saveBodyMassintoHealthstore:mass];
}
- (void)saveBodyMassintoHealthstore:(double)width
{
HKUnit *massUnit = [HKUnit poundUnit];
HKQuantity * weightQuantity = [HKQuantity quantityWithUnit:massUnit doubleValue:width];
HKQuantityType *weightType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierLeanBodyMass];
NSDate *now = [NSDate date];
HKQuantitySample *weightsample = [HKQuantitySample quantitySampleWithType:weightType quantity:weightQuantity startDate:now endDate:now];
[self.healthStore saveObject:weightsample withCompletion:^(BOOL success, NSError *error){
if (!success){
NSLog(#"Error");
}
}];
}
- (void)saveBodyMassIndexintoHealthstore:(double)weight
{
HKUnit *massUnit = [HKUnit mileUnit];
HKQuantity * weightQuantity = [HKQuantity quantityWithUnit:massUnit doubleValue:weight];
HKQuantityType *weightType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyMassIndex];
NSDate *now = [NSDate date];
HKQuantitySample *weightsample = [HKQuantitySample quantitySampleWithType:weightType quantity:weightQuantity startDate:now endDate:now];
[self.healthStore saveObject:weightsample withCompletion:^(BOOL success, NSError *error){
if (!success)
{
NSLog(#"Error");
}
}];
}
But my values are not showing in health kit application. So please guide me anybody. I can’t understand where is the issue exactly. Thanks in advance..
Try this one:
- (void)saveBodyMassIndexintoHealthstore:(double)weight {
// Each quantity consists of a value and a unit.
HKUnit *kilogramUnit = [HKUnit gramUnitWithMetricPrefix:HKMetricPrefixKilo];
HKQuantity *weightQuantity = [HKQuantity quantityWithUnit:kilogramUnit doubleValue:weight];
HKQuantityType *weightType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyMass];
NSDate *now = [NSDate date];
// For every sample, we need a sample type, quantity and a date.
HKQuantitySample *weightSample = [HKQuantitySample quantitySampleWithType:weightType quantity:weightQuantity startDate:now endDate:now];
[self.healthStore saveObject:weightSample withCompletion:^(BOOL success, NSError *error) {
if (!success) {
NSLog(#"Error while saving weight (%f) to Health Store: %#.", weight, error);
}
}];
}
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
Question: How do I properly set my reminder due date since I have a recurrence rule?
Here is what the reminder object looks like:
EKReminder <0x1700cf490> {title = Dickens's CANINE GOLD WELLNESS doses[1.00]; **dueDate = (null)**; **completionDate = (null)**; priority = 0; calendarItemIdentifier = D1D99FEA-2BFA-4DB1-9D86-7FB26246B50A; alarms = (
"EKAlarm <0x1780a9420> {triggerInterval = -79200.000000}"
)}
The error I am getting is:
Reminder Error=[A repeating reminder must have a due date.]
You can see in the code that I am fooling around with NSDateComponents as a solution since startDateComponents which I just set the month/day/year and local timezone of the reminder which will produce an all day reminder, which in this case is fine. I will probably move the date components and setting of the due date inside the recurrence section when it is done.
Here is my code:
-(void)setReminders:(NSString *)reminderText
andDate:(NSString *)reminderdate
andPetName:(NSString*)petName
andDose:(NSNumber *)dose {
EKEventStore *store = [[EKEventStore alloc] init];
NSDate * reminderNewDate = [self getDateFromString:reminderdate];
petName = [ConfigOps readProperty:kConfigOpsPetKey];
NSString *reminderTitle = [NSString stringWithFormat:#"%#'s %#", petName, reminderText];
NSUInteger doseCount = 0;
if ([dose integerValue] != 0 || dose != nil) {
doseCount = [dose integerValue];
}
else{
doseCount = 0;//NOTE: looks like purchases will have doses not reminders so set to 0 for now.
}
[store requestAccessToEntityType:EKEntityTypeReminder completion:^(BOOL granted, NSError *error) {
// access code here
EKReminder *new_reminder = [EKReminder reminderWithEventStore:store];
new_reminder.title = reminderTitle;
new_reminder.calendar = store.defaultCalendarForNewEvents;
//get the date components
NSDateComponents *comp = [[NSDateComponents alloc]init];
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *weekdayComponents =
[gregorian components:(NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit |
NSWeekdayCalendarUnit) fromDate:reminderNewDate];
NSInteger day = [weekdayComponents day];
NSInteger month = [weekdayComponents month];
//NSInteger weekday = [weekdayComponents weekday];//future reference
NSInteger year = [weekdayComponents yearForWeekOfYear];
//Month is dose+month = end of reccurence
month = month + doseCount;
[comp setYear:year];
[comp setMonth:month];
[comp setDay:day];
NSDate *date = [gregorian dateFromComponents:comp];
NSTimeZone *myNSTimeZone = gregorian.timeZone;
NSDateComponents *start = new_reminder.startDateComponents;
start.timeZone = myNSTimeZone;
start.month = [weekdayComponents month];
start.day = [weekdayComponents day];
start.year = [weekdayComponents yearForWeekOfYear];
new_reminder.startDateComponents = start;
new_reminder.dueDateComponents = start;
new_reminder.completed = NO;
//Create alarm 22 hours before
double alarmAmountInSeconds = 60.0*60.0*22.0;
EKAlarm *alarm = [EKAlarm alarmWithRelativeOffset:(-1.0*alarmAmountInSeconds)];
[new_reminder addAlarm:alarm];
//new_reminder.alarms = [NSArray arrayWithObject:alarm];
//create nice text for note.
//Hey there! petName needs remindertext from your friendly clinic, clinicName!
new_reminder.notes = reminderText;
if (doseCount != 0) {
EKRecurrenceRule *recurranceRule = [[EKRecurrenceRule alloc] initRecurrenceWithFrequency:EKRecurrenceFrequencyMonthly
interval:1
end:[EKRecurrenceEnd recurrenceEndWithOccurrenceCount:doseCount]
];
new_reminder.calendar = [store defaultCalendarForNewReminders];
[new_reminder addRecurrenceRule:recurranceRule];
}
NSError *er;
//EKEventEditViewController
BOOL success = [store saveReminder:new_reminder commit:YES error:&er];
if (success) {
// Handle here
NSString *alertMessage = [NSString stringWithFormat:#"Reminder Created for\n%#", reminderTitle];
NSString *alertTitle = #"Please check your Reminders";
UIAlertView *alertR = [[UIAlertView alloc]initWithTitle: alertTitle
message: alertMessage
delegate: self
cancelButtonTitle:nil
otherButtonTitles:#"OK",nil];
[alertR show];
}
else{
//log error
NSLog(#" Reminder Error=[%#]", [er localizedDescription]);
//log to error table in database &inform Flurry?
}
}];
}
The method works if there is no recurrence set since it doesn't require a start date/due date.
After some fixing of the date (I found was returning nil), I found that I have to set and end recurrence rule when adding a dose amount.
Here is the code which gets rid of the error (which is pretty funny of Apple to have - kudos Apple!).
if (doseCount != 0) {
EKRecurrenceRule *recurranceRule = [[EKRecurrenceRule alloc] initRecurrenceWithFrequency:EKRecurrenceFrequencyMonthly
interval:1
end:[EKRecurrenceEnd recurrenceEndWithOccurrenceCount:doseCount]
];
new_reminder.calendar = [store defaultCalendarForNewReminders];
//FIX for : recuurence end - Reminder Error = [A repeating reminder must have a due date.]
EKRecurrenceEnd *endRec = [EKRecurrenceEnd recurrenceEndWithEndDate:date];
EKRecurrenceRule *recur = [[EKRecurrenceRule alloc]initRecurrenceWithFrequency:EKRecurrenceFrequencyDaily interval: 1 end:endRec];
unsigned unitFlags= NSYearCalendarUnit|NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit |NSMinuteCalendarUnit|NSSecondCalendarUnit|NSTimeZoneCalendarUnit;
NSDateComponents *dailyComponents=[gregorian components:unitFlags fromDate:date];
[new_reminder setDueDateComponents:dailyComponents];
[new_reminder addRecurrenceRule:recur];
//add it.
[new_reminder addRecurrenceRule:recurranceRule];
}
Hope this helps someone get through this.
For those looking for Swift version:
func editReminder(r: EKReminder) -> Bool {
if(r.recurrenceRules.count > 0 && r.dueDateComponents == nil) {
let startDate = NSDate()
let dueDate: NSDate = NSDate(timeIntervalSinceNow: 31536000) // 1 year from now
let gregorian = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)
let unitFlags = NSCalendarUnit(UInt.max)
r.dueDateComponents = gregorian?.components(unitFlags, fromDate: dueDate)
}
var error: NSError?
return eventStore.saveReminder(r, commit: true, error: &error)
}
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).