{"id":15288651,"url":"https://github.com/vinhnx/shift","last_synced_at":"2025-09-06T15:42:06.056Z","repository":{"id":40494870,"uuid":"322311404","full_name":"vinhnx/Shift","owner":"vinhnx","description":"Light-weight \u0026 concurrent EventKit wrapper","archived":false,"fork":false,"pushed_at":"2025-03-20T14:19:23.000Z","size":220,"stargazers_count":62,"open_issues_count":3,"forks_count":6,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-10T01:07:13.810Z","etag":null,"topics":["async-await","asynchronous","calendar","combine","concurrency","eventkit","ios","swift","swift-package-manager","swift5","swiftui","wrapper","xcode"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vinhnx.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"vinhnx","patreon":"vinhnx","open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"lfx_crowdfunding":null,"polar":null,"buy_me_a_coffee":null,"thanks_dev":null,"custom":null}},"created_at":"2020-12-17T13:58:17.000Z","updated_at":"2025-03-20T14:19:25.000Z","dependencies_parsed_at":"2023-11-08T07:24:27.600Z","dependency_job_id":null,"html_url":"https://github.com/vinhnx/Shift","commit_stats":{"total_commits":74,"total_committers":3,"mean_commits":"24.666666666666668","dds":"0.027027027027026973","last_synced_commit":"ba3909eb8c4f0702da71474145973843b4264879"},"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vinhnx%2FShift","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vinhnx%2FShift/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vinhnx%2FShift/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vinhnx%2FShift/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vinhnx","download_url":"https://codeload.github.com/vinhnx/Shift/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248137887,"owners_count":21053775,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async-await","asynchronous","calendar","combine","concurrency","eventkit","ios","swift","swift-package-manager","swift5","swiftui","wrapper","xcode"],"created_at":"2024-09-30T15:51:50.547Z","updated_at":"2025-04-10T01:07:18.485Z","avatar_url":"https://github.com/vinhnx.png","language":"Swift","funding_links":["https://github.com/sponsors/vinhnx","https://patreon.com/vinhnx"],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003eShift\u003c/h1\u003e\n\u003cp align=\"center\"\u003eLight-weight EventKit wrapper.\u003c/p\u003e\n\n---\n\n[![Swift](https://github.com/vinhnx/Shift/actions/workflows/ci.yml/badge.svg)](https://github.com/vinhnx/Shift/actions/workflows/ci.yml)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fvinhnx%2FShift%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/vinhnx/Shift)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fvinhnx%2FShift%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/vinhnx/Shift)\n\nShift is a light-weight concurrency wrapper for [EventKit](https://developer.apple.com/documentation/eventkit):\n+ Concurrency ready with `async`/`await`. (tag: `0.7.0`)\n+ Tranditional, [`Result`](https://developer.apple.com/documentation/swift/result) completion handler if preferred (tag: `0.6.0`)\n+ Thread-safe.\n+ SwiftUI supported.\n\nShift is currently being used by [Clendar](https://github.com/vinhnx/Clendar) app.\n\n### Requirement\n\n+ iOS 15.0 for async/await, tag `0.7.0`\n+ iOS 14.0 and below for Result-based, tag \u003c`0.6.0`\n+ Swift version 5.5\n+ Xcode 13.1\n\n### Install\n\nThis component is built using Swift Package Manager, it is pretty straight forward to use:\n\n1. In Xcode (11+), open your project and navigate to File \u003e Swift Packages \u003e Add Package Dependency...\n2. Paste the repository URL (https://github.com/vinhnx/Shift) and click Next.\n3. For Rules, select Version, in here, you can choose either:\n  + Async/await =\u003e tag `0.7.0`\n  + Result-based completion handler =\u003e tag `0.6.0`\n5. Click Finish to resolve package into your Xcode project.\n\n![Screen Shot 2021-08-15 at 11 28 54](https://user-images.githubusercontent.com/1097578/129467248-0ceac3c8-56f1-4a67-887f-538283121508.png)\n\n### Tag Version:\n\nConcurrency support is now ready, in tag [`0.7.0`](https://github.com/vinhnx/Shift/releases/tag/0.7.0)\n\nIn order to use old Result-based completion hanlders, please use tag [`0.6.0`](https://github.com/vinhnx/Shift/releases/tag/0.6.0).\n\n### Getting Started\n\n**First thing first**: \n\n+ Add Calendar usage description to your app's Info.plist to request for user's Calendars access.\n\n```\n\u003ckey\u003eNSCalendarsUsageDescription\u003c/key\u003e\n\t\u003cstring\u003e\u0026quot;$(PRODUCT_NAME) needs your permission to create events\u0026quot;\u003c/string\u003e\n```\n\n+ (Optional) configure own calendar name to request access to, preferrable in `AppDelegate`'s `didFinishLaunchingWithOptions` (Swift) or `App`'s `init()` (SwiftUI):\n\nSwift AppDelegate:\n\n```swift\nimport UIKit\n\n@UIApplicationMain\nclass AppDelegate: UIResponder, UIApplicationDelegate {\n    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -\u003e Bool {\n        // Override point for customization after application launch.\n        Shift.configureWithAppName(\"MyApp\")\n        return true\n    }\n}\n```\n\nin SwiftUI App, first `import Shift`, then configure your app's name to differntiate the name of your app's calendar in system's EventKit.\n\n```swift\nimport SwiftUI\nimport Shift\n\n@main\nstruct MyApp: App {\n    init() {\n        Shift.configureWithAppName(\"MyApp\")\n    }\n\n    var body: some Scene {\n        WindowGroup {\n            ContentView()\n        }\n    }\n}\n```\n\n---\n\n### A quick NOTE about concurrency \n\nSince `async` functions can only be called on concurrency context, if you call `async` function inside synchornouse context, Xcode will throws an error:\n\n\u003cimg width=\"742\" alt=\"Screen Shot 2021-11-29 at 11 35 39\" src=\"https://user-images.githubusercontent.com/1097578/143809680-19252608-d1e5-4754-995d-67bcb5df144d.png\"\u003e\n\nSo, there are two ways to `await`ing for concurrency result, base on context:\n+ Inside `async` function\n```swift\nfunc doSomethingAsync() async {\n    // ... other works\n    let events = try? await Shift.shared.fetchEvents(for: Date())\n    // ... other works\n}\n```\n\n+ Inside `Task` closure:\n```swift\nfunc regularFunction() {\n    // ... other works\n\n    Task {\n        let events = try? await Shift.shared.fetchEvents(for: Date())\n        // then...\n    }\n\n    // ... other works\n}\n```\n\nEither is fine, base on caller's context. \n\n\u003e You can read more about Task here https://developer.apple.com/documentation/swift/task.\n\nIn SwiftUI views, you can call `async` functions inside View' `.task` modifier:\n```swift\nimport EventKit\nimport SwiftUI\nimport Shift\n\nstruct ContentView: View {\n    @StateObject var eventKitWrapper = Shift.shared\n    @State private var selectedEvent: EKEvent?\n\n    var body: some View {\n        LazyVStack(alignment: .leading, spacing: 10) {\n            ForEach(eventKitWrapper.events, id: \\.self) { event in\n                Text(event: event)\n            }\n        }\n        .padding()\n        .task { // wrap async call inside .task modifier\n            try? await eventKitWrapper.fetchEventsForToday() \n        }\n    }\n}\n```\n\n\u003e You can read more about SwiftUI's `.task` modifier here https://developer.apple.com/documentation/swiftui/view/task(priority:_:).\n\n---\n\n### Usage Example\n\nFetch list of events for a particular date:\n\n#### async/await (NEW)\n\ninside regular async function:\n```swift\nfunc fetchEvents() async {\n    do {\n        let events = try await Shift.shared.fetchEvents(for: Date()) // await for events fetching\n    } catch {\n        print(error) // handle error\n    }\n}\n```\n\nor standalone:\n```swift\nTask {\n    let events = try? await Shift.shared.fetchEvents(for: Date()) // await for events fetching\n}\n```\n\n#### Result-based completion handlers (old pattern, but still doable if you preferred this to async/await)\n\n```swift\nShift.shared.fetchEvents(for: Date()) { result in\n    switch result {\n    case let .success(events): print(events) // got events\n    case let .failure(error): print(error) // handle error\n    }\n}\n```\n\n```swift\nShift.shared.fetchEventsRangeUntilEndOfDay(from: Date()) { result in\n    switch result {\n    case let .success(events): print(events) // got events\n    case let .failure(error): print(error) // handle error\n    }\n}\n```\n\n---\n\nCreate Event:\n\n#### async/await\n\ninside regular async function:\n```swift\nfunc myAsyncFunction() async {\n    try? await Shift.shared.createEvent(\"Be happy!\", startDate: startTime, endDate: endTime)\n}\n```\n\nor standalone:\n```swift\nTask {\n    try? await Shift.shared.createEvent(\"Be happy!\", startDate: startTime, endDate: endTime)\n}\n```\n\n#### Result\n\n```swift\nShift.shared.createEvent(\"Be happy!\", startDate: startTime, endDate: endTime) { result in\n    switch result {\n    case let .success(event): print(event) // created event\n    case let .failure(error): print(error) // handle error\n    }\n}\n```\n\n---\n\nDelete event:\n\n#### async/await\n\ninside regular async function:\n```swift\nfunc myAsyncFunction() async {\n    try? await Shift.shared.deleteEvent(identifier: eventID)\n}\n```\n\nor standalone:\n```swift\nTask {\n    try? await Shift.shared.deleteEvent(identifier: eventID)\n}\n```\n\n### Result\n\n```swift\nShift.shared.deleteEvent(identifier: eventID) { result in\n    switch result {\n    case let .success: print(\"done!\") // deleted event\n    case let .failure(error): print(error) // handle error\n    }\n}\n```\n\n---\n\n## SwiftUI Example\n\nShift is conformed `ObservableObject` with an `@Published` `events` property, so it's straight-forward to use in SwiftUI binding mechanism.\n\n#### Result-based example:\n\n```swift\nimport EventKit\nimport SwiftUI\nimport Shift\n\nstruct ContentView: View {\n    @StateObject var eventKitWrapper = Shift.shared\n    @State private var selectedEvent: EKEvent?\n\n    var body: some View {\n        LazyVStack(alignment: .leading, spacing: 10) {\n            ForEach(eventKitWrapper.events, id: \\.self) { event in\n                Text(event: event)\n            }\n        }\n        .padding()\n        .onAppear {\n            eventKitWrapper.fetchEventsForToday()\n        }\n    }\n}\n```\n\n#### async/await example:\n\n```swift\nimport EventKit\nimport SwiftUI\nimport Shift\n\nstruct ContentView: View {\n    @StateObject var eventKitWrapper = Shift.shared\n    @State private var selectedEvent: EKEvent?\n\n    var body: some View {\n        LazyVStack(alignment: .leading, spacing: 10) {\n            ForEach(eventKitWrapper.events, id: \\.self) { event in\n                Text(event: event)\n            }\n        }\n        .padding()\n        .task {\n            try? await eventKitWrapper.fetchEventsForToday() \n        }\n    }\n}\n```\n\n---\n\n### Apps currently using Shift\n\n+ [Clendar](https://github.com/vinhnx/Clendar) - Clendar - universal calendar app. Written in SwiftUI. Available on App Store. MIT License.\n\n([add yours here](https://github.com/vinhnx/Shift/pulls))\n\n---\n\n### Help, feedback or suggestions?\n\nFeel free to [open an issue](https://github.com/vinhnx/Shift/issues) or contact me on [Twitter](https://twitter.com/@vinhnx) for discussions, news \u0026 announcements \u0026 other projects. 🚀\n\nI hope you like it! :)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvinhnx%2Fshift","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvinhnx%2Fshift","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvinhnx%2Fshift/lists"}