Search code examples
flutterpackagecalendar

add_2_calendar not working after updating to Xcode iOS 17


In my flutter iOS app am using the package add_2_calendar to add some events to users calendar. After updating to iOS 17 I noticed that this functionality is not working any more. Inside my Info.plist I have the following

    <key>NSCalendarsUsageDescription</key>
    <string>$(PRODUCT_NAME) user your calendar</string>
    <key>NSContactsUsageDescription</key>
    <string>$(PRODUCT_NAME) user your calendar</string>

I tried to include NSCalendarsWriteOnlyAccessUsageDescription as the NSCalendarsUsageDescription Deprecated but nothing changed

<key>NSCalendarsWriteOnlyAccessUsageDescription</key>
    <string>Add event to calendar</string>

On terminal am just getting the message XPC connection was invalidated

How I use it in my flutter code below:

final Event calendarEvent = Event(
    title: '$homeTeamName vs $awayTeamName',
    description: '$leagueName ($round)\nReferee: $referee',
    location: stadium,
    startDate: matchTimestamp,
    endDate: matchTimestamp.add(const Duration(hours: 2)),
    iosParams: const IOSParams(
      reminder: Duration(hours: 1),
      url: 'https://hadjimamas.github.io/',
    ),
    androidParams: const AndroidParams(emailInvites: []),
  );

..some code

InkWell(
          onTap: () {
            Add2Calendar.addEvent2Cal(calendarEvent);
          },

Package Documentation add_2_calendar


Solution

  • In the following link you can find a quick fix related with this issue Quick Fix

    Actually edit add_2_calendar/ios/Classes/SwiftAdd2CalendarPlugin.swift with the following:

    import Flutter
    import UIKit
    import EventKit
    import EventKitUI
    import Foundation
    
    extension Date {
      init(milliseconds:Double) {
          self = Date(timeIntervalSince1970: TimeInterval(milliseconds) / 1000)
      }
    }
    
    var statusBarStyle = UIApplication.shared.statusBarStyle
    
    public class SwiftAdd2CalendarPlugin: NSObject, FlutterPlugin {
    
    public static func register(with registrar: FlutterPluginRegistrar) {
      let channel = FlutterMethodChannel(name: "add_2_calendar", binaryMessenger: registrar.messenger())
      let instance = SwiftAdd2CalendarPlugin()
      registrar.addMethodCallDelegate(instance, channel: channel)
    }
    
    public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
      if call.method == "add2Cal" {
        let args = call.arguments as! [String:Any]
       
          
        addEventToCalendar(from: args,completion:{ (success) -> Void in
              if success {
                  result(true)
              } else {
                  result(false)
              }
          })
      }
    }
    
    private func addEventToCalendar(from args: [String:Any], completion: ((_ success: Bool) -> Void)? = nil) {
        
        
        let title = args["title"] as! String
        let description = args["desc"] is NSNull ? nil: args["desc"] as! String
        let location = args["location"] is NSNull ? nil: args["location"] as! String
        let timeZone = args["timeZone"] is NSNull ? nil: TimeZone(identifier: args["timeZone"] as! String)
        let startDate = Date(milliseconds: (args["startDate"] as! Double))
        let endDate = Date(milliseconds: (args["endDate"] as! Double))
        let alarmInterval = args["alarmInterval"] as? Double
        let allDay = args["allDay"] as! Bool
        let url = args["url"] as? String
        
        let eventStore = EKEventStore()
        let event = createEvent(eventStore: eventStore, alarmInterval: alarmInterval, title: title, description: description, location: location, timeZone: timeZone, startDate: startDate, endDate: endDate, allDay: allDay, url: url, args: args)
    
        presentCalendarModalToAddEvent(event, eventStore: eventStore, completion: completion)
    }
    
    private func createEvent(eventStore: EKEventStore, alarmInterval: Double?, title: String, description: String?, location: String?, timeZone: TimeZone?, startDate: Date?, endDate: Date?, allDay: Bool, url: String?, args: [String:Any]) -> EKEvent {
        let event = EKEvent(eventStore: eventStore)
        if let alarm = alarmInterval{
            event.addAlarm(EKAlarm(relativeOffset: alarm*(-1)))
        }
        event.title = title
        event.startDate = startDate
        event.endDate = endDate
        if (timeZone != nil) {
            event.timeZone = timeZone
        }
        if (location != nil) {
            event.location = location
        }
        if (description != nil) {
            event.notes = description
        }
        if let url = url{
            event.url = URL(string: url);
        }
        event.isAllDay = allDay
        
        if let recurrence = args["recurrence"] as? [String:Any]{
            let interval = recurrence["interval"] as! Int
            let frequency = recurrence["frequency"] as! Int
            let end = recurrence["endDate"] as? Double// Date(milliseconds: (args["startDate"] as! Double))
            let ocurrences = recurrence["ocurrences"] as? Int
            
            let recurrenceRule = EKRecurrenceRule.init(
                recurrenceWith: EKRecurrenceFrequency(rawValue: frequency)!,
                interval: interval,
                end: ocurrences != nil ? EKRecurrenceEnd.init(occurrenceCount: ocurrences!) : end != nil ? EKRecurrenceEnd.init(end: Date(milliseconds: end!)) : nil
            )
            event.recurrenceRules = [recurrenceRule]
        }
        
        return event
    }
    
    private func getAuthorizationStatus() -> EKAuthorizationStatus {
        return EKEventStore.authorizationStatus(for: EKEntityType.event)
    }
    
    // Show event kit ui to add event to calendar
    
    func presentCalendarModalToAddEvent(_ event: EKEvent, eventStore: EKEventStore, completion: ((_ success: Bool) -> Void)? = nil) {
        if #available(iOS 17, *) {
            OperationQueue.main.addOperation {
                self.presentEventCalendarDetailModal(event: event, eventStore: eventStore)
            }
        } else {
            let authStatus = getAuthorizationStatus()
            switch authStatus {
            case .authorized:
                OperationQueue.main.addOperation {
                    self.presentEventCalendarDetailModal(event: event, eventStore: eventStore)
                }
                completion?(true)
            case .notDetermined:
                //Auth is not determined
                //We should request access to the calendar
                eventStore.requestAccess(to: .event, completion: { [weak self] (granted, error) in
                    if granted {
                        OperationQueue.main.addOperation {
                            self?.presentEventCalendarDetailModal(event: event, eventStore: eventStore)
                        }
                        completion?(true)
                    } else {
                        // Auth denied
                        completion?(false)
                    }
                })
            case .denied, .restricted:
                // Auth denied or restricted
                completion?(false)
            default:
                completion?(false)
            }
        }
    }
    
    // Present edit event calendar modal
    
    func presentEventCalendarDetailModal(event: EKEvent, eventStore: EKEventStore) {
        let eventModalVC = EKEventEditViewController()
        eventModalVC.event = event
        eventModalVC.eventStore = eventStore
        eventModalVC.editViewDelegate = self
        
        if #available(iOS 13, *) {
            eventModalVC.modalPresentationStyle = .fullScreen
        }
        
        if let root = UIApplication.shared.keyWindow?.rootViewController {
            root.present(eventModalVC, animated: true, completion: {
                statusBarStyle = UIApplication.shared.statusBarStyle
                UIApplication.shared.statusBarStyle = UIStatusBarStyle.default
            })
        }
    }
    }
    
    extension SwiftAdd2CalendarPlugin: EKEventEditViewDelegate {
    
    public func eventEditViewController(_ controller: EKEventEditViewController, didCompleteWith action: EKEventEditViewAction) {
        controller.dismiss(animated: true, completion: {
            UIApplication.shared.statusBarStyle = statusBarStyle
        })
    }
    }