{"id":16426258,"url":"https://github.com/chimehq/extendable","last_synced_at":"2026-03-05T10:31:58.872Z","repository":{"id":59470204,"uuid":"528863550","full_name":"ChimeHQ/Extendable","owner":"ChimeHQ","description":"A set of utilities for more pleasant work with ExtensionKit","archived":false,"fork":false,"pushed_at":"2024-07-09T13:54:01.000Z","size":56,"stargazers_count":47,"open_issues_count":0,"forks_count":7,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-16T04:14:18.224Z","etag":null,"topics":["extensionkit","extensions","ios","macos","swift","swiftui"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ChimeHQ.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","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":["mattmassicotte"]}},"created_at":"2022-08-25T13:28:32.000Z","updated_at":"2025-03-07T06:08:17.000Z","dependencies_parsed_at":"2024-04-04T22:23:18.256Z","dependency_job_id":"254f3f3f-6e1b-4af0-98ff-c84ec521d90f","html_url":"https://github.com/ChimeHQ/Extendable","commit_stats":{"total_commits":20,"total_committers":1,"mean_commits":20.0,"dds":0.0,"last_synced_commit":"61d425bef4db8d138c9f0464511a52bdf9744790"},"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChimeHQ%2FExtendable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChimeHQ%2FExtendable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChimeHQ%2FExtendable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChimeHQ%2FExtendable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ChimeHQ","download_url":"https://codeload.github.com/ChimeHQ/Extendable/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243908684,"owners_count":20367457,"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":["extensionkit","extensions","ios","macos","swift","swiftui"],"created_at":"2024-10-11T08:07:59.432Z","updated_at":"2026-03-05T10:31:58.832Z","avatar_url":"https://github.com/ChimeHQ.png","language":"Swift","funding_links":["https://github.com/sponsors/mattmassicotte"],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n[![Build Status][build status badge]][build status]\n[![Platforms][platforms badge]][platforms]\n[![Documentation][documentation badge]][documentation]\n[![Matrix][matrix badge]][matrix]\n\n\u003c/div\u003e\n\n# Extendable\nA set of utilities for more pleasant work with ExtensionKit\n\n## Installation\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/ChimeHQ/Extendable\", from: \"0.1.0\")\n],\ntargets: [\n    .target(\n        name: \"ExtensionSide\",\n        dependencies: [\"Extendable\"]\n    ),\n    .target(\n        name: \"HostSide\",\n        dependencies: [.product(name: \"ExtendableHost\", package: \"Extendable\")]\n    ),\n]\n```\n\n## Global Connection\n\nSetting up an ExtensionKit extension can be confusing, and requires a fair amount of boilerplate. `ConnectableExtension` makes it easier to manage the global host connection.\n\n```swift\n@main\nfinal class ExampleExtension: ConnectableExtension {\n    init() {\n    }\n\n    func acceptConnection(_ connection: NSXPCConnection) throws {\n        // configure your global connection and possibly\n        // store references to host interface objects\n    }\n}\n```\n\n## Scenes\n\nDealing with View-based extensions is even more complex. And, there isn't a clear way to get access to the host connection in your views. Extendable comes with a few components that make it easier to build scenes and manage view connections.\n\n### ConnectingAppExtensionScene\n\nThis is a `AppExtensionScene` that makes it easier to get access to the scene's connection within your `View`.\n\n```swift\nConnectingAppExtensionScene(sceneID: \"one\") { (sceneId, connection) in\n    try ConnectionView(sceneId: sceneId, connection: connection)\n}\n```\n\n### AppExtensionSceneGroup\n\nI expect this type won't be needed once Ventura ships. And, maybe it's just me, but I've been unable to figure out how to use `AppExtensionSceneBuilder` without a wrapper type. So here it is.\n\n### Example View\n\nYou can use `ConnectingAppExtensionScene` and `AppExtensionSceneGroup` independently, or as part of a more standard extension structure. But, if you want, you can also make use of the `ConnectableSceneExtension` protocol to really streamline your view class. Here's a full example:\n\n```swift\n@main\nfinal class ViewExtension: ConnectableSceneExtension {\n    init() {\n    }\n\n    func acceptConnection(_ connection: NSXPCConnection) throws {\n        // handle global connection\n    }\n    \n    var scene: some AppExtensionScene {\n        AppExtensionSceneGroup {\n            ConnectingAppExtensionScene(sceneID: \"one\") { (sceneId, connection) in\n                try ConnectionView(sceneId: sceneId, connection: connection)\n            }\n            ConnectingAppExtensionScene(sceneID: \"two\") { (sceneId, connection) in\n                try ConnectionView(sceneId: sceneId, connection: connection)\n            }\n        }\n    }\n}\n\nstruct ConnectionView: View {\n    let sceneName: String\n    let connection: NSXPCConnection?\n\n    init(sceneId: String, connection: NSXPCConnection?) throws {\n        self.sceneName = sceneId\n        self.connection = connection\n    }\n\n    var value: String {\n        return String(describing: connection)\n    }\n\n    var body: some View {\n        VStack {\n            Rectangle().frame(width: nil, height: 4).foregroundColor(.green)\n            Spacer()\n            Text(\"\\(sceneName): \\(value)\")\n            Spacer()\n            Rectangle().frame(width: nil, height: 4).foregroundColor(.red)\n        }\n    }\n}\n```\n\n## ExtendableHost\n\nExtendable also includes a second library called `ExtendableHost`.\n\nYou can its `AppExtensionBrowserView` and `ExtensionHostingView` to integrate the ExtensionKit view system with SwiftUI in your host application.\n\n```swift\n// very simple init extension to help with actor-isolation compatibility\nlet process = try await AppExtensionProcess(appExtensionIdentity: identity)\n```\n\n## Isolation and AppExtension\n\nCurrently, the `init` in the `AppExtention` protocol lacks any isolation. This makes it difficult to initialize instance variables if you are relying on the true-but-unexpressed `@MainActor` isolation of extensions. I've included a workaround that can help. SE-0414 will make this unecessary, as will ExtensionFoundation adding annotations. In the mean time though, it's nice to have no warnings.\n\n```swift\n@main\nfinal class MyExtension: AppExtension {\n    @InitializerTransferred private var value: MainActorType\n\n    nonisolated init() {\n        self._value = InitializerTransferred(mainActorProvider: {\n            MainActorType()\n        })\n    }\n}\n```\n\n## Contributing and Collaboration\n\nI would love to hear from you! Issues or pull requests work great. A [Matrix space][matrix] is also available for live help, but I have a strong bias towards answering in the form of documentation.\n\nI prefer collaboration, and would love to find ways to work together if you have a similar project.\n\nI prefer indentation with tabs for improved accessibility. But, I'd rather you use the system you want and make a PR than hesitate because of whitespace.\n\nBy participating in this project you agree to abide by the [Contributor Code of Conduct](CODE_OF_CONDUCT.md).\n\n[build status]: https://github.com/ChimeHQ/Extendable/actions\n[build status badge]: https://github.com/ChimeHQ/Extendable/workflows/CI/badge.svg\n[platforms]: https://swiftpackageindex.com/ChimeHQ/Extendable\n[platforms badge]: https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FChimeHQ%2FExtendable%2Fbadge%3Ftype%3Dplatforms\n[documentation]: https://swiftpackageindex.com/ChimeHQ/Extendable/main/documentation\n[documentation badge]: https://img.shields.io/badge/Documentation-DocC-blue\n[matrix]: https://matrix.to/#/%23chimehq%3Amatrix.org\n[matrix badge]: https://img.shields.io/matrix/chimehq%3Amatrix.org?label=Matrix\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchimehq%2Fextendable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchimehq%2Fextendable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchimehq%2Fextendable/lists"}