Batch saving EKEvents to Google calendar causing loss of random events - ios

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
}
})
}
}

Related

Trying to Update Calendar, Getting Various Errors

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?

How do I deal with multiple widget instances accessing the same CoreData store?

Background Info
I have a main application, which writes a single entry to a database contained in an App Group (we'll call them "DB1", and "Group1", respectively). To the same project, I have added an iOS 14 Home Screen Widget extension. This extension is then added to Group1.
All three sizes (small, medium, large) show the same information from the DB1 entry, just rearranged, and for the smaller widgets, some parts omitted.
Problem
The problem is, if I have multiple instances of the widget (say a small and a medium), then when I rebuild the target, and it loads/runs, I get CoreData errors such as the following:
<NSPersistentStoreCoordinator: 0x---MEM--->: Attempting recovery from error encountered during addPersistentStore: Error Domain=NSCocoaErrorDomain Code=134081 "(null)" UserInfo={NSUnderlyingException=Can't add the same store twice}
Relevant Code
Here's the code for the Timeline function
public func timeline(with context: Context, completion: #escaping (Timeline<Entry>) -> ()) {
//Set up widget to refresh every minute
let currentDate = Date()
let refreshDate = Calendar.current.date(byAdding: .minute, value: 1, to: currentDate)!
WidgetDataSource.shared.loadTimelineEntry { entry in
guard let e = entry else { return }
let entries: [TimelineEntry] = [e]
let timeline = Timeline(entries: entries, policy: .after(refreshDate))
completion(timeline)
}
}
And here is the loadTimelineEntry function
(persistentContainer gets initialized in the init() of WidgetDataSource, the class that holds loadTimelineEntry function)
func loadTimelineEntry(callback: #escaping (TimelineEntry?) -> Void) {
persistentContainer.loadPersistentStores(completionHandler: { (storeDescription, error) in
print("Loading persistent stores")
var widgetData: [WidgetData]
if let error = error {
print("Unresolved error \(error)")
callback(nil)
} else {
let request = WidgetData.createFetchRequest()
do {
widgetData = try self.persistentContainer.viewContext.fetch(request)
guard let data = widgetData.first else { callback(nil); return }
print("Got \(widgetData.count) WidgetData records")
let entry = TimelineEntry(date: Date())
callback(entry)
} catch {
print("Fetch failed")
callback(nil)
}
}
})
}
What I've Tried
Honestly, not much. I had gotten this same error before I put the widget into the Group1. That time it was due to the main app already having DB1 created on it's first run, then on the subsequent widget run, it looked in it's own container, didn't find it, and attempted to create it's own DB1, and the OS didn't let it due to them having the same name.
Widgets being a fairly new feature, there are not many questions regarding this specific use case, but I'm sure someone out there has a similar setup. Any help or tips will be highly appreciated!
Thanks to Tom Harrington's comment, I was able to solve the warnings by adding a check at the top of my loadTimelineEntry function like so:
if !persistentContainer.persistentStoreCoordinator.persistentStores.isEmpty {
//Proceed with fetch requests, etc. w/o loading stores
} else {
//Original logic to load store, and fetch data
}

Error while running multiple SFSpeechRecognitionTask in background

As per requirement of the App I am developing, I have to pass multiple audio files to SFSpeechRecognizer and get the transcription in return.
I did it in two ways
First Method - Using Recursion (Running correctly, you can skip it if you want)
I have first completed this task by getting transcription one by one. i.e when the SFSpeechRecognitionTask gets completed, the result gets saved and the process runs again through a recursive call.
class Transcription
{
let url = [URL(fileURLWithPath: "sad")]
var fileCount = 3
let totalFiles = 4;
func getTranscriptionRecursive()
{
getTranscriptionOfAudioFile(atURL: url[fileCount], fileCount: fileCount, totalFiles: totalFiles) { (result) in
if(self.fileCount <= self.totalFiles)
{
self.fileCount = self.fileCount+1
self.getTranscriptionRecursive()
}
}
}
func getTranscriptionOfAudioFile(atURL url: URL, fileCount: Int, totalFiles: Int, completion: #escaping ((SFSpeechRecognitionResult?)->Void))
{
let request = SFSpeechURLRecognitionRequest(url: url)
request.shouldReportPartialResults = false
let recognizer = SFSpeechRecognizer(locale: Locale(identifier: "en-US"))
if (recognizer?.isAvailable)! {
recognizer?.recognitionTask(with: request) { result, error in
//error handling
completion(result)
}
}
}
}
This method worked great but it takes way too much time as each SFSpeechRecognizer request takes time to complete.
Second Method - Using Loop and Background Thread
(This one is having the issue)
I tried to create multiple requests and execute them in background at once.
For that I created a For Loop till the count of audio files, and in that loop I called the function to create SFSpeechRecognizer Request and task.
for index in 0..<urls.count
{
DispatchQueue.global(qos: .background).async {
self.getTranscriptionOfAudio(atURL: self.urls[index]) { (result, myError, message) in
//error handling
//process Results
}
}
}
where as the function to get speech recognition results is
func getTranscriptionOfAudio(atURL audioURL: URL?, completion: #escaping ((SFSpeechRecognitionResult? , Error?, String?)->Void))
{
let request = SFSpeechURLRecognitionRequest(url: audioURL!)
request.shouldReportPartialResults = false
let recognizer = SFSpeechRecognizer(locale: Locale(identifier: "en-US"))
if (recognizer?.isAvailable)! {
recognizer?.recognitionTask(with: request) { result, error in
//error handling
completion(results,nil,nil)
}
} else {
completion(nil,nil,"Reognizer could not be initialized");
}
}
When I run this code, only one task executes and other tasks give this error
+[AFAggregator logDictationFailedWithError:] Error
Domain=kAFAssistantErrorDomain Code=209 "(null)"
I searched this error on internet but there is no documentation that contains its details.
It might be due to running SFSpeechRecognitionTask in parallel, but in official Apple documents here they didn't forbid to do so, as we can create SFSpeechRecognitionRequest object separately. We don't use singleton object of SFSpeechRecognizer
let me know if anyone has any idea what is going on and what you suggest me to do.

Error getting new calendar for new reminders

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.

CMMotionactivitymanager authorizationstatus

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()
})
}

Resources