Does someone know how to use EKCalendarMBS?

Hi there,

I’m trying to use EKCalendarMBS, using the MBS ‘EventKit’ example in Mac64bits/EventKit.

Both entity types access always return granted=false, while error is Nil.

Is there something I need to do to test this ?

Using:

  • Xojo 2021r3.1
  • macOS 12.4 Monterey
  • MBS Complete 215

Thanks !

This is what I do for both macOS and iOS, as a common external Method and Class:

  1. I build an SQLite database
  2. I populate the SQLite database with all Calendar Events within a given date range.
  3. I then query this database to get the results I need as a RowSet.

CalendarEventsDB is a SQLiteDatabase
myEventStoreMBS is a ClassEventStoreMBS

Build the SQLite file:

Public Sub doReset()
  #If (TargetDesktop And TargetMacOS) Or TargetiOS Then
    Var tempSQL As String
    Var totalSQL() As String
    
    If myEventStoreMBS = Nil Or Not myEventStoreMBS.isAccessGrantedCalendar Or CalendarEventsDB <> Nil Then 'if the database has already been set up, then exit!
      Return 'access to Calendar not granted?
    End If
    
    Try
      Var f As FolderItem = CommonFolders.SpecialFolderDatabases.Child("CalendarEventsDB.SQLite")
      
      If f <> Nil And f.Exists Then
        CommonFolders.doDeleteFolderWAD(f)
      End If
      
      'set up the new in memory database
      CalendarEventsDB = New SQLiteDatabase
      CalendarEventsDB.DatabaseFile = f 'CommonFolders.getDownloadFolderItemWAD("CalendarEventsDB.SQLite")
      CalendarEventsDB.WriteAheadLogging = True
      CalendarEventsDB.CreateDatabase
      
      If Not CalendarEventsDB.Connect Then
        CalendarEventsDB = Nil
        Return
      End If
      
      totalSQL.Add("CREATE TABLE IF NOT EXISTS CalendarEvents (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,")
      'Calendar
      totalSQL.Add("CalendarIdentifier TEXT,")
      totalSQL.Add("CalendarColor INTEGER,")
      totalSQL.Add("isCalendarImmutable INTEGER,") 'Immutable
      'totalSQL.Add("isCalendarSubscribed INTEGER,") 'Subscribed
      totalSQL.Add("CalendarTitle TEXT,")
      totalSQL.Add("CalendarType TEXT,")
      
      'Calendar Event
      totalSQL.Add("isAllDay INTEGER,")
      'totalSQL.Add("CalendarItemIdentifier TEXT,")
      'totalSQL.Add("CreationDateTime TIMESTAMP,")
      totalSQL.Add("EndDateTime TIMESTAMP,")
      totalSQL.Add("EventIdentifier TEXT,")
      'totalSQL.Add("hasNotes INTEGER,")
      totalSQL.Add("Location TEXT,")
      totalSQL.Add("Notes TEXT,")
      'totalSQL.Add("OccurrenceDateTime TIMESTAMP,")
      totalSQL.Add("StartDateTime TIMESTAMP,")
      totalSQL.Add("Title TEXT,")
      totalSQL.Add("URL TEXT")
      
      totalSQL.Add(")")
      
      tempSQL = String.FromArray(totalSQL, " ")
      
      CalendarEventsDB.ExecuteSQL(tempSQL)
      'CalendarEventsDB.ExecuteSQL("VACUUM") 'clear out any previous space. Not needed since it deletes the file!
      
    Catch Error
      CommonWindow.doDisplayErrorWAD(Error.Message, CurrentMethodName, Error.ErrorNumber)
    End Try
  #EndIf
End Sub

Populate the SQLite database:

Protected Sub doPopulateCalendarEvents(StartDate As DateTime, EndDate As DateTime, DaysPlusMinus As Integer = 90)
  #If (TargetDesktop And TargetMacOS) Or TargetiOS Then
    If CalendarEventsDB = Nil Or myEventStoreMBS = Nil Or Not myEventStoreMBS.isAccessGrantedCalendar Then
      Return 'DB not yet initialised
    End If
    
    If StartDate = Nil Then
      StartDate = DateTime.Now
      StartDate = StartDate.SubtractInterval(0, 0, DaysPlusMinus)
      
    End If
    If EndDate = Nil Then
      EndDate = DateTime.Now
      EndDate = EndDate.AddInterval(0, 0, DaysPlusMinus)
    End If
    
    Dim tempNSPredicateMBS As NSPredicateMBS = myEventStoreMBS.predicateForEvents(startDate, endDate)
    Dim tempEKEventMBS() As EKEventMBS = myEventStoreMBS.eventsMatchingPredicate(tempNSPredicateMBS)
    
    Try
      CalendarEventsDB.ExecuteSQL("DELETE FROM CalendarEvents") 'wipe any existing records!
      
      For Each e As EKEventMBS In tempEKEventMBS
        If e.calendar.calendarIdentifier = "" Or e.eventIdentifier = "" Or e.title = "" Then
          'Break 'shouldn't happen!
          Var aBreak As Boolean
          
        Else
          Var rec As New DatabaseRow
          
          rec.Column("CalendarIdentifier").StringValue = e.calendar.calendarIdentifier
          rec.Column("CalendarColor").IntegerValue = CommonPictures.getColorToNumberWAD(e.calendar.Color.colorValue) 'e.calendar.Color.colorValue.ToString
          rec.Column("isCalendarImmutable").IntegerValue = If(e.calendar.Immutable, 1, 0)
          'rec.Column("isCalendarSubscribed").IntegerValue = If(e.calendar.Subscribed, 1, 0)
          rec.Column("CalendarTitle").StringValue = e.calendar.title
          rec.Column("CalendarType").StringValue = getCalendarType(e.calendar.type)
          
          rec.Column("isAllDay").IntegerValue = If(e.AllDay, 1, 0)
          'rec.Column("CalendarItemIdentifier").StringValue = e.calendarItemIdentifier
          'rec.Column("CreationDateTime").StringValue = e.creationDateTime.SQLDateTime
          rec.Column("EndDateTime").StringValue = e.endDateTime.SQLDateTime
          rec.Column("EventIdentifier").StringValue = e.eventIdentifier
          'rec.Column("hasNotes").IntegerValue = If(e.hasNotes, 1, 0)
          rec.Column("Location").StringValue = e.location
          rec.Column("Notes").StringValue = e.notes
          'rec.Column("OccurrenceDateTime").StringValue = e.occurrenceDateTime.SQLDateTime
          rec.Column("StartDateTime").StringValue = e.startDateTime.SQLDateTime
          rec.Column("Title").StringValue = e.title
          rec.Column("URL").StringValue = e.URL
          
          CalendarEventsDB.AddRow("CalendarEvents", rec)
        End If
      Next
      
    Catch Error
      CommonWindow.doDisplayErrorWAD(Error.Message, CurrentMethodName, Error.ErrorNumber)
    End Try
  #EndIf
  End Sub

Get the Calendar Events as a RowSet:

Protected Sub getCalendarEvents(ByRef rs As RowSet)
  #If (TargetDesktop And TargetMacOS) Or TargetiOS Then
    myEventStoreMBS = New ClassEventStoreMBS
    myEventStoreMBS.requestAccessToEntityType(EKEventStoreMBS.kEntityTypeEvent)
    
    If myEventStoreMBS.defaultCalendarForNewEvents = Nil Then 'cancel pressed?
      CommonWindow.doMessageBox("You must give " + app.ApplicationCore + " access to Calendar Events to access the calendars")
      Return
    End If
    
    Var MemoryDB As New SQLiteDatabase
    Var tempSQL As String
    
    If Not MemoryDB.Connect Then
      Return
    End If
    
    tempSQL = "CREATE TABLE IF NOT EXISTS Data (id INTEGER PRIMARY KEY AUTOINCREMENT, "
    tempSQL = tempSQL + "Title TEXT, "
    tempSQL = tempSQL + "CalendarUID TEXT, "
    tempSQL = tempSQL + "Type TEXT, "
    tempSQL = tempSQL + "Description TEXT, "
    tempSQL = tempSQL + "Colour TEXT, "
    tempSQL = tempSQL + "isEditable INTEGER "
    tempSQL = tempSQL + ") "
    
    Try
      MemoryDB.ExecuteSQL(tempSQL)
    Catch Error
      CommonWindow.doDisplayErrorWAD(Error.Message, CurrentMethodName, Error.ErrorNumber)
      Return
    End Try
    
    Var tempEKCalendarMBS() As EKCalendarMBS = myEventStoreMBS.calendarsForEntityType(EKEventStoreMBS.kEntityTypeEvent)
    
    For Each myCalendarMBS As EKCalendarMBS In tempEKCalendarMBS
      Var rec As New DatabaseRow
      
      rec.Column("Title").StringValue = myCalendarMBS.Title
      rec.Column("CalendarUID").StringValue = myCalendarMBS.calendarIdentifier
      'rec.Column("Type").StringValue = tempType
      rec.Column("Type").StringValue = getCalendarType(myCalendarMBS.type)
      rec.Column("Description").StringValue = ""
      rec.Column("Colour").StringValue = myCalendarMBS.Color.colorValue.ToString
      rec.Column("isEditable").IntegerValue = If(myCalendarMBS.allowsContentModifications, 1, 0)
      
      Try
        MemoryDB.AddRow("Data", rec)
      Catch Error
        CommonWindow.doDisplayErrorWAD(Error.Message, CurrentMethodName, Error.ErrorNumber)
        Return
      End Try
    Next
    
'Only show new Calendars not already chosen by the user
    Var CalendarUIDArray() As String = CommonHMS.getCalendarUIDArray
    
    Try
      tempSQL = "SELECT * FROM Data "
      If CalendarUIDArray.LastIndex >= 0 Then 'exclude any CalendarUIDs that are already being used by this User
        tempSQL = tempSQL + "WHERE NOT CalendarUID IN(""" + String.FromArray(CalendarUIDArray, """,""") + """)"
      End If
      rs = MemoryDB.SelectSQL(tempSQL)
    Catch Error
      CommonWindow.doDisplayErrorWAD(Error.Message, CurrentMethodName, Error.ErrorNumber)
      Return
    End Try
    
    If MemoryDB <> Nil Then MemoryDB.Close
  #EndIf
End Sub

Get the Calendar Type as text:

Private Function getCalendarType(TypeID As Integer) As String
  #If (TargetDesktop And TargetMacOS) Or TargetiOS Then
    Select Case TypeID
    Case EKCalendarMBS.kTypeBirthday
      Return "Birthday"
      
    Case EKCalendarMBS.kTypeCalDAV
      Return "CalDAV"
      
    Case EKCalendarMBS.kTypeExchange
      Return "Exchange"
      
    Case EKCalendarMBS.kTypeLocal
      Return "Local"
      
    Case EKCalendarMBS.kTypeSubscription
      Return "Subscription"
      
    Else
      Return "Unknown"
    End Select
  #EndIf
End Function

Your app is code signed?
Has right info.plist entries for calendar usage?

Your app is code signed?

I’m just running the MBS example. I don’t think I need to code sign to run the app in debug.

Has right info.plist entries for calendar usage?

I’m just trying to run the MBS example, the plist is provided in the example folder, do I need to edit the plist ?

How do you give access to Calendar Events ? Does the app make the access request when calling
myEventStoreMBS.requestAccessToEntityType ?
I don’t get a chance to answer, no request is shown.

You have to code sign the application.
Without proper signature, access is not granted.
The info.plist entries are needed and included in the project as external info.plist file.

Hi Christian,

I’m just using the project /Examples/Mac64bit/EventKit/EventKit.xojo_binary_project.
I only added 2 labels to show the result of GotPermissions method.
Nothing else…
The example project has a plist (the one in the same example folder), I didn’t change it.

I’ve code signed and even notarized the app using AppWrapper4.
I still don’t get access, I receive granted=False for both Events and Reminder.

I suppose I should get a popup asking for permissions but I don’t.

Any other idea ?

Thanks for your details, interesting !
I’ll try something similar if I can get access to the calendar.

I use a plist file to tell Apple that I need Calendar permissions:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
	<dict>
		<key>NSAppTransportSecurity</key>
		<dict>
			<key>NSExceptionDomains</key>
			<dict>
				<key>BamBamSoftware.com</key>
				<dict>
					<key>NSIncludesSubdomains</key>
					<true/>
					<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
					<true/>
				</dict>
			</dict>
		</dict>
	</dict>
<dict>
	<key>NSCalendarsUsageDescription</key>
	<string>Please allow Calendar access</string>
</dict>
</plist>

Well, something is strange as I tried it here. Showed a dialog on one computer, but not on another one.

And the documentation states that it only asks once per dialog and then remembers the result.

My plist has the same Calendar and Reminder settings:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
   <key>NSCalendarsUsageDescription</key>
   <string>Calendar access please</string>
   <key>NSRemindersUsageDescription</key>
   <string>Reminder access please</string>
</dict>
</plist>

Are both computers running the same macOS version ?

I only access the Calendars and not the Reminders, so I only request permission for the Calendar, so that only one permission warning dialog appears and not two.

I am just amazed that the same code works for both macOS and iOS unchanged (thanks to MBS):

Yes, but I wonder if macOS Ventura changes something on the requirements.
Or Apple has a bug there currently.

I ran tests yesterday of Christian’s EventKit classes on macOS Ventura public beta 8. The “EventKit” demo project (in Examples/Mac64Bit/EventKit/) works well, but you have to follow the instructions and add a step that adds the provided entitlements and code signs the build. If you don’t do that, you get the symptoms described by Olivier.

Generally, my existing EventKit code worked well but macOS 13 seems to no longer allow creating an all-day calendar event using the same date & time for the start and end. Easy to work around, but a surprising change.

I’m not using Ventura yet, my tests are running on Monterey only (12.4).
I’ll do more tests and keep you posted.

Strange but …

I saved the MBS example to a test folder (save as).
Copied the plist from MBS example to the test folder.

Running the new test app in debug:

  • I’m getting the access request prompt
  • I can access all calendar items

I don’t know what or where was the problem but it seems solved.

Maybe good to mention that I can run and test the app in debug without code signing it.

Many thanks to @David_Cox, @Christian_Schmitz and @Tom_Catchesides for helping.

I’ll mark this topic as ‘solved’ even if I don’t know how it was fixed :wink: