Problems fetching events from EKEventStore - ios

I am trying to read all calendar events from the EventStore. The routine I use, works sometimes but not always.
func getCalendarEvents(_ anfangOpt: Date?, _ endeOpt: Date?) -> [EKEvent]? {
guard let anfang = anfangOpt, let ende = endeOpt else { return nil }
var events: [EKEvent]? = nil
let eventStore = EKEventStore()
eventStore.requestAccess( to: EKEntityType.event, completion: { _,_ in })
if EKEventStore.authorizationStatus(for: EKEntityType.event) == EKAuthorizationStatus.authorized {
var predicate: NSPredicate? = nil
predicate = eventStore.predicateForEvents(withStart: anfang, end: ende, calendars: nil)
if let aPredicate = predicate {
events = eventStore.events(matching: aPredicate)
}
}
return events
}
This function always returns the events. But they are sometimes incomplete. So that
for event in bereinigteEvents {
if dateInInterval(prüfdatum, start: event.startDate, ende: event.endDate) {
istimurlaub = true
if let zwischenname = event.title {
eventname = zwischenname
} else {
eventname = "n/a"
}
eventcalendar = event.calendar.title
trigger.append ("Auslöser: „" + eventname + "“ im Kalender „" + eventcalendar + "“")
}
}
sometimes crashes at the line "eventcalendar = event.calendar.title" and the error message that "nil" was unexpectedly found.
Thank you!
After the first answer I have changed the function, which gets the events to:
func getCalendarEvents(_ anfangOpt: Date?, _ endeOpt: Date?) -> [EKEvent]? {
guard let anfang = anfangOpt, let ende = endeOpt else { return nil }
var events: [EKEvent]? = nil
let eventStore = EKEventStore()
func fetchEvents() {
var predicate: NSPredicate? = nil
predicate = eventStore.predicateForEvents(withStart: anfang, end: ende, calendars: nil)
if let aPredicate = predicate {
events = eventStore.events(matching: aPredicate)
}
}
if EKEventStore.authorizationStatus(for: EKEntityType.event) == EKAuthorizationStatus.authorized {
fetchEvents()
} else {
eventStore.requestAccess( to: EKEntityType.event, completion: {(granted, error) in
if (granted) && (error == nil) {
fetchEvents()
}
})
}
return events
}
But it still crashes with "unexpectedly found nil" in "event.calendar.title".
I ended up using this
Swift 4 How to get all events from calendar?
routine to fetch the events.
The problem still occurs sometimes (!!): Occasionally "nil" is found in "event.calender.title", although it shouldn't be "nil"

The line
eventStore.requestAccess( to: EKEntityType.event, completion: { _,_ in })
is pointless because it works asynchronously. The result of the request is returned after the authorizationStatus check in the next line.
I recommend to first check the status. If the access is not granted ask for permission and perform the fetch. If it's granted perform the fetch directly. This can be accomplished by moving the code to fetch the events into a method.
Note:
It seems that you want to fetch the events when calling the method. Why do you declare start and end date as optional and check for nil?
Declare
func getCalendarEvents(_ anfang: Date, _ ende: Date) -> [EKEvent]? { ...
then you get notified at compile time whether a parameter is nil.
PS: Deutsche Parameternamen mit Umlauten sehen sehr lustig aus. (German parameter names with umlauts look pretty funny)

Problem was, that event.calendar is actually an optional (which I was not aware of).
if let eventZwischenCal = event.calendar {
eventcalendar = eventZwischenCal.title
} else {
eventcalendar = "n/a"
}
fixes the problem.

Related

field value in firestore document increments by wrong number due to print statement repeating 4 times

So my goal is to always have a proper increment value when a ticket is purchased. I'm having a strange bug where when I update a field value by incrementing it, it increments by the wrong number the first time every time I run a fresh new simulation due to the repeats in print statements, but if I was to update the value again in the same fresh simulation, it will increment by the proper value every time after that.
The value I use to increment is a label text which is dependent on a UIStepper value.
Here is the complete function I use to update and increment this value:
func paymentAuthorizationViewController(_ controller: PKPaymentAuthorizationViewController, didAuthorizePayment payment: PKPayment, handler completion: #escaping (PKPaymentAuthorizationResult) -> Void) {
guard let count = guestNumberCount.text else { return }
guard let labelAsANumber = Int64(count) else { return }
guard let user = Auth.auth().currentUser else { return }
let dfmatter = DateFormatter()
dfmatter.dateFormat = "EEEE, MMMM d yyyy, h:mm a"
let dateFromString = dfmatter.date(from: actualDateOfEvent.text ?? "")
let dateStamp: TimeInterval = dateFromString!.timeIntervalSince1970
let dateSt: Int = Int(dateStamp)
getStudentID { (studid) in
if let id = studid {
self.db.document("student_users/\(user.uid)/events_bought/\(self.navigationItem.title!)").setData(["event_name": self.navigationItem.title!, "event_date": self.actualDateOfEvent.text ?? "", "event_cost": self.actualCostOfEvent.text ?? "", "for_grades": self.gradeOfEvent , "school_id": id, "expiresAt":dateSt, "isEventPurchased": true, "time_purchased": Date()], merge: true) { (error) in
if let error = error {
print("There was an error trying to add purchase info to the database: \(error)")
} else {
print("The purchase info was successfully stored to the database!")
}
}
}
}
getDocumentIDOfSelectedEvent { (eventidentification) in
if let eventid = eventidentification {
self.getSchoolDocumentID { (docID) in
if let doc = docID {
self.db.document("school_users/\(doc)/events/\(eventid)/extraEventInfo/TicketCount").updateData(["ticketsPurchased": FieldValue.increment(Int64(labelAsANumber))]) { (error) in
if let error = error {
print("There was an error updating the number of tickets: \(error)")
} else {
print("Number of tickets purchased succesfully updated!")
print(labelAsANumber)
}
}
}
}
}
}
performSegue(withIdentifier: Constants.Segues.fromTicketFormToPurchaseDetails, sender: self)
completion(PKPaymentAuthorizationResult(status: .success, errors: []))
}
I can't really figure out any issues within the function, neither within my whole project as well in regards to this situation other than the print statement. In the print(labelAsANumber) statement, it prints the correct value of the label text (stepper value), but it prints it four times, which is causing the issue. How can I prevent this repetition of print statements and overall incorrect numeric incrementing?

Is it possible to read Apple Watch goals (move, step and stand) from HealthKit?

Is it possible to read Apple Watch move goal from HealthKit?
I can retrieve the Move value by using the Quantity Identifier HKQuantityTypeIdentifier.activeEnergyBurned. I could not find a similar identifier for the move goal.
//Declared globally
var healthStore: HKHealthStore?
func prepareHealthKit() {
guard HKHealthStore.isHealthDataAvailable() else {
return
}
var readTypes = Set<HKObjectType>()
readTypes.insert(HKObjectType.activitySummaryType())
healthStore = HKHealthStore()
healthStore!.requestAuthorization(toShare: nil, read: readTypes) { (isSuccess, error) in
/*
Assuming you know the following steps:
1. Start workout session: i.e. "HKWorkoutSession"
2. Wait for delegate: i.e "workoutSession(_:didChangeTo:from:date:)"
3. Start Query for Activity Summary in the delegate:
i.e our "startQueryForActivitySummary()"
*/
}
}
func startQueryForActivitySummary() {
func createPredicate() -> NSPredicate? {
let calendar = Calendar.autoupdatingCurrent
var dateComponents = calendar.dateComponents([.year, .month, .day],
from: Date())
dateComponents.calendar = calendar
let predicate = HKQuery.predicateForActivitySummary(with: dateComponents)
return predicate
}
let queryPredicate = createPredicate()
let query = HKActivitySummaryQuery(predicate: queryPredicate) { (query, summaries, error) -> Void in
if let summaries = summaries {
for summary in summaries {
let activeEnergyBurned = summary.activeEnergyBurned.doubleValue(for: HKUnit.kilocalorie())
let activeEnergyBurnedGoal = summary.activeEnergyBurnedGoal.doubleValue(for: HKUnit.kilocalorie())
let activeEnergyBurnGoalPercent = round(activeEnergyBurned/activeEnergyBurnedGoal)
print(activeEnergyBurnGoalPercent)
}
}
}
healthStore?.execute(query)
}
References:
https://crunchybagel.com/accessing-activity-rings-data-from-healthkit
https://developer.apple.com/documentation/healthkit/hkactivitysummary
https://developer.apple.com/documentation/healthkit/hkactivitysummaryquery
I got the answer. The move goal is accessible from HKActivitySummary.
You should request permission to read HKActivitySummaryType:
let activitySummaryType = HKActivitySummaryType.activitySummaryType()
let readDataTypes: Set<HKObjectType> = [activitySummaryType]
healthStore.requestAuthorization(toShare: nil, read: readDataTypes, completion: myCompletionHandler)
Then use HKActivitySummaryQuery to read the summary information
let query = HKActivitySummaryQuery(predicate: myPredicate) { (query, summaries, error) -> Void in
if error != nil {
fatalError("*** Did not return a valid error object. ***")
}
if let activitySummaries = summaries {
for summary in activitySummaries {
print(summary.activeEnergyBurnedGoal)
//do something with the summary here...
}
}
}
healthStore.execute(query)
Other activity summary data that is accessible from HKActivitySummary is available here.

Execute a function after completing all background process in swift

I am using GCD in swift
like this :
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
//all background task
dispatch_async(dispatch_get_main_queue()) {
self.second()
}
}
In this code second function is getting called before completing all background task that's why I am not able to take some data which I am using in second function. I want to second method after completing all background task. Can anyone tell me how to achieve this task?
***************In background I am taking healthkit data like******
let healthKitTypesToRead =
Set(
arrayLiteral: HKObjectType.characteristicTypeForIdentifier(HKCharacteristicTypeIdentifierDateOfBirth)!,
HKObjectType.characteristicTypeForIdentifier(HKCharacteristicTypeIdentifierBiologicalSex)!,
HKObjectType.workoutType()
)
let newCompletion: ((Bool, NSError?) -> Void) = {
(success, error) -> Void in
if !success {
print("You didn't allow HealthKit to access these write data types.\nThe error was:\n \(error!.description).")
return
}
else
{
let stepCount = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)
// Our search predicate which will fetch data from now until a day ago
// (Note, 1.day comes from an extension
// You'll want to change that to your own NSDate
//let date = NSDate()
//let predicate = HKQuery.predicateForSamplesWithStartDate(date, endDate: NSDate(), options: .None)
// The actual HealthKit Query which will fetch all of the steps and sub them up for us.
let stepCountQuery = HKSampleQuery(sampleType: stepCount!, predicate:.None, limit: 0, sortDescriptors: nil) { query, results, error in
var steps: Double = 0
if results?.count > 0
{
for result in results as! [HKQuantitySample]
{
steps += result.quantity.doubleValueForUnit(HKUnit.countUnit())
}
testClass.HK_stepCount = String(steps)
}
//completion(steps, error)
}
self.healthKitStore.executeQuery(stepCountQuery)
//EDIT.....
let tHeartRate = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate)
let tHeartRateQuery = HKSampleQuery(sampleType: tHeartRate!, predicate:.None, limit: 0, sortDescriptors: nil) { query, results, error in
if results?.count > 0
{
var string:String = ""
for result in results as! [HKQuantitySample]
{
let HeartRate = result.quantity
string = "\(HeartRate)"
print(string)
}
testClass.HK_HeartRate = string
finalCompletion(Success: true)
}
}
self.healthKitStore.executeQuery(tHeartRateQuery)
}
}
healthKitStore.requestAuthorizationToShareTypes(healthKitTypesToWrite, readTypes: healthKitTypesToRead, completion: newCompletion)
I am not able to take value of step count, It get executed after calling second(method) called, plz suggest me what to do?
You can create a separate function to execute your task on other thread
func someFunction(finalCompletion: (Success: Bool)->()) {
let healthKitTypesToRead =
Set(
arrayLiteral: HKObjectType.characteristicTypeForIdentifier(HKCharacteristicTypeIdentifierDateOfBirth)!,
HKObjectType.characteristicTypeForIdentifier(HKCharacteristicTypeIdentifierBiologicalSex)!,
HKObjectType.workoutType()
)
let newCompletion: ((Bool, NSError?) -> Void) = {
(success, error) -> Void in
if !success {
print("You didn't allow HealthKit to access these write data types.\nThe error was:\n \(error!.description).")
return
}
else
{
let stepCount = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)
// Our search predicate which will fetch data from now until a day ago
// (Note, 1.day comes from an extension
// You'll want to change that to your own NSDate
//let date = NSDate()
//let predicate = HKQuery.predicateForSamplesWithStartDate(date, endDate: NSDate(), options: .None)
// The actual HealthKit Query which will fetch all of the steps and sub them up for us.
let stepCountQuery = HKSampleQuery(sampleType: stepCount!, predicate:.None, limit: 0, sortDescriptors: nil) { query, results, error in
var steps: Double = 0
if results?.count > 0
{
// Edit--
for result in results as! [HKQuantitySample]
{
steps += result.quantity.doubleValueForUnit(HKUnit.countUnit())
// heartBeat += ....
}
testClass.HK_stepCount = String(steps)
finalCompletion(Success: true)
}
//completion(steps, error)
}
self.healthKitStore.executeQuery(stepCountQuery)
}
}
healthKitStore.requestAuthorizationToShareTypes(healthKitTypesToWrite, readTypes: healthKitTypesToRead, completion: newCompletion)
}
Another function?
I will edit this answer in some time to tell you about a better technique to deal with async request. In general you should have a separate singleton class for such background tasks. (RESTful API service class.. but for now you can use the below method)
func getHeartBeatInfo(finalCompletionHeart: (Success: Bool)->()) {
let tHeartRate = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeartRate)
let tHeartRateQuery = HKSampleQuery(sampleType: tHeartRate!, predicate:.None, limit: 0, sortDescriptors: nil) { query, results, error in
if results?.count > 0
{
var string:String = ""
for result in results as! [HKQuantitySample]
{
let HeartRate = result.quantity
string = "\(HeartRate)"
print(string)
}
testClass.HK_HeartRate = string
finalCompletionHeart(Success: true)
}
}
self.healthKitStore.executeQuery(tHeartRateQuery)
}
And then you can call this method like so:
override func viewDidLoad() {
someFunction( { (finalCompletion) in
if finalCompletion == true {
getHeartBeatInfo( { finalCompletionHeart in
if finalCompletionHeart == true {
self.second()
}
})
}
})
}

How to get all Events out of a Calendar (Swift)

I am kinda new to Swift, but really interested.
I would like to get all the events I stored in a Calendar called "Work" and show them in a tableView.
I was looking for questions like this, but the code shown there seems to be kinda old and not really working. How do I do that? The tableView should be able to show the Title, start and end-Date. Is it possible to get like all Titles in an Array of Strings. Same with the start and end?
Would be awesome to get some tips!
Update:
I declared the variables outside the class.
Now I tried a code that looks like this, thanks to an answer I got here, but I don't get the cells to display anything?! And Yes I already created a testEvent in my Work calendar on the simulator.
override func viewDidAppear(animated: Bool) {
let eventStore = EKEventStore()
switch EKEventStore.authorizationStatusForEntityType(.Event) {
case .Authorized:
readEvents()
case .Denied:
print("Access denied")
case .NotDetermined:
eventStore.requestAccessToEntityType(.Event, completion: { (granted: Bool, NSError) -> Void in
if granted {
self.readEvents()
}else{
print("Access denied")
}
})
default:
print("Case Default")
}
self.tableView.reloadData()
}
func readEvents() {
let eventStore = EKEventStore()
let calendars = eventStore.calendarsForEntityType(.Event)
for calendar in calendars {
if calendar.source.title == "Work" {
let oneMonthAgo = NSDate(timeIntervalSinceNow: -30*24*3600)
let oneMonthAfter = NSDate(timeIntervalSinceNow: +30*24*3600)
let predicate = eventStore.predicateForEventsWithStartDate(oneMonthAgo, endDate: oneMonthAfter, calendars: [calendar])
var events = eventStore.eventsMatchingPredicate(predicate)
for event in events {
titles.append(event.title)
startDates.append(event.startDate)
endDates.append(event.endDate)
}
}
}
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return titles.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel!.text = titles[indexPath.row]
cell.detailTextLabel!.text = "From: \(startDates[indexPath.row]) Until: \(endDates[indexPath.row])"
// Configure the cell...
return cell
}
You can try something like this
import EventKit
var titles : [String] = []
var startDates : [Date] = []
var endDates : [Date] = []
var store = EKEventStore()
let calendars = store.calendars(for: .event)
for calendar in calendars {
if calendar.title == "Work" {
let oneMonthAgo = Date(timeIntervalSinceNow: -30*24*3600)
let oneMonthAfter = Date(timeIntervalSinceNow: 30*24*3600)
let predicate = store.predicateForEvents(withStart: oneMonthAgo, end: oneMonthAfter, calendars: [calendar])
let events = store.events(matching: predicate)
for event in events {
titles.append(event.title)
startDates.append(event.startDate)
endDates.append(event.endDate)
}
}
}
(Updated with Swift 5 APIs)
func fetchEventsFromCalendar() -> Void {
let status = EKEventStore.authorizationStatus(for: EKEntityType.event)
switch (status) {
case .notDetermined:
requestAccessToCalendar()
case .authorized:
self.fetchEventsFromCalendar(calendarTitle: "Calendar")
break
case .restricted, .denied: break
}
}
func requestAccessToCalendar() {
eventStore.requestAccess(to: EKEntityType.event) { (accessGranted, error) in
self.fetchEventsFromCalendar(calendarTitle: "Calendar")
}
}
// MARK: Fetech Events from Calendar
func fetchEventsFromCalendar(calendarTitle: String) -> Void {
//PGAEventsCalendar
for calendar:EKCalendar in calendars! {
if calendar.title == calendarTitle {
let selectedCalendar = calendar
let startDate = NSDate(timeIntervalSinceNow: -60*60*24*180)
let endDate = NSDate(timeIntervalSinceNow: 60*60*24*180)
let predicate = eventStore.predicateForEvents(withStart: startDate as Date, end: endDate as Date, calendars: [selectedCalendar])
addedEvents = eventStore.events(matching: predicate) as [EKEvent]
print("addedEvents : \(addedEvents)")
}
}
}
Make sure to give the proper title of the calendar, because different calendars have different list of events, In my case calendar with title "Calendar" was having the required events list, before giving check for this:
if calendar.title == calendarTitle {
My first list was getting overrides by list from other calendar, so I used to get only few events which were not relevant to me.
Swift 3.0
let eventStore = EKEventStore()
let calendars = eventStore.calendars(for: .event)
for calendar in calendars {
if calendar.title == "Work" {
let oneMonthAgo = NSDate(timeIntervalSinceNow: -30*24*3600)
let oneMonthAfter = NSDate(timeIntervalSinceNow: +30*24*3600)
let predicate = eventStore.predicateForEvents(withStart: oneMonthAgo as Date, end: oneMonthAfter as Date, calendars: [calendar])
let events = eventStore.events(matching: predicate)
for event in events {
titles.append(event.title)
startDates.append(event.startDate as NSDate)
endDates.append(event.endDate as NSDate)
}
}
}

Attempting to save an event in EKEventStore crashes the app

I have a very odd bug in my app. Attempting to save an event using saveEvent causes the app to continue in one of 3 ways:
Everything gets saved correctly and without issues
The app crashes with a unrecognized selector sent to instance error, where the offending selector is constraints: and the object to which it's sent is always different and rather unpredictable (they are almost always private SDK classes)
The app crashes with EXC_BAD_ACCESS error
In trying to debug this, I've stripped the app to just the view controller listing the events, with a button to add a new one. The first time I present the view controller to add an event, everything goes smoothly, but the second time I do this, it throws an error.
Here is the code I use:
self.event = EKEvent(eventStore: self.eventStore!)
self.event!.calendar = self.calendar!
self.event!.startDate = self.defaultStartDate()
self.event!.endDate = self.event!.startDate.dateByAddingTimeInterval(3600)
var error: NSError?
self.eventStore!.saveEvent(self.event!, span:EKSpanThisEvent, error: &error)
if let e = error {
println("Saving error: \(error)")
}
If the values for calendar, startDate or endDate are invalid, I get a descriptive error with no crash, but here it crashes at the self.eventStore!.saveEvent(). Any help is appreciated!
Edit
Turns out it was due to an extraneous call to self.eventStore.reset().
After a long search I find the solution.
You have to save your events in background embedding code on a dispatch_async block.
enum UWCalendarError: Int {
case AlreadyExists
case Generic
case NotGranted
}
class Calendar {
static func saveEvent(title: String, startDate: NSDate, duration: NSTimeInterval, completion: (success: Bool, error: UWCalendarError?) -> Void) {
if Calendar.isEventAlreadyScheduled(title, startDate: startDate, duration: duration) {
completion(success: false, error: .AlreadyExists)
} else {
dispatch_async(dispatch_get_main_queue(),{
let store = EKEventStore()
store.requestAccessToEntityType(EKEntityTypeEvent) {(granted, error) in
if !granted {
completion(success: false, error: .NotGranted)
}
var event = EKEvent(eventStore: store)
event.title = title
event.startDate = startDate
event.endDate = event.startDate.dateByAddingTimeInterval(duration)
event.calendar = store.defaultCalendarForNewEvents
var err: NSError?
store.saveEvent(event, span: EKSpanThisEvent, commit: true, error: &err)
if err == nil {
completion(success: true, error: nil)
} else {
completion(success: false, error: .Generic)
}
}
})
}
}
static func isEventAlreadyScheduled(title: String, startDate: NSDate, duration: NSTimeInterval) -> Bool {
let endDate = startDate.dateByAddingTimeInterval(duration)
let eventStore = EKEventStore()
let predicate = eventStore.predicateForEventsWithStartDate(startDate, endDate: endDate, calendars: nil)
let events = eventStore.eventsMatchingPredicate(predicate)
if events == nil {
return false
}
for eventToCheck in events {
if eventToCheck.title == title {
return true
}
}
return false
}
}

Resources