I'm trying to add a feature in my application so the user can get a list of reminders using the following method.
The following method is the main method I'm using for retrieving the reminders:
func getReminders(){
var eventStore : EKEventStore = EKEventStore()
// This lists every reminder
var calender = getCalender(eventStore)
let calendars = eventStore.calendarsForEntityType(EKEntityTypeReminder)
as! [EKCalendar]
//cals.append(calender)
var predicate = eventStore.predicateForRemindersInCalendars([calender])
eventStore.fetchRemindersMatchingPredicate(predicate) { reminders in
for reminder in reminders {
println(reminder.title)
self.remindersTitles.append(reminder.title!!)
}}
var startDate=NSDate().dateByAddingTimeInterval(-60*60*24)
var endDate=NSDate().dateByAddingTimeInterval(60*60*24*3)
var predicate2 = eventStore.predicateForEventsWithStartDate(startDate, endDate: endDate, calendars: nil)
println("startDate:\(startDate) endDate:\(endDate)")
var eV = eventStore.eventsMatchingPredicate(predicate2) as! [EKEvent]!
if eV != nil {
for i in eV {
println("Title \(i.title!)" )
println("stareDate: \(i.startDate)" )
println("endDate: \(i.endDate)" )
}
}
}
As you notice I'm creating a calendar and assign it the return value of a method called 'getCalender':
func getCalender(store: EKEventStore) -> EKCalendar {
let defaults = NSUserDefaults.standardUserDefaults()
if let id = defaults.stringForKey("GSCalender") {
return store.calendarWithIdentifier(id)
} else {
var calender = EKCalendar(forEntityType: EKEntityTypeReminder, eventStore: store)
calender.title = "Genie Sugar Calender!"
calender.CGColor = UIColor.redColor().CGColor
calender.source = store.defaultCalendarForNewReminders().source!
var error: NSError?
store.saveCalendar(calender, commit: true, error: &error)
if error == nil {
defaults.setObject(calender.calendarIdentifier, forKey: "GSCalender")
}
if calender == nil {
println("nothing here")
}
return calender
}
}
But the issue is that the application is stuck at this line of the second method:
calender.source = store.defaultCalendarForNewReminders().source!
And returns me this error:
Error getting default calendar for new reminders: Error Domain=EKCADErrorDomain Code=1013 "The operation couldn’t be completed.
Any ideas please to overcome this problem? with my advanced thanks
I noticed the iPhone simulator, after it being reset - returns nil for store.defaultCalendarForNewReminders(). I believe it is a simulator bug.
By doing lots of tests, I came up with an observation that the simulators can fetch defaultCalendarForNewReminders() smoothly (with calendar usage permission)....but with the testing on a real device with iOS 14.6 it is returning nil, even an iPhone has the default calendar for events already!
I had also tried with EKCalendarChooser in order to select other calendars, but still it not worked!
So I think it is an iOS bug.
Related
I can add a new calendar to the user's calendars like this, using the saveCalendar(_:commit:) method:
let ekStore = EKEventStore()
func saveCalendar(calendar: EKCalendar) throws {
try ekStore.saveCalendar(calendar, commit: true)
}
and
let newList = EKCalendar(for: .reminder, eventStore: ekStore)
newList.source = ekStore.defaultCalendarForNewReminders()?.source
newList.title = newListName
newList.cgColor = listColor
do {
try saveCalendar(calendar: newList)
} catch {
print("Error adding list: \(error.localizedDescription)")
}
I then store the calendar object.
When the user finishes editing a calendar (reminders list) in my app, I try to save it like this, using the stored calendar as a starting point:
let updatedList = existingCalendar
updatedList.title = newListName
updatedList.cgColor = listColor
do {
try saveCalendar(calendar: updatedList)
} catch {
print("Error saving list: \(error.localizedDescription)")
}
But the calendar doesn't save and I get this error: That account does not support reminders..
I have also tried explicitly setting the calendar's source:
updatedList.source = ekStore.defaultCalendarForNewReminders()?.source
but then I get this error: That calendar may not be moved to another account..
My Question: How do I update calendars (reminder lists) from my app?
I am having trouble understanding some of the CloudKit sharing concepts and the WWDC 2016 "What's new in CloudKit" video doesn't appear to explain everything that is required to allow users to share and access shared records.
I have successfully created an app that allows the user to create and edit a record in their private database.
I have also been able to create a Share record and share this using the provided sharing UIController. This can be successfully received and accepted by the participant user but I can't figure out how to query and display this shared record.
The app creates a "MainZone" in the users private database and then creates a CKRecord in this "MainZone". I then create and save a CKShare record and use this to display the UICloudSharingController.
How do I query the sharedDatabase in order to access this record ? I have tried using the same query as is used in the privateDatabase but get the following error:
"ShareDB can't be used to access local zone"
EDIT
I found the problem - I needed to process the accepted records in the AppDelegate. Now they appear in the CloudKit dashboard but I am still unable to query them. It seems I may need to fetch the sharedDatabase "MainZone" in order to query them.
Dude, I got it: First you need to get the CKRecordZone of that Shared Record. You do it by doing the following:
let sharedData = CKContainer.default().sharedCloudDatabase
sharedData.fetchAllRecordZones { (recordZone, error) in
if error != nil {
print(error?.localizedDescription)
}
if let recordZones = recordZone {
// Here you'll have an array of CKRecordZone that is in your SharedDB!
}
}
Now, with that array in hand, all you have to do is fetch normally:
func showData(id: CKRecordZoneID) {
ctUsers = [CKRecord]()
let sharedData = CKContainer.default().sharedCloudDatabase
let predicate = NSPredicate(format: "TRUEPREDICATE")
let query = CKQuery(recordType: "Elder", predicate: predicate)
sharedData.perform(query, inZoneWith: id) { results, error in
if let error = error {
DispatchQueue.main.async {
print("Cloud Query Error - Fetch Establishments: \(error)")
}
return
}
if let users = results {
print(results)
self.ctUsers = users
print("\nHow many shares in cloud: \(self.ctUsers.count)\n")
if self.ctUsers.count != 0 {
// Here you'll your Shared CKRecords!
}
else {
print("No shares in SharedDB\n")
}
}
}
}
I didn't understand quite well when you want to get those informations. I'm with the same problem as you, but I only can get the shared data by clicking the URL... To do that you'll need two functions. First one in AppDelegate:
func application(_ application: UIApplication, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShareMetadata) {
let acceptSharesOperation = CKAcceptSharesOperation(shareMetadatas: [cloudKitShareMetadata])
acceptSharesOperation.perShareCompletionBlock = {
metadata, share, error in
if error != nil {
print(error?.localizedDescription)
} else {
let viewController: ViewController = self.window?.rootViewController as! ViewController
viewController.fetchShare(cloudKitShareMetadata)
}
}
CKContainer(identifier: cloudKitShareMetadata.containerIdentifier).add(acceptSharesOperation)
}
in ViewConroller you have the function that will fetch this MetaData:
func fetchShare(_ metadata: CKShareMetadata) {
let operation = CKFetchRecordsOperation(recordIDs: [metadata.rootRecordID])
operation.perRecordCompletionBlock = { record, _, error in
if error != nil {
print(error?.localizedDescription)
}
if record != nil {
DispatchQueue.main.async() {
self.currentRecord = record
//now you have your Shared Record
}
}
}
operation.fetchRecordsCompletionBlock = { _, error in
if error != nil {
print(error?.localizedDescription)
}
}
CKContainer.default().sharedCloudDatabase.add(operation)
}
As I said before, I'm now trying to fetch the ShareDB without accessing the URL. I don't want to depend on the link once I already accepted the share. Hope this helps you!
I have a large set of activities that I want to batch save to the calendar. Which calendar is selected by the user. They have the option to export to iCloud calendar or Google calendar. When exporting to iCloud calendar everything runs smoothly. No problems. However, when exporting to Google calendar I am encountering som weird issues. The number of events to be saved is somewhere around 60-90 events. I use the function provided below to export the calendar events in the background. The operation runs fine and during logging all events are included and when iterating over the events they have all receieved an eventIdentifier.
However, on every occasion, about 5-10 events are not synced up to Google calendar and not shown on the phones calendar. The events that are not showing are different for each export, so it is not the event itself that is faulty. I have tried so many different approaches, but no success.
What I have tried:
- Removed the background operation.
- Removed calendar status callback.
- Moved the function outside of the closure and calling it directly.
- Removed the #autorelease.
- Checked that the EKEventStore and EKCalendar is alive during the whole operation.
Does anyone of you know a good explanation for this? I checked if google had any limits on saves, but according to the documents the calendar may turn into readonly when importing 10 000+ events in a short time, in which I am not even close to.
I would love any feedback. This is driving me crazy. As I said earlier, iCloud export works just fine.
Here is my export code:
import UIKit
import EventKit
struct Activity {
var title : String!
var startDate : NSDate!
var endDate : NSDate!
}
class CalendarManager: NSObject {
class func saveToCalendarInBackground(activities: [Activity], eventStore: EKEventStore, calendar: EKCalendar, calendarStatus: (status: String!, progress: Float!) -> (), completion:(success: Bool!, error: NSError!) -> ()) -> Void {
//Run the operations on another thread (not main), but do UI updates on the main thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
if activities.count > 0 {
var formatter = NSDateFormatter()
autoreleasepool {
//Save duties to calendar
for (index, activity) in enumerate(activities) {
//Update status
let progress = Float(index + 1) / Float(activities.count)
//Return callbacks on main thread
dispatch_sync(dispatch_get_main_queue(), {
calendarStatus(status: "Saving \(index+1) of \(activities.count)", progress: progress)
})
//Save activity
var event = EKEvent(eventStore: eventStore)
event.calendar = calendar
event.title = activity.title
event.startDate = activity.startDate
event.endDate = activity.endDate
var saveEventError : NSError?
if eventStore.saveEvent(event, span: EKSpanThisEvent, commit: false, error: &saveEventError) {
println("Activity saved. Commit needed.")
}
else {
println("Save error: \(saveEventError?.localizedDescription)")
}
//
}
}
//Save all pending events
var saveAllEventsError : NSError?
if eventStore.commit(&saveAllEventsError) == true{
println("Save all events complete!")
//Return callbacks on main thread
dispatch_async(dispatch_get_main_queue(), {
println("Calendar Save completion.")
calendarStatus(status: "Calendar save complete!", progress: 1)
completion(success: true, error: nil)
})
return
}
else {
//Return callbacks on main thread
dispatch_async(dispatch_get_main_queue(), {
completion(success: false, error: NSError(domain: "Calendar Save Error", code: 0, userInfo: [NSLocalizedFailureReasonErrorKey : "Error batch saving events: \(saveAllEventsError?.localizedDescription)"]))
})
println("Save all events ERROR: \(saveAllEventsError?.localizedDescription)")
return
}
}
else {
//Return callbacks on main thread
dispatch_async(dispatch_get_main_queue(), {
completion(success: false, error: NSError(domain: "Calendar Save Error", code: 0, userInfo: [NSLocalizedFailureReasonErrorKey : "Found no events to save!"]))
})
return
}
})
}
}
I would like to create a custom calendar in which to create all the app related events. I would like to this calendar to be of EKSourceType Local.
I can create such a calendar on the simulator and add events to it but when I try to add it on my iPhone it does't work.
My code to create the calendar:
if (eventManager.eventStore?.calendarWithIdentifier(NSUserDefaults.standardUserDefaults().objectForKey("calendar_identifier") as? String) == nil){
//The calendar doesn't exist, create a new one with the correct properties.
calendar = EKCalendar(forEntityType: EKEntityTypeEvent, eventStore: eventManager.eventStore)
calendar.title = "My Calendar"
calendar.CGColor = UIColor(red: 0, green: 0.62, blue: 0.10, alpha: 1).CGColor
// Find the proper source type value.
var localSource:EKSource? = nil
if let eventStore = eventManager.eventStore{
//get the correct source for the calendar
for source in eventStore.sources() as [EKSource] {
println("eventStore.source - \(source)")
switch (source.sourceType.value){
/*
case (EKSourceTypeSubscribed.value):
localSource = source
*/
case (EKSourceTypeLocal.value):
localSource = source
default:
break
}
}
calendar.source = localSource
}
//save the newly created calendar
eventManager.eventStore!.saveCalendar(calendar, commit: true, error: &error)
//check for any errors.
if(error == nil){
//no errors were encountered save identifier
NSUserDefaults.standardUserDefaults().setObject(calendar.calendarIdentifier, forKey: "calendar_identifier")
println("Making calendar")
}else{
println("error in making calendar - \(error?.localizedDescription)")
return
}
}
I first check if a calendar identifier exists in the NSUserDefaults. I then create the new calendar and assign the local source to it once it is found. Then I save the calendar (which works and returns true.) No errors are returned and when I print the source it gives:
EKSource <0x1700dad30> {UUID = BD42B102-A185-4A93-9114-737001A4C408; type = Local; title = Default; externalID = (null)}
What am I doing wrong here, or what is a better way?
It seems to be a bug/strange behavior with iCloud.
That happens if you have iCloud activated, but you have disabled to sync the Reminders. This Guy had the same problem(but couldn't resolve it).
Try to enable Reminder-sync.
I want to get the authorization status for CMMotionActivityManager. For other services like calendar and location we have some property in the API that gives us the user authorization status for these classes. How i can get the authorization status for CMMotionActivityManager class?
CMMotionActivityManager does not currently offer a way to check authorisation status directly like other frameworks.
iOS - is Motion Activity Enabled in Settings > Privacy > Motion Activity
However, as the comments in the above question mention, if you attempt a query using
queryActivityStartingFromDate:toDate:toQueue:withHandler
and the user has not authorised your application, the handler (CMMotionActivityQueryHandler) will return this error.
CMErrorMotionActivityNotAuthorized
With introduction of IOS 11.* there is the possibility to call CMMotionActivityManager.authorizationStatus() which gives you a detailed status.
Here's how I'm doing it :
manager = CMMotionActivityManager()
let today = NSDate()
manager.queryActivityStartingFromDate(today, toDate: today, toQueue: NSOperationQueue.mainQueue(),
withHandler: { (activities: [CMMotionActivity]?, error: NSError?) -> Void in
if let error = error where error.code == Int(CMErrorMotionActivityNotAuthorized.rawValue){
print("NotAuthorized")
}else {
print("Authorized")
}
})
I had to adjust Zakaria's answer a bit for Swift 3.0 and also the new Error made problems, so I had to convert it back to NSError to get the code but this is how my function looks now. Thanks!
func triggerActivityPermissionRequest() {
let manager = CMMotionActivityManager()
let today = Date()
manager.queryActivityStarting(from: today, to: today, to: OperationQueue.main, withHandler: { (activities: [CMMotionActivity]?, error: Error?) -> () in
if error != nil {
let errorCode = (error! as NSError).code
if errorCode == Int(CMErrorMotionActivityNotAuthorized.rawValue) {
print("NotAuthorized")
}
} else {
print("Authorized")
}
manager.stopActivityUpdates()
})
}