1

I have used CalenderKit on my app. I have 2 types of users my goal is to collect calender data from targeted type and reflect it to other targeted type.

  • First type went quite well, I have collected the date range data and simply pulled it to Firebase. Works fine.

  • Second type makes me lose my mind. So idea here is to pull stored data from Firebase, generate it to EKEvent and reflect it to an empty Calendar via CalenderKit.

// EventBox Class
struct EventBox {
    let startDate: String
    let endDate: String
    let isAllDay: Bool
    let title: String
}
    var userEventBox: [EventBox] = []
    
    func getEvents() {
        
        self.db.collection(XX).document(XX).collection("Calendar").addSnapshotListener { [self] (querySnapshot, error) in
            
            self.userEventBox = []
            
            if let e = error {
                print("There was an issue retrieving data from Firestore. \(e)")
            } else {
                
                if let snapshotDocuments = querySnapshot?.documents {
                    
                    for doc in snapshotDocuments {
                        
                        let data = doc.data()
                        if let title = data["title"] as? String ,
                           let startDate = data["startDate"] as? String ,
                           let endDate = data["endDate"] as? String ,
                           let isAllDay = data["isAllDay"] as? Bool
                        {
                            let newEventBox = EventBox(startDate: startDate, endDate: endDate, isAllDay: isAllDay, title: title)
                            self.userEventBox.append(newEventBox)
                            print(newEventBox)
                            self.generate() //Triggers second func after data collected and stored
                        }
                    }
                }
            }
        }
    }
    

    func generate() {
        
        for generation in userEventBox {

// I had issues when I tried to save data to Firebase as Date and pull it back. So I decided to store dates as String and use dateFormatter when downloaded.
            
            let isoStartDate = generation.startDate
            let isoEndDate = generation.endDate
            
            let dateFormatter = ISO8601DateFormatter()
            let startDate = dateFormatter.date(from:isoStartDate)!
            let endDate = dateFormatter.date(from:isoEndDate)!

      //dates formatted      

            if let event = EKEvent() as? EKEvent {

            event.title = generation.title
            event.isAllDay = generation.isAllDay
            event.startDate = startDate
            event.endDate = endDate
            event.calendar.cgColor = CGColor(red: 1, green: 1, blue: 1, alpha: 1)
                self.generated = [event]
            }
            
        }
    }
    
    var generated = [EKEvent()] // This variable is where I store all events after its generated

// And finally I am triggering the first getEvents func in override

    override func eventsForDate(_ date: Date) -> [EventDescriptor] {
        
        self.getEvents()
        
        let calenderKitEvents = generated.map(EKWrapper.init)
        
        return calenderKitEvents
    }
}

Problem is I am having this error and I couldnt figured it out for days. Any help will be appreciated.

Ugur Usta
  • 21
  • 1
  • 4
  • Do you need to store events in the user's calendar? If not, then do not use `EKEvent` and create your own `:EventDescriptor` class that will "talk" to calendarkit – Richard Topchii Aug 18 '22 at 19:06
  • Please also post your `EventBox` class – Richard Topchii Aug 18 '22 at 20:02
  • The error is pretty clear. You can't call the `EKEvent()` initialiser. You need to use the [`init(eventStore:)`](https://developer.apple.com/documentation/eventkit/ekevent/1507483-init) initialiser. That is, `EKEvent` must be created in a store. it cannot exist without one – Paulw11 Aug 18 '22 at 21:50
  • Also, I strongly suggest you figure out how to store dates properly as a Firestore timeStamp. It will make sorting and querying by date much easier for you. Firestore swift supports `Codable` so if you create a Swift struct with a `Date` property, the conversion is handled for you – Paulw11 Aug 18 '22 at 21:52
  • No I dont need to store events, just in Firebase. Whenever 1st user type opens calendar or edits something its triggering the upload func. Storing in firebase is the important part. Whenever second user type opens 1st type user profile's calendar then this code you see above should work and reflect it. Thanks for the comment @RichardTopchii I will try to do it today also EventBox class added top of the code. – Ugur Usta Aug 19 '22 at 05:43
  • @Paulw11 Definitely I will work on storing Dates format but I am literally going crazy I need to accomplish main goal first. The Apple Developer manual about EKEvent and related terms are a little hard to understand. I will try to figure it out and update you. Thanks Mate! – Ugur Usta Aug 19 '22 at 05:48
  • If you aren't accessing or storing events in a calendar on the device then you can't use `EKEvent` - It is tied to `EKEventStore`. Just use your own structure for events. – Paulw11 Aug 19 '22 at 07:01
  • 1
    @Paulw11 I figured it out. Please check the answer below. Curious about your comments. – Ugur Usta Aug 19 '22 at 08:06

1 Answers1

1

So here is what I did and what has been achieved with it.

// created another class for generation
struct EventBoxWithDate {
    var startDate: Date
    var endDate: Date
    var isAllDay: Bool
    var title: String
    var color: CGColor
}

// Than I created a custom EKWrapper


final class CustomEKWrapper: EventDescriptor {
    public var dateInterval: DateInterval {
        get {
            DateInterval(start: ekEvent.startDate, end: ekEvent.endDate)
        }
        
        set {
            ekEvent.startDate = newValue.start
            ekEvent.endDate = newValue.end
        }
    }
    
    public var isAllDay: Bool {
        get {
            ekEvent.isAllDay
        }
        set {
            ekEvent.isAllDay = newValue
        }
    }
    
    public var text: String {
        get {
            ekEvent.title
        }
        
        set {
            ekEvent.title = newValue
        }
    }

    public var attributedText: NSAttributedString?
    public var lineBreakMode: NSLineBreakMode?
    
    public var color: UIColor {
        get {
            UIColor(cgColor: ekEvent.color)
        }
    }
    
    public var backgroundColor = UIColor()
    public var textColor = SystemColors.label
    public var font = UIFont.boldSystemFont(ofSize: 12)
    public weak var editedEvent: EventDescriptor? {
        didSet {
            updateColors()
        }
    }
    
    public private(set) var ekEvent: EventBoxWithDate
    
    public init(eventKitEvent: EventBoxWithDate) {
        self.ekEvent = eventKitEvent
        applyStandardColors()
    }
    
    public func makeEditable() -> Self {
        let cloned = Self(eventKitEvent: ekEvent)
        cloned.editedEvent = self
        return cloned
    }
    
    public func commitEditing() {
        guard let edited = editedEvent else {return}
        edited.dateInterval = dateInterval
    }
    
    private func updateColors() {
      (editedEvent != nil) ? applyEditingColors() : applyStandardColors()
    }
    
    /// Colors used when event is not in editing mode
    private func applyStandardColors() {
      backgroundColor = dynamicStandardBackgroundColor()
      textColor = dynamicStandardTextColor()
    }
    
    /// Colors used in editing mode
    private func applyEditingColors() {
      backgroundColor = color.withAlphaComponent(0.95)
      textColor = .white
    }
    
    /// Dynamic color that changes depending on the user interface style (dark / light)
    private func dynamicStandardBackgroundColor() -> UIColor {
      let light = backgroundColorForLightTheme(baseColor: color)
      let dark = backgroundColorForDarkTheme(baseColor: color)
      return dynamicColor(light: light, dark: dark)
    }
    
    /// Dynamic color that changes depending on the user interface style (dark / light)
    private func dynamicStandardTextColor() -> UIColor {
      let light = textColorForLightTheme(baseColor: color)
      return dynamicColor(light: light, dark: color)
    }
    
    private func textColorForLightTheme(baseColor: UIColor) -> UIColor {
      var h: CGFloat = 0, s: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
      baseColor.getHue(&h, saturation: &s, brightness: &b, alpha: &a)
      return UIColor(hue: h, saturation: s, brightness: b * 0.4, alpha: a)
    }
    
    private func backgroundColorForLightTheme(baseColor: UIColor) -> UIColor {
      baseColor.withAlphaComponent(0.3)
    }
    
    private func backgroundColorForDarkTheme(baseColor: UIColor) -> UIColor {
      var h: CGFloat = 0, s: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
      color.getHue(&h, saturation: &s, brightness: &b, alpha: &a)
      return UIColor(hue: h, saturation: s, brightness: b * 0.4, alpha: a * 0.8)
    }
    
    private func dynamicColor(light: UIColor, dark: UIColor) -> UIColor {
      if #available(iOS 13.0, *) {
        return UIColor { traitCollection in
          let interfaceStyle = traitCollection.userInterfaceStyle
          switch interfaceStyle {
          case .dark:
            return dark
          default:
            return light
          }
        }
      } else {
        return light
      }
    }
}

// And edited the code

    
    func getEvents() {
                
        self.db.collection(xx).document(xx).collection("Calendar").addSnapshotListener { [self] (querySnapshot, error) in
            
            self.userEventBox = []
            
            if let e = error {
                print("There was an issue retrieving data from Firestore. \(e)")
            } else {
                
                if let snapshotDocuments = querySnapshot?.documents {
                    
                    for doc in snapshotDocuments {
                        // @PAUL here I use timestamp for healthier usage as suggested.
                        let data = doc.data()
                        if let title = data["title"] as? String ,
                           let startDate = data["startDate"] as? Timestamp ,
                           let endDate = data["endDate"] as? Timestamp ,
                           let isAllDay = data["isAllDay"] as? Bool
                        {
                            let newEventBox = EventBox(startDate: startDate.dateValue(), endDate: endDate.dateValue(), isAllDay: isAllDay, title: title)
                            self.userEventBox.append(newEventBox)
                        }
                    }
                    self.generate()
                }
            }
        }
    }
    

    func generate() {
        

        for generation in userEventBox {
 
            let event = EventBoxWithDate(startDate: generation.startDate, endDate: generation.endDate, isAllDay: generation.isAllDay, title: generation.title, color: UIColor.green.cgColor)

           // EDITED THIS =>   self.generated = [event] 
           // TO THIS 
                self.generated.append(event)

            
        }
// AND ADDED
reloadData()
// EVERYTHING WORKS NOW
        }

    
    var generated: [EventBoxWithDate] = []
    
    override func eventsForDate(_ date: Date) -> [EventDescriptor] {
                
        let calenderKitEvents = generated.map(CustomEKWrapper.init)
        
        return calenderKitEvents
    }
}

//

This customization work quite well I can reflect my stored events on CalenderKit. Only thing left here is generated variable is storing all events but when code runs in override func and maps the generated variable it only shows the earliest event on the calender. When I figure it out I will update you . Maybe it will help someone else.

UPDATED THE GENERATE FUNC AND IT WORKS NOW FOR ALL EVENTS

Ugur Usta
  • 21
  • 1
  • 4
  • Looks good. You can even make `EventBox` conform to `EventDescriptor` to avoid converting/initializing your `CustomEKWrapper`. Overall, it's a good direction and the way the library is meant to be used. Always create your own `EventDescriptor` if the default `Event` class is too limiting. – Richard Topchii Aug 19 '22 at 15:31
  • 1
    @RichardTopchii Thanks Mate! Its a quite relief hahah. I will keep modifying the code for future purposes and thanks to you as well for this great library. I mean it that this is the best lib I have used for creating a calender, Less complicated highly useful. Looking forward to CalendarKit updates!! – Ugur Usta Aug 19 '22 at 16:43