1

Please note that I am attempting to add an appointment on an iPhone 13 Pro Max simulator, where I am not able to create a new event using XCode 13.

I have been basing my code on this sample,

https://github.com/richardtop/CalendarApp

Here is the source code for CalendarViewController.swift, where I think the createNewEvent is a key function:

import UIKit
import CalendarKit
import EventKit
import EventKitUI

final class CalendarViewController: DayViewController, EKEventEditViewDelegate {
    private var eventStore = EKEventStore()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Calendar"
        // The app must have access to the user's calendar to show the events on the timeline
        requestAccessToCalendar()
        // Subscribe to notifications to reload the UI when 
        subscribeToNotifications()
    }
    
    private func requestAccessToCalendar() {
        // Request access to the events
        eventStore.requestAccess(to: .event) { [weak self] granted, error in
            // Handle the response to the request.
            DispatchQueue.main.async {
                guard let self = self else { return }
                self.initializeStore()
                self.subscribeToNotifications()
                self.reloadData()
            }
        }
    }
    
    private func subscribeToNotifications() {
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(storeChanged(_:)),
                                               name: .EKEventStoreChanged,
                                               object: eventStore)
    }
    
    private func initializeStore() {
        eventStore = EKEventStore()
    }
    
    @objc private func storeChanged(_ notification: Notification) {
        reloadData()
    }
    
    // MARK: - DayViewDataSource
    
    // This is the `DayViewDataSource` method that the client app has to implement in order to display events with CalendarKit
    override func eventsForDate(_ date: Date) -> [EventDescriptor] {
        // The `date` always has it's Time components set to 00:00:00 of the day requested
        let startDate = date
        var oneDayComponents = DateComponents()
        oneDayComponents.day = 1
        // By adding one full `day` to the `startDate`, we're getting to the 00:00:00 of the *next* day
        let endDate = calendar.date(byAdding: oneDayComponents, to: startDate)!
        
        let predicate = eventStore.predicateForEvents(withStart: startDate, // Start of the current day
                                                      end: endDate, // Start of the next day
                                                      calendars: nil) // Search in all calendars
        
        let eventKitEvents = eventStore.events(matching: predicate) // All events happening on a given day
        let calendarKitEvents = eventKitEvents.map(EKWrapper.init)
        
        return calendarKitEvents
    }
    
    // MARK: - DayViewDelegate
    
    // MARK: Event Selection
    
    override func dayViewDidSelectEventView(_ eventView: EventView) {
        guard let ckEvent = eventView.descriptor as? EKWrapper else {
            return
        }
        presentDetailViewForEvent(ckEvent.ekEvent)
    }
    
    private func presentDetailViewForEvent(_ ekEvent: EKEvent) {
        let eventController = EKEventViewController()
        eventController.event = ekEvent
        eventController.allowsCalendarPreview = true
        eventController.allowsEditing = true
        navigationController?.pushViewController(eventController,
                                                 animated: true)
    }
    
    // MARK: Event Editing
    
    override func dayView(dayView: DayView, didLongPressTimelineAt date: Date) {
        // Cancel editing current event and start creating a new one
        endEventEditing()
        let newEKWrapper = createNewEvent(at: date)
        create(event: newEKWrapper, animated: true)
    }
    
    private func createNewEvent(at date: Date) -> EKWrapper {
        let newEKEvent = EKEvent(eventStore: eventStore)
        newEKEvent.calendar = eventStore.defaultCalendarForNewEvents
        
        var components = DateComponents()
        components.hour = 1
        let endDate = calendar.date(byAdding: components, to: date)
        
        newEKEvent.startDate = date
        newEKEvent.endDate = endDate
        newEKEvent.title = "New event"

        let newEKWrapper = EKWrapper(eventKitEvent: newEKEvent)
        newEKWrapper.editedEvent = newEKWrapper
        return newEKWrapper
    }
    
    override func dayViewDidLongPressEventView(_ eventView: EventView) {
        guard let descriptor = eventView.descriptor as? EKWrapper else {
            return
        }
        endEventEditing()
        beginEditing(event: descriptor,
                     animated: true)
    }
    
    override func dayView(dayView: DayView, didUpdate event: EventDescriptor) {
        guard let editingEvent = event as? EKWrapper else { return }
        if let originalEvent = event.editedEvent {
            editingEvent.commitEditing()
            
            if originalEvent === editingEvent {
                // If editing event is the same as the original one, it has just been created.
                // Showing editing view controller
                presentEditingViewForEvent(editingEvent.ekEvent)
            } else {
                // If editing event is different from the original,
                // then it's pointing to the event already in the `eventStore`
                // Let's save changes to oriignal event to the `eventStore`
                try! eventStore.save(editingEvent.ekEvent,
                                     span: .thisEvent)
            }
        }
        reloadData()
    }
    
    
    private func presentEditingViewForEvent(_ ekEvent: EKEvent) {
        let eventEditViewController = EKEventEditViewController()
        eventEditViewController.event = ekEvent
        eventEditViewController.eventStore = eventStore
        eventEditViewController.editViewDelegate = self
        present(eventEditViewController, animated: true, completion: nil)
    }
    
    override func dayView(dayView: DayView, didTapTimelineAt date: Date) {
        endEventEditing()
    }
    
    override func dayViewDidBeginDragging(dayView: DayView) {
        endEventEditing()
    }
    
    // MARK: - EKEventEditViewDelegate
    
    func eventEditViewController(_ controller: EKEventEditViewController, didCompleteWith action: EKEventEditViewAction) {
        endEventEditing()
        reloadData()
        controller.dismiss(animated: true, completion: nil)
    }
}

It gets to the point where I can add an appointment, but the Add button in the top RHS does not work, and I am unable to edit the New event text.

https://i.stack.imgur.com/XvZ3s.jpg

The issue for me is not so much the warning. I just want to be able to create an appointment using CalendarKit. Does anyone have any suggestions? TIA.

user8128167
  • 6,929
  • 6
  • 66
  • 79
  • 1
    Please, share the code you're using with CalendarKit. Also, that screen is actually coming from the EventKitUI (Apple's framework), not CalendarKit: https://developer.apple.com/documentation/eventkitui – Richard Topchii Apr 17 '22 at 01:13
  • 1
    It is the code from here https://github.com/richardtop/CalendarApp.git – user8128167 Apr 17 '22 at 03:04
  • 1
    The MCVE should be included here, not in an external link (which can be broken in the future). I’d suggest you edit this question to include just the salient info (the “minimal” of MCVE), i.e., code that creates an event. – Rob Apr 17 '22 at 05:18
  • So, what you're saying is that the CalendarApp doesn't work straight out of the box? – Richard Topchii Apr 17 '22 at 17:27
  • Yes, I followed your Youtube video youtube.com/watch?v=iC4XxmExqFA and I also cloned the code in the GIT repository and in both cases the `Add` button failed to create a new appointment in the calendar. – user8128167 Apr 18 '22 at 23:11
  • I think part of the issue might be that I am unable to toggle the software keyboard in iPhone 13 Pro Max emulator on Mac OS X Big Sur even after I select `I/O` -> `Keyboard` -> `Toggle Software Keyboard` in the simulator or if I click in the "New Event" textbox. – user8128167 Apr 18 '22 at 23:21
  • Actually, it appears that no click or keyboard events are recognized in the simulator no matter where I click or type in the `New Event` dialog. – user8128167 Apr 18 '22 at 23:26
  • Did you grant the app permissions to access the calendar? That might be the issue, try reinstalling the app and when it asks for the permission, press "OK" or "Allow" – Richard Topchii Apr 19 '22 at 03:14
  • I've just retested the app and it seems to be fully working out of the box with the latest CalendarKit installed. – Richard Topchii Apr 19 '22 at 03:15
  • Yes, that was it, thank you! Please post it as an answer and I will mark it as such. The issue is that I chose `Device` -> `Erase All Content and Settings...` in the simulator but when debugged the app again after that, it didn't request access to the calendar again for some reason, but when I tested on a different device it requested permission on the first run and things worked properly. – user8128167 Apr 19 '22 at 16:34

1 Answers1

2

The issue is that I chose Device -> Erase All Content and Settings... after giving permission to access my calendar in the simulator but when I debugged the app again after that, so I guess that means it loses access to my calendar, but it didn't request access to the calendar again for some reason.

When I tested on a different device it requested permission on the first run and things worked properly after that.

user8128167
  • 6,929
  • 6
  • 66
  • 79