1

I am trying to understand how to subscribe to EventKit calendar event updates within SwiftIU.

The EventKit documentation shows how to do this in Swift (https://developer.apple.com/documentation/eventkit/updating_with_notifications).

It says to subscribe to calendar update notifications by writing:

NotificationCenter.default.addObserver(self, selector: Selector("storeChanged:"), name: .EKEventStoreChanged, object: eventStore)

It also appears that the storeChanged function needs do be tagged with @objc based on xcode warnings. However, when I do the below within a SwiftUI app, I get the error @objc can only be used with members of classes, @objc protocols, and concrete extensions of classes

import SwiftUI
import EventKit

struct ContentView: View {
    let eventStore = EKEventStore()
    
    var body: some View {
        Text("Hello, world!")
            .padding()
            .onAppear{
                NotificationCenter.default.addObserver(self, selector: Selector("observeEvents"), name: .EKEventStoreChanged, object: eventStore)
            }
    }
    
    @objc func observeEvents() {
        print("called with updated events")
    }
}

Am I doing something wrong? What would be the way to subscribe to calendar event updates within the EventStore in SwiftUI?

1 Answers1

1

You have a couple of options. One is to use an ObservableObject that can have @objc functions and declare it as the delegate for the notifications:

struct ContentView: View {
    @StateObject private var eventDelegate = EventDelegate()
    
    var body: some View {
        Text("Hello, world!")
            .padding()
    }
}

class EventDelegate: ObservableObject {
    let eventStore = EKEventStore()
    
    init() {
        NotificationCenter.default.addObserver(self, selector: #selector(observeEvents), name: .EKEventStoreChanged, object: eventStore)
        //remember to clean up your observers later
    }
    
    @objc func observeEvents() {
        print("called with updated events")
    }
}

A second option would be to use NotificationCenter's publisher inside your View. Note this example is incomplete, as it's unclear what actions you want to take with the notification, but it's a template to get you started:

struct ContentView: View {
    @State private var eventStore = EKEventStore() //normally wouldn't use @State for a reference type like this, but it seems pointless to recreate it
    @State private var pub : NotificationCenter.Publisher?
    
    var body: some View {
        Text("Hello, world!")
            .padding()
            .onAppear {
                pub = NotificationCenter.default.publisher(for: .EKEventStoreChanged, object: eventStore)
                //use sink or assign here
            }
    }
}
jnpdx
  • 45,847
  • 6
  • 64
  • 94
  • Thank you for your response! This will definitely help me get going. The first example from above works, and I receive the calendar event update notification. However, I wouldn’t be able to pass a state variable owned by the ContentView to the “observeEvents” function. Also, it appears that the notification for the event store update won't be called until the app is the main app running, is that correct? – silentfilozofer Feb 13 '22 at 21:45
  • Due to me trying to pass a State variable owned by ContentView down into the event update handler function, I am thinking of taking the second approach, but I'm not altogether familiar with that pattern, and adding the below did not work. Am I missing something? ``` pub?.sink(receiveCompletion: { _ in print("EVENT UPDATE") // call handle function, passing in state... ie, pass in date State from above }, receiveValue: { _ in print("EVENT UPDATE") // call handle function, passing in state... ie, pass in date State from above }) ``` – silentfilozofer Feb 13 '22 at 21:46
  • In regards to your first comment, you didn't show any `@State` in your example, but if you did have state, why not store it as a `Published` property in the `ObservableObject`? Then you'd have access to it. – jnpdx Feb 13 '22 at 22:03
  • To your second comment, can you expand on what you mean by "did not work"? In general, I'd say that the `.sink` should attach directly to the code I wrote -- it shouldn't be a new line starting with `pub?` – jnpdx Feb 13 '22 at 22:04
  • Sorry, I should have been more clear. What I meant is, the code within the .sink wasn't getting called after I added a calendar event. And this was even after I put it directly after the pub line, as you suggested above. However, I seem to have found a solution that works (https://stackoverflow.com/a/61133418/16330832 was also useful)... it appears that the publisher method you suggested does work, but only when I use .onReceive. This works: `.onReceive(NotificationCenter.default.publisher(for: .EKEventStoreChanged, object: eventStore)) { (output) in print("event update") }` – silentfilozofer Feb 14 '22 at 03:00
  • I marked your response as the correct answer, as I am so grateful for your help! I wouldn't have been able to get this working without your help. Thank you so much for helping me learn about this. NOTE: Also of note for anyone reading this after, is that the notification is only after the app has been re-opened (ie, while it is the active app). – silentfilozofer Feb 14 '22 at 03:00