I have the iCloud calendars and some subscribed calendars on my device, all calendars apps including the native calendar app display the calendars correctly, but in my app i can't retrieve them.
I am trying to retrieve the event store calendars as follow:
A)
NSArray *calendarArray = [self.eventStore calendarsForEntityType:EKEntityTypeEvent];
// always return result an empty array
B)
NSMutableArray *calendars = [[NSMutableArray alloc]init];
for (EKSource *source in self.eventStore.sources) {
NSSet *sourceCalendars = [source calendarsForEntityType:EKEntityTypeEvent];
[calendars addObjectsFromArray:sourceCalendars.allObjects];
}
// always return result an empty array
The event store instance is allocated and initialized correctly, and the privacy for accessing calendars also granted.
What can I do ?
Regards
When you have more than 100 calendar and Reminder lists then event store stop working properly.
For more info check this link: http://support.apple.com/kb/HT4489?viewlocale=en_US
Related
I've developed an app that uses calendar using the eventkit.
The user can push a button and delete a specific event. Everything works just fine, except when it comes to recurrent events.
If the user itself created the recurrent events, it works as expected, the single event is deleted and the rest remains in calendar. But if the recurrent events is created by other and accepted by user and then try to delete one specific of those events, they all disappear from calendar. Why?
In this case iPad only uses one Exchange calendar, can the problem be Exchange specific?
/* Get the Exchange Calendar */
EKEventStore* store = [[EKEventStore alloc] init];
NSError* error = nil;
NSMutableArray* calendars = [[store calendarsForEntityType:EKEntityTypeEvent] mutableCopy];
NSMutableArray* cals = [[NSMutableArray alloc] init];
for (EKCalendar *cal in calendars) {
if (cal.type == EKCalendarTypeExchange) {
[cals addObject:cal];
break;
}
}
/* get the correct event, get Events with startDate & endDate and differ with eventId */
// (tappedEvent = event to remove)
NSPredicate *predicate = [store predicateForEventsWithStartDate:[tappedEvent valueForKey:#"from"] endDate:[tappedEvent valueForKey:#"to"] calendars:cals];
NSArray *theEvents=[store eventsMatchingPredicate:predicate];
NSString *recurrenceEventIdentifier = [tappedEvent valueForKey:#"appID"];
for(EKEvent * theEvent in theEvents){
if([theEvent.eventIdentifier isEqualToString: recurrenceEventIdentifier] && ![store removeEvent:theEvent span:EKSpanThisEvent error:&error]){
NSLog(#"Error in removing event: %#",error);
}
}
[store commit:&error];
if(error){
...
}
I have one question near the end.
I am working from the belief/experience that seeding iCloud more than once is a bad idea and that if a user can do the wrong thing, he probably will sooner or later.
What I want to do:
A. When the user changes the app preference "Enable iCloud" from NO to YES, display AlertView asking (Yes or No) if the user wishes to seed the cloud with existing non-iCloud Data.
B. Ensure that the app seeds iCloud only once on an iCloud account, refraining to put up the AlertView once seeding is completed the first time.
My Method:
Following Apple's Docs concerning the proper use of NSUbiquitousKeyValueStore, I am using the following method in, - (void)application: dFLWOptions:
- (void)updateKVStoreItems:(NSNotification*)notification {
// Get the list of keys that changed.
NSDictionary* userInfo = [notification userInfo];
NSNumber* reasonForChange = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];
NSInteger reason = -1;
// If a reason could not be determined, do not update anything.
if (!reasonForChange)
return;
// Update only for changes from the server.
reason = [reasonForChange integerValue];
if ((reason == NSUbiquitousKeyValueStoreServerChange) ||
(reason == NSUbiquitousKeyValueStoreInitialSyncChange)) { // 0 || 1
// If something is changing externally, get the changes
// and update the corresponding keys locally.
NSArray* changedKeys = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey];
NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
// This loop assumes you are using the same key names in both
// the user defaults database and the iCloud key-value store
for (NSString* key in changedKeys) {//Only one key: #"iCloudSeeded" a BOOL
BOOL bValue = [store boolForKey:key];
id value = [store objectForKey:#"iCloudSeeded"];
[userDefaults setObject:value forKey:key];
}
}
}
Include the following code near the top of application: dFLWO:
NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(updateKVStoreItems:)
name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
object:store]; // add appDelegate as observer
After loading iCloud Store, then seed it with non-iCloud data ONLY if seeding has never been done
- (BOOL)loadiCloudStore {
if (_iCloudStore) {return YES;} // Don’t load iCloud store if it’s already loaded
NSDictionary *options =
#{
NSMigratePersistentStoresAutomaticallyOption:#YES
,NSInferMappingModelAutomaticallyOption:#YES
,NSPersistentStoreUbiquitousContentNameKey:#"MainStore"
};
NSError *error=nil;
_iCloudStore = [_coordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil URL:[self iCloudStoreURL] options:options error:&error];
if (_iCloudStore) {
NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
BOOL iCloudSeeded =
[store boolForKey:#"iCloudSeeded"];//If the key was not found, this method returns NO.
if(!iCloudSeeded) // CONTROL IS HERE
[self confirmMergeWithiCloud]; // Accept one USER confirmation for seeding in AlertView ONCE world wide
return YES; // iCloud store loaded.
}
NSLog(#"** FAILED to configure the iCloud Store : %# **", error);
return NO;
}
Once the seeding is completed do the following to prevent any repeat seeding:
if (alertView == self.seedAlertView) {
if (buttonIndex == alertView.firstOtherButtonIndex) {
[self seediCloud];
NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
[store setBool:YES forKey:#"iCloudSeeded"]; // NEVER AGAIN
//[store synchronize];
}
}
}
Be sure to get a total iCloud reset before the above process using:
[NSPersistentStoreCoordinator
removeUbiquitousContentAndPersistentStoreAtURL:[_iCloudStore URL]
options:options
error:&error])
This is a very tidy solution to my problem, IMHO, but I can not quite get it done.
MY QUESTION:
How do I respond to the first notification to updateKVStoreItems: above? It is a notification with bad info. I says the value is TRUE, but I have never set it to TRUE. How do I set default values for a key in NSUbiquitousKeyValueStore?
I find that the first notification is of reason : NSUbiquitousKeyValueStoreInitialSyncChange
When that note comes in, bValue is YES. THIS IS MY PROBLEM. It is as if, iCloud/iOS assumes any new BOOL to be TRUE.
I need this value to be NO initially so that I can go ahead and follow the Apple Docs and set
the NSUserDefault to NO. And then Later when the seeding is done, to finally set the value: YES for the key:#"iCloudSeeded"
I find I can not penetrate the meaning of the following from Apple:
NSUbiquitousKeyValueStoreInitialSyncChange
Your attempt to write to key-value storage was discarded because an initial download from iCloud has not yet happened.
That is, before you can first write key-value data, the system must ensure that your app’s local, on-disk cache matches the truth in iCloud.
Initial downloads happen the first time a device is connected to an iCloud account, and when a user switches their primary iCloud account.
I don't quite understand the implications of number 2 below, which I found online:
NSUbiquitousKeyValueStoreInitialSyncChange – slightly more complicated, only happens under these circumstances:
1. You start the app and call synchronize
2. Before iOS has chance to pull down the latest values from iCloud you make some changes.
3. iOS gets the changes from iCloud.
If this problem was with NSUserDefaults and not NSUbiquitousKeyValueStore, I believe I would need to go to registerDefaults.
I am almost there,
How do I do this please!
Thanks for reading, Mark
The code was looking for both
A. NSUbiquitousKeyValueStoreInitialSyncChange and
B. NSUbiquitousKeyValueStoreServerChange
I was unable to figure out what to do with the notifications. I know see that I did not need to do anything with either. My app only needs to read and write, in order to solve the problem I laid out in my question header.
The app gets the current value with:
NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
BOOL iCloudSeeded = [store boolForKey:#"iCloudSeeded"];
The app sets the value in the NSUbiquitousKeyValueStore with:
NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
[store setBool:YES forKey:#"iCloudSeeded"];
I believe I am correct in saying the following: Writing is done into memory. Very soon thereafter the data is put by the system onto disk.
From there it is taken and put into iCloud and is made available to the other devices running the same app on the same iCloud account. In the application I have described, no observer needs to be added, and
nothing else needs to be done. This is maybe an "unusual" use of NSUbiquitousKeyValueStore.
If you came here looking for a an more "usual" use, say when a user type something into a textview and it later
appears on a view of other devices running the same app, check out a simple demo I came across at :
https://github.com/cgreening/CMGCloudSyncTest
The better functioning (monitoring only) notification handler follows:
- (void)updateKVStoreItems:(NSNotification*)notification {
NSNumber *reason = notification.userInfo[NSUbiquitousKeyValueStoreChangeReasonKey];
if(!reason) return;
// get the reason code
NSInteger reasonCode = [notification.userInfo[NSUbiquitousKeyValueStoreChangeReasonKey] intValue];
BOOL bValue;
NSUbiquitousKeyValueStore *store;
switch(reasonCode) {
case NSUbiquitousKeyValueStoreServerChange:{ // code 0, monitoring only
store = [NSUbiquitousKeyValueStore defaultStore];
bValue = [store boolForKey:#"iCloudSeeded"];
id value = [store objectForKey:#"iCloudSeeded"];
DLog(#"New value for iCloudSeeded=%d\nNo Action need be take.",bValue);
// For monitoring set in UserDefaults
[[NSUserDefaults standardUserDefaults] setObject:value forKey:#"iCloudSeeded"];
break;
}
case NSUbiquitousKeyValueStoreAccountChange: {// ignore, log
NSLog(#"NSUbiquitousKeyValueStoreAccountChange");
break;
}
case NSUbiquitousKeyValueStoreInitialSyncChange:{ // ignore, log
NSLog(#"NSUbiquitousKeyValueStoreInitialSyncChange");
break;
}
case NSUbiquitousKeyValueStoreQuotaViolationChange:{ // ignore, log
NSLog(#"Run out of space!");
break;
}
}
}
Adding 9/3/14
So sorry but I continued to have trouble using a BOOL, I switched to an NSString and now
all is well.
METHOD TO ENSURE THAT THE "MERGE" BUTTON FOR SEEDING ICOUD IS USED AT MOST ONCE DURING APP LIFETIME
Use NSString and not BOOL in KV_STORE. No need to add observer, except for learning
In Constants.h :
#define SEEDED_ICLOUD_MSG #"Have Seeded iCloud"
#define ICLOUD_SEEDED_KEY #"iCloudSeeded"
Before calling function to seed iCloud with non-iCloud data:
NSUbiquitousKeyValueStore* kvStore = [NSUbiquitousKeyValueStore defaultStore];
NSString* strMergeDataWithiCloudDone =
[kvStore stringForKey:ICLOUD_SEEDED_KEY];
NSComparisonResult *result = [strMergeDataWithiCloudDone compare:SEEDED_ICLOUD_MSG];
if(result != NSOrderedSame)
//put up UIAlert asking user if seeding is desired.
If user chooses YES : set Value for Key after the merge is done.
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (alertView == self.seedAlertView) {
if (buttonIndex == alertView.firstOtherButtonIndex) {
[self seediCloudwithNoniCloudData];
NSUbiquitousKeyValueStore* kvStoretore = [NSUbiquitousKeyValueStore defaultStore];
[store setObject:SEEDED_ICLOUD_MSG forKey:ICLOUD_SEEDED_KEY];
}
}
}
Thereafter on all devices, for all time, the code
NSUbiquitousKeyValueStore* kvStoretore = [NSUbiquitousKeyValueStore defaultStore];
NSString* msg =
[kvStore stringForKey:ICLOUD_SEEDED_KEY];
produces: msg == SEEDED_ICLOUD_MESSAGE
In setting of iPhone, I was login the google calendar.
I program an iPhone app, that can detect the event that is belonged to the logged in google calendar as above.
EKEventStore* eventStore;
eventStore = [[EKEventStore alloc] init];
NSPredicate* predicate = [eventStore predicateForEventsWithStartDate:firstDate endDate:lastDate calendars:eventStore.calendars];
NSArray* fetchedEvents = [eventStore eventsMatchingPredicate:predicate];
for(EKEvent* ecEvent in fetchedEvents)
{
// How to do
}
How can I do that?
I also research but almost have to:
Method 1: program for user choose an calender (include google calendar)
Method 2: program for get default calendar (may be that is google calendar)
Method 3: program a app that login and get google calendar as same as Using Google Calendar Api in iPhone have other method.
Thank you for your help.
Re: question 1, this will tell you if the event belongs to a google calendar:
EKCalendar *calendar = event.calendar;
if ([[calendar source] sourceType] == EKSourceTypeCalDAV &&
[[[calendar source] title] isEqualToString:#"Gmail"])
NSLog(#"found a Gcal event");
Sometimes (if there are multiple google accounts configured) [[calendar source] title] will return the gmail address instead of #"Gmail", so you may want to check for that as well.
I want to add a new calendar event to the default iOS calendar. That works fine, until a user uses Gmail for syncing calendars.
I use the following code:
if (!calendar) {
calendar = [EKCalendar calendarForEntityType:EKEntityTypeEvent eventStore:eventStore];
// set calendar name
[calendar setTitle:#"My calendar"];
EKSource *theSource = [eventStore defaultCalendarForNewEvents].source;
calendar.source = theSource;
// save this in NSUserDefaults data for retrieval later
NSString *calendarIdentifier = [calendar calendarIdentifier];
NSError *error = nil;
BOOL saved = [eventStore saveCalendar:calendar commit:YES error:&error];
if (saved) {
// saved successfuly, store it's identifier in NSUserDefaults
[[NSUserDefaults standardUserDefaults] setObject:calendarIdentifier forKey:#"railplanner_calendar_identifier"];
} else {
// unable to save calendar
return NO;
}
}
This works fine when iCloud is enabled, or with local calendars.
But when a user uses Gmail for syncing calendars, my custom calendar doesn't appear in the calendars list. The local calendars disappear too.
Does anyone know how I can add a new calendar with a new event to a Gmail (or Google) Calendar?
Many thanks in advance.
This is likely to be impossible with the current Google Calendar synchronization, you could detect the type of the default calendar, and in the case of Google use the related Google iPhone API to create your new calendar (not really helpful I know)
My app uses EventKit to read and write new reminders to and from the Reminders app, which works well. However, I've only found a way to write reminders to the default list that the user selects in the Settings app... My question is, does anyone know if there's a way to create a whole new list, rather than use the default list.
Nope.
Apple doesn't allow apps to write to lists other than the default list - looking through the docs yields no way to do so.
YES!!!
Looking through some more literature, I found this!
It seems that the EKReminder objects can be added to any list - based on my limited understanding, this should work at least to write to a different list:
NSArray *calendars = [_eventStore
calendarsForEntityType:EKEntityTypeReminder];
for (EKCalendar *calendar in calendars)
{
NSLog(#"Calendar = %#", calendar.title);
}
EKCalendar *calendar = ... //pick one.
EKReminder *reminder = [EKReminder reminderWithEventStore:self.eventStore];
reminder.title = #"Go to the store and buy milk";
reminder.calendar = calendar;
NSError *error = nil;
[_eventStore saveReminder:reminder commit:YES error:&error];