Local EKCalendar saved with not errors, disappears - ios

I'm trying to create a calendar with EKSourceTypeLocal source. Iterate over the self.eventStore.sources, find the one with sourceType == .Local and attempt to create a new calendar with it.
self.calendar = EKCalendar(forEntityType: EKEntityType.Event, eventStore: self.eventStore)
self.calendar?.title = "My Awesome Calendar"
self.calendar?.source = src // src.sourceType == .Local
print("Created \(self.calendar!)")
do {
try self.eventStore.saveCalendar(self.calendar!, commit: true)
} catch let err as NSError {
print("Whoops: \(err)")
}
That executes without a problem, and allows me to add some events to that calendar as well. However, when I switch to the native Calendar app, this new one is no there, if I query self.eventStore.calendarsForEntityType(EKEntityType.Event) after the above has completed, it's not there either, and if I restart the app, the calendar I created and all its events are nowhere to be found. What's happening?

If you have calendars from other sources active on your device, calendars of type EKSourceTypeLocal will not be visible in the Apple Calendars app.
Additionally, the event store will not show the local calendar when other calendar sources are active.
If you de-activate the other calendars, you should be able to see the saved local calendar in both the Calendars app and the event store.
I’m assuming your app has the necessary permissions obtained using requestAccessToEntityType:completion:.

Related

EKEventStore will not return Exchange Calendar items by external or local identifier

I am using the EKEventStore API to save events to the default calendar using the following:
EKEventStore - saveEvent:span:commit:error:.
Once the event is saved, I store the externalID and localID in my database for future reference using the following:
externalID = [myEvent calendarItemExternalIdentifier]; and
localID = [myEvent eventIdentifier].
The problem I am having is that when I then go back to try and retrieve the event using the following:
[[eventStore calendarItemsWithExternalIdentifier:externalID] firstObject]
OR
[eventStore eventWithIdentifier:localID],
iOS is not able to find my event.
If I run the exact same code, but have my default calendar set to an iCloud calendar, however, everything works correctly.
But if the default calendar is an Exchange calendar, I am getting the following error message:
"Error getting calendar item with UUID [insert externalID here]: Error Domain=EKCADErrorDomain Code=1010 "(null)""
Has anyone encountered this issue?
I have had this code deployed for over 2 years now and users recently reported they weren't able to open appointments created on Exchange calendars. Not sure what changed or when, but I have tested this on iOS 10 and 11, and both have the issue.
Any insight would be greatly appreciated,
Sincerely,
~Arash
Unfortunately not... we were able to work around this issue by including an internal reference ID in the notes of the event. Then, if both the externalEventID and eventID failed to return the event, which is our issue with Exchange, then we fall back to use the start and end date in an EKEventSearchCallback to look for our event.
Basically, it ends up looking like this:
EKEventSearchCallback searchBlock = ^(EKEvent *event, BOOL *stop)
{
if([MyCustomClass event:event
notesMatchesKnownValueDict:knownValueDict])
{
foundEvent = event;
*stop = YES;
}
};
NSPredicate *predicate = [eventStore predicateForEventsWithStartDate:startDate
endDate:endDate
calendars:nil];
[eventStore enumerateEventsMatchingPredicate:predicate usingBlock:searchBlock];
Another solution might be waiting for the event to sync with Exchange. For example, I save the event and keep track of the ID assigned to it. Have a loop check every few seconds for a change. When the change happens, it's on the exchange server and you're good to reference that in your DB.
Try it out, let me know if it works!

Firebase does not update values throughout an app until the user is logged out

So, I have been coming across a problem where my Firebase app does not update user values when a user makes an update. To be more clear: Lets say user 1 has a photo of a dog and then changes it to a cat.
Once they change it to a cat, my node value in Firebase is successfully updated but the user themselves won't be able to see the change in other previously loaded areas in the app (other places with the dog picture) until they log out and then log back in.
For this reason I was wondering if there was any way to conduct a background app refresh that way all previous dog values in the app are changed to cat values without the user having to log out and then log back in. Please note that this same problem occurs not only with my user's profile picture but also any other user field I have setup.
Here is how I am updating a node value for my user in Firebase:
let storageRef = FIRStorage.storage().reference()
_ = FIRStorageMetadata()
let filePath = "\(FIRAuth.auth()?.currentUser?.uid)/\("userPhoto")"
let profileImageData = UIImageJPEGRepresentation(self.profilePicture.image!, 1.0)
if let data = profileImageData {
storageRef.child(filePath).put(data, metadata: nil){(metaData,error) in
if let error = error {
print(error.localizedDescription)
return
} else {
let downloadURL = metaData!.downloadURL()!.absoluteString
let userPhotoUpdateRef = FIRDatabase.database().reference().child("users").child(self.currentUser).child("userPhoto")
userPhotoUpdateRef.setValue(downloadURL)
}
}
}
If you have any questions please ask! Any help would be appreciated!
The Firebase SDK for Cloud Storage provides an easy way to read file from and write files to cloud storage. It does not provide a way to monitor those files.
The easiest way to provide a monitoring approach is to write the metadata of the files to the Firebase Realtime Database. See this short section in the Storage docs for a brief mention of that: https://firebase.google.com/docs/storage/ios/file-metadata#custom_metadata
When you write data to a location in the Firebase Database, all apps that are actively monitoring that location will be instantly updated. When they get that update, you can reload the image from Cloud Storage for Firebase.

How to test if "Allow Full Access" permission is granted from containing app?

I'm working on a keyboard extension project. At some points of the application code I need to test if the user have granted the "Allow Full Access" permission for the keyboard extension. The deal is that I need to do those tests from the application side, and based on this let the user to access keyboard settings or alert him in case the permission wasn't granted.
The problem is that the methods that provided here like:
func isOpenAccessGranted() -> Bool {
return UIPasteboard.generalPasteboard().isKindOfClass(UIPasteboard)
}
or:
func isOpenAccessGranted() -> Bool {
let fm = NSFileManager.defaultManager()
let containerPath = fm.containerURLForSecurityApplicationGroupIdentifier(
"group.com.example")?.path
var error: NSError?
fm.contentsOfDirectoryAtPath(containerPath!, error: &error)
if (error != nil) {
NSLog("Full Access: Off")
return false
}
NSLog("Full Access: On");
return true
}
Working only from the keyboard side, as the keyboard is the only one that is affected from this permission. From the containing app side both of those methods always return true.
Does someone knows a reliable way to test this from the application side?
Consider using NSUSerdefaults in your app and keyboard. In order for an extension and app to be able to share the same NSUserdefaults, you can simply turn on App Groups. Select your main app target under your project in the project navigator and go to capabilities and enable App Groups by toggling it to on, adding your Developer profile and fixing the possibly arising issues.
Now create a new container. According to the help, it must start with “group.”, so give it a name like “group.com.mycompany.myapp”. Select your Today Extension target and repeat this process of switching on app groups. Don’t create a new one, rather select this newly created group to signify that the Today Extension is a part of the group.
This will now allow you to share the same NSUserdefaults as your container app.
All you have to do now is use the code you provided and add the saving method to the NSUserdefaults like so:
func isOpenAccessGranted() -> Bool {
let defaults = NSUserDefaults.standardUserDefaults()
let fm = NSFileManager.defaultManager()
let containerPath = fm.containerURLForSecurityApplicationGroupIdentifier(
"group.com.example")?.path
var error: NSError?
fm.contentsOfDirectoryAtPath(containerPath!, error: &error)
if (error != nil) {
NSLog("Full Access: Off")
defaults.setBool(false, forKey: "hasFullAccess")
return false
}
NSLog("Full Access: On");
defaults.setBool(true, forKey: "hasFullAccess")
return true
}
Do this in your keyboard extension. And then in your parent app, simply retrieve them and act upon their value.
let hasFullAccess : Bool = NSUserDefaults.standardUserDefaults().boolForKey("hasFullAccess")
if hasFullAccess{
//User granted full access
}
else{
//User didn't grant full access
}
If the user didn't grant full access the show an alert, else code away!
EDIT:
After contacting Apple's Technical Developer support they told me that this is not possible to achieve in any supported way as of right now. Their response is below:
Our engineers have reviewed your request and have concluded that there
is no supported way to achieve the desired functionality given the
currently shipping system configurations.
If you would like for Apple to consider adding support for such
features in the future, please submit an enhancement request via the
Bug Reporter tool at https://developer.apple.com/bug-reporting/.
Hope that helps, Julian
You can easily test whether the "Allow Full Access" permission is granted on iOS 11 and later.
To get the "Allow Full Access" permission, first subclass the
UIInputViewController class.
Add the code. The code returns a bool value.
Objective-C
[self hasFullAccess];
Swift
self.hasFullAccess
https://developer.apple.com/documentation/uikit/uiinputviewcontroller/2875763-hasfullaccess?changes=_2
Objective-C
[UIInputViewController new].hasFullAccess;

create a custom calendar

I want to create a new custom calendar on iOS, in my application.
I search for some explications on how do it work. But my code doesn't work / the calendar is not created
EKEventStore *eventStore = [[EKEventStore alloc] init];
EKSource *theSource = nil;
for (EKSource *source in eventStore.sources) {
if (source.sourceType == EKSourceTypeLocal) {
theSource = source;
break;
}
}
NSString *identifier;
EKCalendar *cal;
if (identifier == nil)
{
cal = [EKCalendar calendarForEntityType:EKEntityTypeEvent eventStore:eventStore];
cal.title = #"MyCustomCalendar";
cal.source = theSource;
[eventStore saveCalendar:cal commit:YES error:nil];
NSLog(#"cal id = %#", cal.calendarIdentifier);
}
else
{
cal = [eventStore calendarWithIdentifier:identifier];
}
I'm not sure if you are completely set on using EKCalendar, but you do not need to to make a calendar in iOS. You can design your own however you want with a UICollectionView or Buttons laid out dynamically. The important part is to make sure all of your dates are arranged correctly, which is not necessarily trivial. But, if you have a good way to get the first weekday for the first day of a given month, you can get it done. I followed this algorithm: http://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week which seemed a bit complicated, but in practice was easy to code and reliable. Otherwise I believe apple has a 'firstWeekday' method/property you can try, but that didn't work for me. Once you have the right first weekday you can enumerate through the days of the rest of the month and put them how you want on your calendar.
It seems like you are unsure how to setup your calendar data with EKCalendar. I found a nice tutorial you may find useful:
http://oleb.net/blog/2012/05/creating-and-deleting-calendars-in-ios/
For more specific questions try the documentation, you can find it with a google search.
Otherwise, what kind of data export are you looking for? iCloud? Local? Are you working with an actual calendar provided or just saving something with all of your calendar data that another application can read?
The one possible way to get this issue is, mismatching calendar source type.
Since you've created the calendar with local source type, system could not find local source if you already synced your iCloud / gmail / exchange calendars.
So my suggestion is don't stick with local source and try to create the calendar in any available source. If you go for this approach, you'll also be ready for solving appropriate issues (like the case iCloud is synced but user has not given permission for iCloud-calendar sync).

iCloud calendar synchronization issue while creating calendar from application

Trying to implement iCloud Calendar synchronization for iOS.
The idea is to create a new calendar from my app and sync it with iCloud when iCloud sync is on actually.
To get corresponding source I'm using the following code:
EKSource* localSource=nil;
for (EKSource* source in self.eventStore.sources)
{
if(source.sourceType == EKSourceTypeCalDAV && [source.title isEqualToString:#"iCloud"])
{
localSource = source;
break;
}
}
Then creating a calendar in that source and saving.
When iCloud sync is on and Calendar synchronization is on for iCloud as well from iPhone->Settings->iCloud->Calendar It's working just fine.
After switching off calendar synchronization from above mentioned settings theoretically it should not allow to create calendar in that store any more. But actually even in that case it allows to get corresponding iCloud store from my application and create/save a new calendar.
After creating a new calendar it's not showing it in iPhone's calendars list. But when you logging in to the iCloud web interface you can see there a lot of calendars with the same name that you have just added. The number of calendars with that name is getting more and more. Seams like there is an infinite loop problem in calendar synchronization for iCloud. So far seams like it's an iOS problem and could not find any report on that anywhere.
Not sure if you figured this out, if you did, post your solution :-)
But it does seem to be a bug - I just checked iCal on my Mac and it's loaded up with duplicate calendars.
Just figuring it out, but some rough code I think I have working is to create a Calendar in the EKSource and then check for that Calendar.
Something like this:
-(BOOL)testCal {
BOOL cal = 0;
NSUInteger counter = 1;
for (EKCalendar *thisCalendar in [[DGEK eventStore]calendars] ){
NSLog(#"%#", thisCalendar.title);
if ([thisCalendar.title isEqualToString:#"YourCalName"]) {
cal = YES;
return cal;
}
counter++;
}
return cal;
}
I think that works. Just doing some more testing at the moment.
If the Cal doesnt exist I'm getting the default source with something like this:
source = [[[self eventStore] defaultCalendarForNewEvents] source];
Hope that helps.

Resources