Skip to content

Repository files navigation

Shift

Light-weight EventKit wrapper.


Swift

Shift is a light-weight concurrency wrapper for EventKit:

  • Concurrency ready with async/await. (tag: 0.7.0)
  • Tranditional, Result completion handler if preferred (tag: 0.6.0)
  • Thread-safe.
  • SwiftUI supported.

Shift is currently being used by Clendar app.

Requirement

  • iOS 15.0 for async/await, tag 0.7.0
  • iOS 14.0 and below for Result-based, tag <0.6.0
  • Swift version 5.5
  • Xcode 13.1

Install

This component is built using Swift Package Manager, it is pretty straight forward to use:

  1. In Xcode (11+), open your project and navigate to File > Swift Packages > Add Package Dependency...
  2. Paste the repository URL (https://github.com/vinhnx/Shift) and click Next.
  3. For Rules, select Version, in here, you can choose either:
  • Async/await => tag 0.7.0
  • Result-based completion handler => tag 0.6.0
  1. Click Finish to resolve package into your Xcode project.

Screen Shot 2021-08-15 at 11 28 54

Tag Version:

Concurrency support is now ready, in tag 0.7.0

In order to use old Result-based completion hanlders, please use tag 0.6.0.

Getting Started

First thing first:

  • Add Calendar usage description to your app's Info.plist to request for user's Calendars access.
<key>NSCalendarsUsageDescription</key>
<string>&quot;$(PRODUCT_NAME) needs your permission to create events&quot;</string>
  • (Optional) configure own calendar name to request access to, preferrable in AppDelegate's didFinishLaunchingWithOptions (Swift) or App's init() (SwiftUI):

Swift AppDelegate:

import UIKit
@UIApplicationMainclassAppDelegate:UIResponder,UIApplicationDelegate{func application(_ application:UIApplication, didFinishLaunchingWithOptions launchOptions:[UIApplication.LaunchOptionsKey:Any]?)->Bool{
// Override point for customization after application launch.
Shift.configureWithAppName("MyApp")returntrue}}

in SwiftUI App, first import Shift, then configure your app's name to differntiate the name of your app's calendar in system's EventKit.

import SwiftUI
import Shift
@mainstructMyApp:App{init(){Shift.configureWithAppName("MyApp")}varbody:someScene{WindowGroup{ContentView()}}}

A quick NOTE about concurrency

Since async functions can only be called on concurrency context, if you call async function inside synchornouse context, Xcode will throws an error:

Screen Shot 2021-11-29 at 11 35 39

So, there are two ways to awaiting for concurrency result, base on context:

  • Inside async function
func doSomethingAsync()async{
// ... other works
letevents=try?awaitShift.shared.fetchEvents(for:Date())
// ... other works
}
  • Inside Task closure:
func regularFunction(){
// ... other works
Task{letevents=try?awaitShift.shared.fetchEvents(for:Date())
// then...
}
// ... other works
}

Either is fine, base on caller's context.

You can read more about Task here https://developer.apple.com/documentation/swift/task.

In SwiftUI views, you can call async functions inside View' .task modifier:

import EventKit
import SwiftUI
import Shift
structContentView:View{@StateObjectvareventKitWrapper=Shift.shared
@StateprivatevarselectedEvent:EKEvent?varbody:someView{LazyVStack(alignment:.leading, spacing:10){ForEach(eventKitWrapper.events, id: \.self){ event inText(event: event)}}.padding().task{ // wrap async call inside .task modifier
try?await eventKitWrapper.fetchEventsForToday()}}}

You can read more about SwiftUI's .task modifier here https://developer.apple.com/documentation/swiftui/view/task(priority:_:).


Usage Example

Fetch list of events for a particular date:

async/await (NEW)

inside regular async function:

func fetchEvents()async{do{letevents=tryawaitShift.shared.fetchEvents(for:Date()) // await for events fetching
}catch{print(error) // handle error
}}

or standalone:

Task{letevents=try?awaitShift.shared.fetchEvents(for:Date()) // await for events fetching
}

Result-based completion handlers (old pattern, but still doable if you preferred this to async/await)

Shift.shared.fetchEvents(for:Date()){ result inswitch result {caselet.success(events):print(events) // got events
caselet.failure(error):print(error) // handle error
}}
Shift.shared.fetchEventsRangeUntilEndOfDay(from:Date()){ result inswitch result {caselet.success(events):print(events) // got events
caselet.failure(error):print(error) // handle error
}}

Create Event:

async/await

inside regular async function:

func myAsyncFunction()async{try?awaitShift.shared.createEvent("Be happy!", startDate: startTime, endDate: endTime)}

or standalone:

Task{try?awaitShift.shared.createEvent("Be happy!", startDate: startTime, endDate: endTime)}

Result

Shift.shared.createEvent("Be happy!", startDate: startTime, endDate: endTime){ result inswitch result {caselet.success(event):print(event) // created event
caselet.failure(error):print(error) // handle error
}}

Delete event:

async/await

inside regular async function:

func myAsyncFunction()async{try?awaitShift.shared.deleteEvent(identifier: eventID)}

or standalone:

Task{try?awaitShift.shared.deleteEvent(identifier: eventID)}

Result

Shift.shared.deleteEvent(identifier: eventID){ result inswitch result {caselet.success:print("done!") // deleted event
caselet.failure(error):print(error) // handle error
}}

SwiftUI Example

Shift is conformed ObservableObject with an @Publishedevents property, so it's straight-forward to use in SwiftUI binding mechanism.

Result-based example:

import EventKit
import SwiftUI
import Shift
structContentView:View{@StateObjectvareventKitWrapper=Shift.shared
@StateprivatevarselectedEvent:EKEvent?varbody:someView{LazyVStack(alignment:.leading, spacing:10){ForEach(eventKitWrapper.events, id: \.self){ event inText(event: event)}}.padding().onAppear{
eventKitWrapper.fetchEventsForToday()}}}

async/await example:

import EventKit
import SwiftUI
import Shift
structContentView:View{@StateObjectvareventKitWrapper=Shift.shared
@StateprivatevarselectedEvent:EKEvent?varbody:someView{LazyVStack(alignment:.leading, spacing:10){ForEach(eventKitWrapper.events, id: \.self){ event inText(event: event)}}.padding().task{try?await eventKitWrapper.fetchEventsForToday()}}}

Apps currently using Shift

  • Clendar - Clendar - universal calendar app. Written in SwiftUI. Available on App Store. MIT License.

(add yours here)


Help, feedback or suggestions?

Feel free to open an issue or contact me on Twitter for discussions, news & announcements & other projects. 🚀

I hope you like it! :)

Releases

Sponsor this project

Packages

Used by

Contributors

Languages