Problems accessing Calendar using EKEventStore on OSX Sierra with Swift 3 - swift

This appears very simple, but I've been struggling for several days to get access to the Calendar on OSX. I have switched on the App Sandbox capability, and I've ticked the "Calendar" box in App Data. I have created very simple app with the following view controller class:
import Cocoa
import EventKit
class ViewController: NSViewController {
var eventControl = EKEventStore()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
As you can see, the only lines of code I've added are to import EventKit and to initialise eventControl.
When I run this in debug, I get an error at the eventControl initialisation line
2016-10-28 15:02:00.056521 calendarTest[4105:847101] CoreData: XPC: Unable to load metadata: Error Domain=NSCocoaErrorDomain Code=134070 "An error occurred in the persistent store." UserInfo={Problem=request failed, insufficient permission}
2016-10-28 15:02:00.057742 calendarTest[4105:847101] [error] error: -addPersistentStoreWithType:NSXPCStore configuration:(null) URL:file:///Users/patrickramsden/Library/Calendars/Calendar%20Cache options:{
NSInferMappingModelAutomaticallyOption = 1;
NSMigratePersistentStoresAutomaticallyOption = 1;
agentOrDaemon = 1;
serviceName = "com.apple.CalendarAgent.database";
} ... returned error Error Domain=NSCocoaErrorDomain Code=134070 "An error occurred in the persistent store." UserInfo={Problem=request failed, insufficient permission} with userInfo dictionary {
Problem = "request failed, insufficient permission";
}
I can't work out how to get the right permissions.
I am using Xcode 8.1 and macOS Sierra 10.12.1

You need to add a usage description to your info.plist. This is a small description of why you need access to the services that is presented to the user.
<key>NSCalendarsUsageDescription</key>
<string>Description of why you need access to the Calendar</string>

Has anyone else figured this out?
I am running into the same issue, however trying to access anything in the event Store returns nil. Even though "access is granted". Such as:
let eventStore = EKEventStore()
switch EKEventStore.authorizationStatus(for: .event) {
case .authorized:
print("Access Granted")
break
case .denied:
print("Access denied")
case .notDetermined:
eventStore.requestAccess(to: .event, completion:
{(granted: Bool, error: Error?) -> Void in
if granted {
print("Access Granted")
} else {
print("Access denied")
}
})
break
default:
print("Case Default")
break
}
print(eventStore.calendars(for: .event))

Getting the same messages: How to prevent EventStore access error on first run
However the access is working correctly once you start the application again after the user grants permission! My app is not sandboxed. Have you set the "Privacy - Calendars Usage Description" key in your plist?

Related

requestApplicationPermission userDiscoverability failed in Mac Catalyst

I have ios app using requestApplicationPermission userDiscoverability. Today, I add Mac Catalyst support, but failed: "requestApplicationPermission(.userDiscoverability) failed, error: CloudKit access was denied by user settings".
container.requestApplicationPermission(.userDiscoverability) { [weak self] (status, error) in
guard let self = self else { return }
DispatchQueue.main.async {
if let error = error {
foolPrint("requestApplicationPermission(.userDiscoverability) failed, error: \(error.localizedDescription)")
}
}
}
And I cannot find settings about userDiscoverability in mac settings, where is it?
This iCloud user guide shows how to change these settings:
On iCloud.com, go to Account Settings, then click Manage Apps That Can Look You Up in the Advanced section.
To prevent an app from showing your name and email address to other users, deselect the app’s checkbox in the list.

Access iCloud Calendar Events - macOS App

I want access the users iCloud Calendar events in an app for macOS. While researching I have found some tutorials for iOS but I couldn't find one that works on macOS. I tried to understand Apples Developer Documentation for EventKit but didn't manage to get it running.
Thats what I did:
1 - Accessing the Event Store
1.1 I have changed the 'com.apple.security.personal-information.calendars' key to YES in the entitlements file (Stack Overflow Question regarding this).
Screenshot of the .entitlement of the Project:
1.2 Afterwards I tried to request the access (in the viewDidLoad)
let eventStore = EKEventStore()
switch EKEventStore.authorizationStatus(for: .event) {
case .authorized:
print("Acess granted")
case .denied:
print("Access denied")
case .notDetermined:
eventStore.requestAccess(to: .event, completion: {
(granted, error) in
if granted {
print("granted \(granted)")
}else {
print("error \(String(describing: error))")
}
})
default:
print("Case default")
}
2 - Getting the Calendar Events
let sources = eventStore.sources
for source in sources{
print(source.title)
for calendar in source.calendars(for: .event){
print(calendar.title)
}
}
// create dates
let formatter = DateFormatter()
formatter.dateFormat = "yyyy/MM/dd HH:mm"
let startDate = formatter.date(from: "2019/9/12 0:01")!
let endDate = formatter.date(from: "2019/9/12 23:59")!
let calendars = eventStore.calendars(for: .event)
let predicate = eventStore.predicateForEvents(withStart: startDate, end: endDate, calendars: calendars)
let events = eventStore.events(matching: predicate)
print(calendars)
print(events)
When I run this app I get the following console output:
getCalendarEvents[1970:100712] CoreData: XPC: Unable to load metadata: Error Domain=NSCocoaErrorDomain Code=134070 "An error occurred in the persistent store." UserInfo={Problem=request failed, insufficient permission}
2019-09-23 18:35:24.981947+0200 getCalendarEvents[1970:100712] [error] error: -addPersistentStoreWithType:NSXPCStore configuration:(null) URL:file:///Users/henri/Library/Calendars/Calendar%20Cache options:{
NSInferMappingModelAutomaticallyOption = 1;
NSMigratePersistentStoresAutomaticallyOption = 1;
NSPersistentHistoryTrackingKey = {
NSPersistentHistoryTrackingEntitiesToExclude = (
ChangeRequest
);
};
agentOrDaemon = 1;
serviceName = "com.apple.CalendarAgent.database";
} ... returned error Error Domain=NSCocoaErrorDomain Code=134070 "An error occurred in the persistent store." UserInfo={Problem=request failed, insufficient permission} with userInfo dictionary {
Problem = "request failed, insufficient permission";
}
CoreData: error: -addPersistentStoreWithType:NSXPCStore configuration:(null) URL:file:///Users/henri/Library/Calendars/Calendar%20Cache options:{
NSInferMappingModelAutomaticallyOption = 1;
NSMigratePersistentStoresAutomaticallyOption = 1;
NSPersistentHistoryTrackingKey = {
NSPersistentHistoryTrackingEntitiesToExclude = (
ChangeRequest
);
};
agentOrDaemon = 1;
serviceName = "com.apple.CalendarAgent.database";
} ... returned error Error Domain=NSCocoaErrorDomain Code=134070 "An error occurred in the persistent store." UserInfo={Problem=request failed, insufficient permission} with userInfo dictionary {
Problem = "request failed, insufficient permission";
}
[]
[]
error nil
I expected two arrays:
[EKCalendar] and [EKEvent]
I think I really need help here, I have tried a lot but I am relatively new to Swift development, could somebody please help me out?
Thank you!
The reason why you are not being able to access the EKEventStore is because you need to provide a description string as to why you want to do that. This string will be used by the MacOS to provide an explanation to the user why your app wants to have access to the user’s calendar. This string should be provided with the NSCalendarsUsageDescription key in the info.plist file of your app as described here. Even though it is often said in the documentation that is needed for iOS applications, that has also been needed for Mac applications since MacOS Mojave as described here.

Swift. Can't receive information from personal google drive using google drive api

Hi I'm trying to se what files I have stored on my google drive. The sign in process works without problems, but as soon as I try to request the list of files on my google drive I get this error:
Error Domain=com.google.GTLRErrorObjectDomain Code=403 "Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup."
I follow google drives documentation for installing it, but no matter what I do I get that error over and over again.
Here is what my viewDidLoad looks like:
var error: NSError?
GGLContext.sharedInstance().configureWithError(&error)
if error != nil {
print(error!)
return
}
GIDSignIn.sharedInstance().uiDelegate = self
GIDSignIn.sharedInstance().delegate = self
GIDSignIn.sharedInstance().scopes = scopes
let googleSignIn = GIDSignInButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
googleSignIn.center = view.center
view.addSubview(googleSignIn)
This is the function for when the user succesfully signed in:
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
if error != nil {
print(error)
return
}
print("\n")
print(user.profile.email)
print("\n")
let query = GTLRDriveQuery_FilesList.query()
query.pageSize = 10
service.executeQuery(query, delegate: self, didFinish: #selector(displayResultWithTicket(ticket:finishedWithObject:error:))
)
}
I does print out my email correctly, so I know it is logged in, and right after if prints my email the error appears.
This is the displayResultWithTicket function:
#objc func displayResultWithTicket(ticket: GTLRServiceTicket,
finishedWithObject result : GTLRDrive_FileList,
error : NSError?) {
if let error = error {
print(error)
return
}
if let files = result.files, !files.isEmpty {
print("Files:\n")
for file in files {
print(file.name!)
print(file.identifier!)
}
} else {
print("No files found")
}
}
Any help is appreciated!
To explain about your error, this documentation state that you have reached Google Drive APIs max request rate. Each limits varies on the kind of request you use. It was suggested to Batch the request.
Also, make sure that the status of Google+ API was turned ON. Wait for a few minutes after turning ON ensure to get a fresh token before trying again.
For reference on this error, you can check out this SO post.
Keep getting a “Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup” when attempting to google plus login on my web app
Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup error

CKContainer.discoverAllIdentities always fails

The CKContainer.discoverAllIdentities request always fails in my CloudKit app. It has continually failed over the course of several days.
A simplified version of the code that is failing (which results in the same error) is:
private func getContacts(completion: (([CKUserIdentity]?) -> Void)?) {
container.status(forApplicationPermission: .userDiscoverability) { [weak self] status, error in
if let error = error {
print(error)
}
switch status {
case .granted:
self?.discover(completion: completion)
default:
print("status not granted")
}
}
}
private func discover(completion: (([CKUserIdentity]?) -> Void)?) {
let op = CKDiscoverAllUserIdentitiesOperation()
op.qualityOfService = .userInitiated
op.discoverAllUserIdentitiesCompletionBlock = { error in
if let error = error {
print(error)
}
}
op.userIdentityDiscoveredBlock = { identity in
print(identity)
}
op.start()
}
It results in an error being passed to the op.discoverAllUserIdentitiesCompletionBlock. The description of the error in the log is:
<CKError 0x1c4a51a60: "Server Rejected Request" (15/2000); server message = "Internal server error"; uuid = F67453B9-712D-4E5E-9335-929123E3C978; container ID = "iCloud.com.huntermaximillionmonk.topdraw">
Previously, this operation would work, but only for certain iCloud users. Now it's not for both of my test users.
Problem:
This was a problem in iOS 11.0
Based on my testing:
This works ok in Xcode 9.2 / iOS 11.2.1 on the device (not simulator)
After resetting the simulator works for the first time, doesn't work subsequently, however on the device it works repeatedly.
Code:
let queue = OperationQueue()
func requestPermissions(for permissions: CKApplicationPermissions,
completionHandler: #escaping (CKApplicationPermissionStatus, Error?) -> ()) {
CKContainer.default().requestApplicationPermission(permissions) { status, error in
if let error = error {
print("Error for requesting \(permissions) - \(error)")
}
let statusMessage : String
switch status {
case .granted:
statusMessage = "Granted"
case .denied:
statusMessage = "Denied"
case .couldNotComplete:
statusMessage = "Could not complete"
case .initialState:
statusMessage = "Initial state"
}
print("Permission - \(statusMessage)")
completionHandler(status, error)
}
}
private func discoverAllUsers() {
let operation = CKDiscoverAllUserIdentitiesOperation()
operation.userIdentityDiscoveredBlock = { userIdentity in
print("userIdentity = \(userIdentity)")
}
operation.discoverAllUserIdentitiesCompletionBlock = { error in
if let error = error {
print("Discover all users Error: \(error) ")
}
else {
print("Discover all users completed successfully")
}
}
queue.addOperation(operation)
}
Edit:
Apple fixed this issue day after this answer was posted, coincidence?! I don't think so :)
This is not actually the answer to the question, but a fix that helped me to cross over this error. It will require you to change your app UI interaction and add ContactsUI framework to your project, moreover your user will be responsible for selecting a contact with iCloud related email.
Good news is that the method discoverUserIdentity is still works. So, you can use it to get CKUserIdentity from manually selected contact.
func addContact(_ contact:CNContact) {
var lookUpEmails = [CKUserIdentityLookupInfo]()
for email in contact.emailAddresses {
lookUpEmails.append(CKUserIdentityLookupInfo(emailAddress: (email.value as String)))
}
let checkUserOperation = CKDiscoverUserIdentitiesOperation()
checkUserOperation.userIdentityLookupInfos = lookUpEmails
checkUserOperation.userIdentityDiscoveredBlock = { [unowned self] (identity, info) -> Void in
if identity.hasiCloudAccount {
if let recordID = identity.userRecordID {
//do something with discovered user
}
checkUserOperation.cancel()
}
}
checkUserOperation.queuePriority = Operation.QueuePriority.high
CKContainer.default().add(checkUserOperation)
}
It might sound useless, but in my case, it helped me to solve the Server Rejected Request" (15/2000) error, to fix one of the features of my app and continue to use the other feature related code with less efforts than I thought.
I hope someone will find this helpful.
Just another data point on this that might help with the overall picture. I was still seeing this error on 11.2.5 when I used my own iCloud AppleID (with hundreds of contacts) while running a Test App that called discoverAllIdentitiesWithCompletionHandler. I'd get the dreaded
CKError 0x1c0051730: "Server Rejected Request" (15/2000); server message = "Internal server error".
When I switched to run the exact same code on my daughters iOS11.2.5 device (with just a handful of contacts) the code worked fine.
Leads me to believe there is some rate limiting going on when there are a lot of contacts with iOS11.
(P.S. No errors at all running on iOS10)

"An error occurred with the `activeEnergyQuery`. The error was: Authorization not determined."

I'm using Apple's demo HealthKit app called ActivityRings. I have set up the bundle identifiers and entitlements correctly. The iOS app and Watch Extension are working and it's recording data seemingly ok. It should be ok as I haven't touched any code.
However console log says, "An error occurred with the activeEnergyQuery. The error was: Authorization not determined."
As you can see in the reporting query and handler assignment Apple has written to print for this error.
I'd like to know what this is for. Is there broken functionality?
// Create a query to report new Active Energy Burned samples to our app.
let activeEnergyQuery = HKAnchoredObjectQuery(type: activeEnergyType, predicate: predicate, anchor: nil, limit: Int(HKObjectQueryNoLimit)) { query, samples, deletedObjects, anchor, error in
if let error = error {
print("An error occurred with the `activeEnergyQuery`. The error was: \(error.localizedDescription)")
return
}
// NOTE: `deletedObjects` are not considered in the handler as there is no way to delete samples from the watch during a workout.
guard let activeEnergySamples = samples as? [HKQuantitySample] else { return }
sampleHandler(activeEnergySamples)
}
// Assign the same handler to process future samples generated while the query is still active.
activeEnergyQuery.updateHandler = { query, samples, deletedObjects, anchor, error in
if let error = error {
print("An error occurred with the `activeEnergyQuery`. The error was: \(error.localizedDescription)")
return
}
// NOTE: `deletedObjects` are not considered in the handler as there is no way to delete samples from the watch during a workout.
guard let activeEnergySamples = samples as? [HKQuantitySample] else { return }
sampleHandler(activeEnergySamples)
}
currentQuery = activeEnergyQuery
healthStore.executeQuery(activeEnergyQuery)
}
func endWorkoutOnDate(endDate: NSDate) {
workoutEndDate = endDate
workoutButton.setTitle("Begin Workout")
activeEnergyBurnedLabel.setText("0.0")
if let query = currentQuery {
healthStore.stopQuery(query)
}
saveWorkout()
}
requestAuthorizationToShareTypes function
override func willActivate() {
// This method is called when watch view controller is about to be visible to user.
super.willActivate()
// Only proceed if health data is available.
guard HKHealthStore.isHealthDataAvailable() else { return }
// We need to be able to write workouts, so they display as a standalone workout in the Activity app on iPhone.
// We also need to be able to write Active Energy Burned to write samples to HealthKit to later associating with our app.
let typesToShare = Set([
HKObjectType.workoutType(),
HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierActiveEnergyBurned)!])
let typesToRead = Set([
HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierActiveEnergyBurned)!])
healthStore.requestAuthorizationToShareTypes(typesToShare, readTypes: typesToRead) { success, error in
if let error = error where !success {
print("You didn't allow HealthKit to access these read/write data types. In your app, try to handle this error gracefully when a user decides not to provide access. The error was: \(error.localizedDescription). If you're using a simulator, try it on a device.")
}
}
}
AppDelegate.swift
import UIKit
import HealthKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let healthStore: HKHealthStore = HKHealthStore()
func applicationShouldRequestHealthAuthorization(application: UIApplication) {
healthStore.handleAuthorizationForExtensionWithCompletion { success, error in
if let error = error where !success {
print("You didn't allow HealthKit to access these read/write data types. In your app, try to handle this error gracefully when a user decides not to provide access. The error was: \(error.localizedDescription). If you're using a simulator, try it on a device.")
}
}
}
}
Have you setup your iOS app to handle the healthkit authorization from your watch app? When you request permission to use healthkit types from your Apple Watch, a permission dialog shows up on your iOS app. But, you need to tell your iOS app that you are expecting your apple watch to request it. You do this with the following code in your AppDelegate file:
func applicationShouldRequestHealthAuthorization(application: UIApplication) {
let healthStore = HKHealthStore()
healthStore.handleAuthorizationForExtensionWithCompletion { (success, error) -> Void in
//...
}
}
Note that data can get sent directly from the watch's sensors (like heart rate and calories burned) to healthkit without needing permission from your app. It sounds like your permission errors are because you are trying to read the data (which you don't have permission to do yet).
Your app needs to request authorization to read and write active energy samples. Until the user has chosen whether to authorize your app, authorization will be "not determined". See the HKHealthStore documentation for more information about requesting authorization with requestAuthorizationToShareTypes:readTypes:completion:.