📅 Sample calendar app created with CalendarKit (template)

Overview

CalendarApp

License

CalendarApp

CalendarApp is a template repository serving as a starting point for experiments with CalendarKit. It's a sample calendar app for iOS built with CalendarKit and EventKit. It displays events stored in the EKEventStore similarly to the default calendar app.

Tutorials

Getting Started

To run the app, just clone the repository and open Calendar.xcodeproj. If you'd like to edit the code, press "Use this template" to copy this repository to your GitHub account and keep your changes.

Need Help?

If you have a programming question about how to use CalendarKit in your application, ask it on StackOverflow with the CalendarKit tag.

Please, use GitHub Issues only for reporting a bug or requesting a new feature.

Examples

Video

Try live in a browser

To try CalendarKit with CocoaPods issue the following command in the Terminal:

pod try CalendarKit

Requirements

To run CalendarApp:

  • Xcode 12
  • iOS 14

Contributing

CalendarApp is an ongoing project and contributions are welcome. The goal for this project is to be a reference and demonstrate the capabilities of the CalendarKit. The app is inspired by the iOS / iPadOS Calendar App.

Author

Richard Topchii

License

CalendarApp is available under the MIT license. See the LICENSE file for more info.

You might also like...
Development of the TUM Campus App for iOS devices - for and from students at Technical University of Munich.
Development of the TUM Campus App for iOS devices - for and from students at Technical University of Munich.

Tum Campus App - An Unofficial Guide Through University Life The TUM Campus App (TCA) is an open source project, developed by volunteers and available

Lightweight iOS Photo Blur App

Blurry Blurry is the go-to image blurring tool to help you apply beautiful blurs for your photos. It is perfect for creating wallpapers, backgrounds,

Alfresco iOS App - Alfresco is the open platform for business-critical content management and collaboration.

Welcome to the Alfresco iOS App Alfresco is the open platform for business-critical content management and collaboration. Alfresco Mobile was designed

Build a Swift App as a designer
Build a Swift App as a designer

DesignerNewsApp Simple iOS client for Designer News, by the creator of Design+Code and the team, written in Swift. Usage Download the repository $ git

📱 Nextcloud iOS app

Nextcloud iOS app Check out https://nextcloud.com and follow us on twitter.com/nextclouders or twitter.com/NextcloudiOS How to contribute If you want

🍣Making Recipes iOS app
🍣Making Recipes iOS app

Recipes App ❤️ Support my apps ❤️ Push Hero - pure Swift native macOS application to test push notifications PastePal - Pasteboard, note and shortcut

PixPic, a Photo Editing App
PixPic, a Photo Editing App

PixPic PixPic, a Photo Editing App Built by Our iOS Interns What's the best way to teach interns how to write an iOS app? Just let them do it! This ap

📱The official Wikipedia iOS app.

Wikipedia iOS The official Wikipedia iOS app. License: MIT License Source repo: https://github.com/wikimedia/wikipedia-ios Planning (bugs & features):

An app focused on show in a visual way how sorting algorithms actually works.
An app focused on show in a visual way how sorting algorithms actually works.

Sorting Algorithms App An open source app focused on show in a visual way how sorting algorithms actually works. Available on the app store Do you wan

Comments
  • Restart needed after granted calendar access to display events

    Restart needed after granted calendar access to display events

    Hi, i am using the latest CK Version. When i start my app the first time, i granted the permission to access my calendar. But after that, CalendarKit is not able to display events. I try to call reloadData() in requestAccessToCalendar() but with now success.

    What do i need to change to display events after request access without starting the app again?

    bug 
    opened by niklasgrewe 4
  • got nil when try to open event

    got nil when try to open event

    when press on event I got nil as he can't cast from EventDescriptor to EkWrapper any help please ` // // CalendarViewController.swift // Servbud provider // // Created by Khaled on 22/12/2021. // Copyright © 2021 ORGANIZATIONNAME. All rights reserved. //

    import UIKit import CalendarKit import EventKit import EventKitUI import LKAlertController

    protocol CalendarDisplayLogic: AnyObject { func displayAvailableTimes(times: [[MainNetworkModels.AvailableTimes]]) func displayError(errorMessage: String) func displayUnAuthorized() }

    class CalendarViewController: DayViewController, CalendarDisplayLogic, EKEventEditViewDelegate {

    // MARK: - Properties
    
    typealias Models = CalendarModels
    var router: (NSObjectProtocol & CalendarRoutingLogic & CalendarDataPassing)?
    var interactor: CalendarBusinessLogic?
    private var eventStore =  EKEventStore()
    let apiDate = DateFormatter()
    let currentDate = Calendar.current.dateComponents([.year, .month], from: Date())
    var currentmonth = Calendar.current.dateComponents([.year, .month], from: Date()).month
    var allTimes: [MainNetworkModels.AvailableTimes] = []
    var events = [Event]()
    
    // MARK: - Object lifecycle
    
    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
        setup()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }
    
    // MARK: - Setup
    
    private func setup() {
        let viewController = self
        let interactor = CalendarInteractor()
        let presenter = CalendarPresenter()
        let router = CalendarRouter()
    
        viewController.router = router
        viewController.interactor = interactor
        interactor.presenter = presenter
        presenter.viewController = viewController
        router.viewController = viewController
    }
    
    // MARK: - View Lifecycle
    
    override func viewDidLoad() {
        super.viewDidLoad()
        apiDate.dateFormat = "yyyy-MM-dd HH:mm:ss"
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        setupUI()
        requestAccessToCalendar()
        view.showActivityView()
        interactor?.getAvailableTimes(month: String(currentDate.month ?? 1), year: String(currentDate.year ?? 2022))
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        events.removeAll()
        allTimes.removeAll()
    }
    
    func setupUI(){
        title = "My Calender"
    }
    
    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.reloadData()
            }
        }
    }
    
    private func initializeStore() {
        eventStore = EKEventStore()
    }
    
    override func eventsForDate(_ date: Date) -> [EventDescriptor] {
        events.removeAll()
        allTimes.forEach { time in
            let fromStringString = (time.from?.date ?? "") + " " + (time.from?.time ?? "")
            let toStringString = (time.to?.date ?? "") + " " + (time.to?.time ?? "")
            let newEvent = Event()
            newEvent.dateInterval = DateInterval(start: apiDate.date(from: fromStringString) ?? Date(), end: apiDate.date(from: toStringString) ?? Date())
            newEvent.isAllDay = false
            newEvent.color = .random
            newEvent.backgroundColor = .random
            newEvent.text = "Avabilty \n from: " + (time.from?.time ?? "") + "to: " + (time.to?.time ?? "")
            newEvent.textColor = .white
            events.append(newEvent)
        }
        return events
    }
    
    // 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, to endDate: Date) -> EKWrapper {
        let newEKEvent = EKEvent(eventStore: eventStore)
        newEKEvent.calendar = eventStore.defaultCalendarForNewEvents
        newEKEvent.startDate = date
        newEKEvent.endDate = endDate
        newEKEvent.title = "Availabilty"
        let newEKWrapper = EKWrapper(eventKitEvent: newEKEvent)
        newEKWrapper.editedEvent = newEKWrapper
        return newEKWrapper
    }
    
    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 = "Availabilty"
        
        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 {
                presentEditingViewForEvent(editingEvent.ekEvent)
            } else {
                
            }
        }
        events.removeAll()
        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()
        events.removeAll()
        reloadData()
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd"
        let startDate = dateFormatter.string(from: controller.event?.startDate ?? Date())
        dateFormatter.dateFormat = "HH:mm"
        let startTime = dateFormatter.string(from: controller.event?.startDate ?? Date())
        let endTime = dateFormatter.string(from: controller.event?.endDate ?? Date())
        let sameEventsDay = allTimes.filter { event in
            return event.from?.date == startDate
        }
        var availableTimes = sameEventsDay.map { event -> CalendarModels.availabelTimes in
            let eventFormatter = DateFormatter()
            eventFormatter.dateFormat = "HH:mm:ss"
            let from = dateFormatter.string(from: eventFormatter.date(from: event.from?.time ?? "") ?? Date())
            let to = dateFormatter.string(from: eventFormatter.date(from: event.to?.time ?? "") ?? Date())
            return CalendarModels.availabelTimes(date: event.from?.date, from: from, to: to)
        }
        view.showActivityView()
        if action.rawValue == 1 {
            availableTimes.append(CalendarModels.availabelTimes(date: startDate, from: startTime, to: endTime))
        }
        interactor?.setAvailableTimes(availableTime: availableTimes)
        controller.dismiss(animated: true, completion: nil)
    }
    
    func displayAvailableTimes(times: [[MainNetworkModels.AvailableTimes]]){
        view.hideActivityView()
        allTimes.removeAll()
        times.forEach { item in
            allTimes.append(contentsOf: item)
        }
        events.removeAll()
        reloadData()
    }
    
    func displayError(errorMessage: String) {
        view.hideActivityView()
        showAlert(errorMessage: errorMessage, completion: nil)
    }
    
    func showAlert(errorMessage: String, completion: (( _ choose: Bool) -> Void)?){
        Alert(title: "error", message: errorMessage)
            .addAction("ok", style: .default) { _ in
                completion?(true)
            }
            .show()
    }
    
    func displayUnAuthorized(){
        SharedPrefrences.shared.deconnectUSer()
        router?.goToLogin()
    }
    
    override func dayView(dayView: DayView, willMoveTo date: Date){
        let calendarDate = Calendar.current.dateComponents([.year, .month], from: date)
        if (calendarDate.month != currentmonth) {
            events.removeAll()
            allTimes.removeAll()
            view.showActivityView()
            interactor?.getAvailableTimes(month: String(calendarDate.month ?? 1), year: String(calendarDate.year ?? 2022))
            currentmonth = calendarDate.month
        }
    }
    

    }

    `

    opened by AkaE41 1
  • ekEvent

    ekEvent

    When I was compiling this project, it said "Cannot find 'ekEvent' in scope" The error happened in the line 74 of the "CalenderViewController". How can I fix the problems about the event kit. Please help me out. Thank you very much.

    bug 
    opened by cejas233 1
  • Crash

    Crash

    Hi @richardtop I found a bug (maybe not) when I add invitee from ios calendar and go to calenderkit view and click that event app directly crashes or when I click add invitee in the app in calender kit view it directly crashes. Is there a way to solve it or a way to remove that section that says add invitee? Even I remove the add invitee in the calender kit view if a user has added an invitee from ios calender it will keep crushing. do you have a solution for that? Any document or walktrough video smth?

    ` import UIKit import CalendarKit import EventKit import EventKitUI import Firebase import FirebaseAuth

    class BsCalendarViewController: DayViewController, EKEventEditViewDelegate {

    private let eventStore =  EKEventStore()
    
    let db = Firestore.firestore()
    let currentUserMail = Auth.auth().currentUser?.email
    var xx = ""
    
    override func viewDidLoad() {
        super.viewDidLoad()
        requestAccessToCalendar()
        subscribeToNotifications()
        getCurrentUserData()
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: self.uploadCurrentUserCalendar)
        
        let appearance = UINavigationBarAppearance()
        appearance.configureWithTransparentBackground()
        appearance.shadowColor = nil
        
        let navigationBar = navigationController!.navigationBar
        navigationBar.standardAppearance = appearance
        navigationBar.scrollEdgeAppearance = appearance
        
        self.navigationController?.setToolbarHidden(true, animated: false)
    
    }
    
    func getCurrentUserData() {
        
        db.collection("xx").document(xx!).getDocument { (document, error) in
            
            if let document = document, document.exists {
                   let dataDescription = document.data()
                guard let type = dataDescription?["x x"] else {return}
                self.xx = type as! String
               } else {
                   print("Document does not exist")
               }
        }
    }
    
    var eventBox: [EventBox] = []
    
    func uploadCurrentUserCalendar() {
        
        self.db.collection(xx).document(xx!).collection("Calendar").getDocuments() { (querySnapshot, err) in
            if let err = err {
                let alert = UIAlertController(title: "Upload Error", message: err.localizedDescription, preferredStyle: UIAlertController.Style.alert)
                alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
                self.present(alert, animated: true, completion: nil)
                return
            } else {
                for document in querySnapshot!.documents {
                    document.reference.delete()
                }
                let startDate = Calendar.current.date(byAdding: .day, value: -7, to: Date())
                
                var oneDayComponents = DateComponents()
                oneDayComponents.day = 35
                let endDate = self.calendar.date(byAdding: oneDayComponents, to: startDate!)!
                let predicate = self.eventStore.predicateForEvents(withStart: startDate!, end: endDate, calendars: nil)
                let eventKitEvents = self.eventStore.events(matching: predicate)
            
                for event in eventKitEvents {
                   if let startDate = event.startDate,
                      let endDate = event.endDate,
                      let isAllDay = event.isAllDay as? Bool,
                      let title = event.title{
                       let newEventBox = EventBox(startDate: startDate, endDate: endDate, isAllDay: isAllDay, title: title)
                       self.eventBox.append(newEventBox)
    
                   }
                }
                for uploadData in self.eventBox {
                    self.db.collection(x.xx).document(x.xx!).collection("Calendar").document(uploadData.title).setData([ "title" : uploadData.title, "startDate" : uploadData.startDate, "endDate" : uploadData.endDate, "isAllDay" : uploadData.isAllDay])
                }
            }
        }
        
    
    }
    
    
    override func viewWillDisappear(_ animated: Bool) {
        self.navigationController?.setToolbarHidden(true, animated: false)
    
    }
    override func viewWillAppear(_ animated: Bool) {
        self.navigationController?.setToolbarHidden(true, animated: false)
    
    }
    
    func subscribeToNotifications() {
        NotificationCenter.default.addObserver(self, selector: #selector(storeChanged(_:)), name: .EKEventStoreChanged, object: nil)
    }
    
    @objc func storeChanged(_ notification: Notification) {
        reloadData()
        uploadCurrentUserCalendar()
    }
    
    
    func requestAccessToCalendar() {
        
        eventStore.requestAccess(to: .event) { success, error in
            
        }
    }
    
    
    override func eventsForDate(_ date: Date) -> [EventDescriptor] {
        
        let startDate = date
       
        var oneDayComponents = DateComponents()
        oneDayComponents.day = 1
       
        let endDate = calendar.date(byAdding: oneDayComponents, to: startDate)!
       
        let predicate = eventStore.predicateForEvents(withStart: startDate, end: endDate, calendars: nil)
       
        let eventKitEvents = eventStore.events(matching: predicate)
        let calenderKitEvents = eventKitEvents.map(EKWrapper.init)
    
        return calenderKitEvents
    }
    
    override func dayViewDidSelectEventView(_ eventView: EventView) {
        
        guard let ckEvent = eventView.descriptor as? EKWrapper else {
            return
        }
        
        let ekEvent = ckEvent.ekEvent
        presentDetailView(ekEvent)
    }
    
    private func presentDetailView(_ ekEvent: EKEvent) {
        
        let eventViewController = EKEventViewController()
        eventViewController.event = ekEvent
        eventViewController.allowsCalendarPreview = true
        eventViewController.allowsEditing = true
        navigationController?.pushViewController(eventViewController, animated: true)
    }
    
    override func dayViewDidLongPressEventView(_ eventView: EventView) {
        endEventEditing()
        guard let ckEvent = eventView.descriptor as? EKWrapper else { return }
        
        beginEditing(event: ckEvent, 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 {
                // event creation flow
                presentEditingViewForEvent(editingEvent.ekEvent)
            } else {
                // editing flow
                try! eventStore.save(editingEvent.ekEvent, span: .thisEvent)
            }
            
            
        }
        reloadData()
    }
    
    func presentEditingViewForEvent(_ ekEvent: EKEvent) {
        let editingViewController = EKEventEditViewController()
        editingViewController.editViewDelegate = self
        editingViewController.event = ekEvent
        editingViewController.eventStore = eventStore
        present(editingViewController, animated: true, completion: nil)
    }
    
    override func dayView(dayView: DayView, didTapTimelineAt date: Date) {
        endEventEditing()
    }
    
    override func dayViewDidBeginDragging(dayView: DayView) {
        endEventEditing()
    }
    
    override func dayView(dayView: DayView, didLongPressTimelineAt date: Date) {
        let newEKEvent = EKEvent(eventStore: eventStore)
        newEKEvent.calendar = eventStore.defaultCalendarForNewEvents
        
        var oneHourComponents = DateComponents()
        oneHourComponents.hour = 1
        
        let endDate = calendar.date(byAdding: oneHourComponents, to: date)
        
        newEKEvent.startDate = date
        newEKEvent.endDate = endDate
        newEKEvent.title = "New Event"
        
        let newEKWrapper = EKWrapper(eventKitEvent: newEKEvent)
        newEKWrapper.editedEvent = newEKWrapper
        
        create(event: newEKWrapper, animated: true)
    }
    
    func eventEditViewController(_ controller: EKEventEditViewController, didCompleteWith action: EKEventEditViewAction) {
        endEventEditing()
        reloadData()
        controller.dismiss(animated: true, completion: nil)
    }
    

    }`

    https://user-images.githubusercontent.com/109156022/185787187-5a07a2dd-298c-4de1-a739-4c36e7d389cb.MP4

    bug 
    opened by R35Master 3
Releases(2.0.0)
Owner
Richard Topchii
iOS Developer and content creator. Check out my Youtube channel on iOS development via the link below. Follow me for tips and advice.
Richard Topchii
Basic app to show how to login with Facebook, Google, Twitter. Created for learning purpose :) using Xcode 9 and Swift 4.0

Social Logins iOS Basic app to show how to login with Facebook, Google, Twitter. Created for learning purpose :) using Xcode 9 and Swift 4.0 Note: Bef

Jogendra 12 Nov 4, 2022
Sample iOS app demonstrating Coordinators, Dependency Injection, MVVM, Binding

iOS Sample App Sample iOS app written the way I write iOS apps because I cannot share the app I currently work on. Shown concepts Architecture concept

Igor Kulman 632 Dec 28, 2022
Simple sample of using the VIP (Clean Swift) architecture for iOS

MyAnimeList Simple sample of using the VIP (Clean Swift) architecture for iOS. ViewController: controls the event handling, view life cycle and displa

null 24 Oct 12, 2022
An experimental clone of the new iOS 11 App Store app

appstore-clone An experimental clone of the new iOS 11 App Store app for this Medium Article Description Apple announced an entirely redesigned iOS Ap

Phill Farrugia 498 Dec 13, 2022
This app shows the current percentage of the vaccination campaign in Brazil and its states

This app shows the current percentage of the vaccination campaign in Brazil and its states. The data is obtained thanks to covid19br.

Anderson Kloss Maia 8 Jul 22, 2022
iOS app to record how much things cost using various data persistence implementations.

how-much iOS app to record how much things cost using various data persistence implementations. The basic data unit is an item, a simple dictionary: {

null 22 Aug 15, 2022
Open-Source Messaging App

Acani Chats Open-Source Native iOS Messages App This project and its submodules no longer work and are no longer being maintained. Acani Chats is an i

Acani 2.1k Dec 21, 2022
The (second) best iOS app for GitHub.

GitHawk is the second-highest rated iOS app for GitHub. Features 0️⃣ Inbox Zero your notifications ?? Comment even faster than on GitHub desktop ?? Th

GitHawk 2.8k Dec 23, 2022
The Artsy Auction Kiosk App.

Eidolon The Artsy Auction Kiosk App. Note: Current development is done on the xcode-9 branch using Xcode 9 (available for download on Apple's develope

Artsy 2.7k Dec 25, 2022
iOS app for 5calls.org

5Calls iOS App This is the repository for the iOS app for 5Calls.org. Requirements Xcode 10.2.1 iOS 10.2 Getting Started Install the dependencies: bun

5 Calls 129 Dec 25, 2022