{"id":24568156,"url":"https://github.com/happycodelucky/swiftuiwindowbinder","last_synced_at":"2025-04-22T15:24:58.319Z","repository":{"id":63911774,"uuid":"305267592","full_name":"happycodelucky/SwiftUIWindowBinder","owner":"happycodelucky","description":"Create SwiftUI Views able to access host windows from UIKit (iOS \u0026 tvOS) or AppKit (macOS), with zero set up. Works for existing apps, apps with @main/App, and even Playgrounds","archived":false,"fork":false,"pushed_at":"2020-10-25T16:27:45.000Z","size":72,"stargazers_count":39,"open_issues_count":0,"forks_count":6,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-24T23:33:33.340Z","etag":null,"topics":["appkit","interop","playgrounds","swift","swiftui","uikit","window"],"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/happycodelucky.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-10-19T05:00:38.000Z","updated_at":"2024-08-15T01:06:17.000Z","dependencies_parsed_at":"2023-01-14T13:16:02.895Z","dependency_job_id":null,"html_url":"https://github.com/happycodelucky/SwiftUIWindowBinder","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/happycodelucky%2FSwiftUIWindowBinder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/happycodelucky%2FSwiftUIWindowBinder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/happycodelucky%2FSwiftUIWindowBinder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/happycodelucky%2FSwiftUIWindowBinder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/happycodelucky","download_url":"https://codeload.github.com/happycodelucky/SwiftUIWindowBinder/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250265825,"owners_count":21402179,"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":["appkit","interop","playgrounds","swift","swiftui","uikit","window"],"created_at":"2025-01-23T14:19:57.340Z","updated_at":"2025-04-22T15:24:58.297Z","avatar_url":"https://github.com/happycodelucky.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SwiftUIWindowBinder\n\n![Swift Version](https://img.shields.io/badge/swift-5.3-blue.svg?style=for-the-badge)\n![iOS Version](https://img.shields.io/badge/iOS-13.0-green.svg?style=for-the-badge)\n![macOS Version](https://img.shields.io/badge/macOS-10.15-green.svg?style=for-the-badge)\n![tvOS Version](https://img.shields.io/badge/tvOS-13-green.svg?style=for-the-badge)\n![watchOS Version](https://img.shields.io/badge/watchOS-UNSUPPORTED-red.svg?style=for-the-badge)\n[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=for-the-badge)](.//LICENSE)\n[![Maintained](https://img.shields.io/badge/Maintained%3F-yes-green.svg?style=for-the-badge)](https://github.com/happycodelucky/SwiftUIWindowBinder/graphs/commit-activity)\n![Release](https://img.shields.io/github/v/release/happycodelucky/SwiftUIWindowBinder.svg?style=for-the-badge)\n\n# Overview\n\n**SwiftUIWindowBinder** supports getting SwiftUI access to a host Window object ([UIWindow](https://developer.apple.com/documentation/uikit/uiwindow) or [NSWindow](https://developer.apple.com/documentation/appkit/nswindow)) with *zero set up*. SwiftUI apps [without an application delegate](https://developer.apple.com/documentation/swiftui/app) or scene delegate can still access the Window, and the window is scoped to each document in a multi-window application. Playgrounds are also supported here.\n\n# Installation\nTo use **SwiftUIWindowBinder** within your project see how to reference package using the [Swift Package Manager](https://swift.org/package-manager/) or in [Xcode](https://developer.apple.com/videos/play/wwdc2019/408/), using this repository's GitHub link. Once installed you can import **SwiftUIWindowBinder** as appropriate.\n\n## Adding to Package.swift\nTo add manually in the `Package.swift` use the following reference in your `dependencies` section:\n\n```\ndependencies: [\n    .package(\n        name: \"SwiftUIWindowBinder\".\n        url: \"https://github.com/happycodelucky/SwiftUIWindowBinder.git\", \n        .upToNextMajor(from: \"1.0.0\"),\n],\n\n...\n```\n\n# Usage \u0026 Examples\n\nDocumentation here on the README.md is light. There's not a whole lot to the package, you are encouraged to explore the code and offer fixes/comments on better ways, or additions you might like.\n\nThis package does come with ample documentation (I hope), through a set of [Swift Playgrounds](https://developer.apple.com/documentation/swift_playgrounds) pages in the package itself. Walk through the documentation, run the code, and definitely *read up about the Gotchas*! \n\n\u003e The playgrounds examples are still in draft. They are all runnable, just watch out for some typos.\n\nIn good conscience I can't have no code on a README, so look out below.\n\n## Playgrounds\n\nTo run the [SwiftIWindowBinder Playground](https://github.com/happycodelucky/SwiftUIWindowBindableView/tree/main/Playgrounds/WindowBinderPlayground.playground) examples you will need to open the package in Xcode and run any of the playgrounds under `Playgrounds`. \n\nBe sure to have the options '**Render Documentation**' and '**Build Active Schema**' enabled (they are by default) for the best representation, as the Playgrounds serve as working documentation.\n\n\u003e Although the package supports iOS 13, macOS 10.15, and tvOS 13, you will need to use Xcode 12.2 to run the playgrounds. **If you are on Catalina (macOS 10.15) then the 'macOS' target for the playground will not run** due to requiring Big Sur (macOS 11) to run some of the SwiftUI code.\n\n## Examples \n\nThere are only two real examples of demonstrate here. Using something called `WindowBinder` and a convenience called `WindowButton`. As the playground Wrapping Up documentation eludes to, there could be more done here (such as supporting event view modifiers like `onTapGesture`), but was chosen to avoid. If you want to know more, read through the Playgrounds ;).\n\n### WindowBinder\n\n[`WindowBinder`](Sources/SwiftUIWindowBinder/WindowBinder.swift) is at the core of capturing a `Window` in your SwiftUI `View`. As it name implies it uses a [`Binding`](https://developer.apple.com/documentation/swiftui/binding) parameter to bind a `Window` to a `@State` property of the view (`Window` is a platform abstraction type alias for `UIWindow` or `NSWindow`).\n\n\u003e A `WindowBinder` is a view injected into the *actual* view hierarchy in UIKit or AppKit, able to tap into the hosted `UIWindow` or `NSWindow` respectively.\n\nThe following show the use of `WindowBinder` , binding to `self.$window`, followed by the trailing closure for the content views. The closure contains a [Text](https://developer.apple.com/documentation/swiftui/text) view with the `onTapGesture` view modifier using the bound `window` property.\n\n```swift\nimport SwiftUI\nimport SwiftUIWindowBinder\n\nstruct ContentView : View {\n    /// You will need a `@State` property in your view for the binding\n    @State var window: Window?\n\n    /// View body\n    var body: some View {\n        // Create a WindowBinder and bind it to the state property `window`\n        WindowBinder(window: $window) {\n          \n            Text(\"Hello\")\n                .padding()\n                .onTapGesture {\n                    // `self.window` will be nil initially, until (this) View's actual view is in the\n                    // hosted window hierarchy\n                    guard let window = window else {\n                        return\n                    }\n\n                    // Print the window description\n                    print(window.description)\n                }\n          \n        }\n    }\n}\n```\n\nThere is no requirement your view be authored in this way. `WindowBinder` is not required to be a root view, or even contain any of your view element, it just needs to be in your view. Below is an acceptable alternative. The content closure is a convenience to avoid the need for a stack.\n\n```swift\nstruct ContentView : View {\n    @State var window: Window?\n\n    var body: some View {\n        ZStack(alignment: .center, content: {\n            WindowBinder(window: $window) { /* Nothing */ }\n          \n            Text(\"Hello\")\n                .padding()\n                .onTapGesture { /* ... */}\n        }\n    }\n}\n```\n\n### WindowButton\n\nButtons are probably where window related actions may be used most. For convenience [`WindowButton`](Sources/SwiftUIWindowBinder/WindowButton.swift) wraps the logic of `WindowBinder` and provides the `action:` closure to with a platform dependent `Window` when interacted with.\n\nModifying the example above we get a much simpler looking ContentView:\n```swift\nimport SwiftUI\nimport SwiftUIWindowBinder\n\nstruct ContentView : View {\n    /// View body\n    var body: some View {\n      \n        // Our button that receives a `Window` when interacted with\n        WindowButton { window in\n            // Print the window description\n            print(window.description)\n        } label: {\n            Text(\"Hello\")\n        }\n        // Just like Button, WindowButton can be styled just the same\n        .buttonStyle(DefaultButtonStyle())\n      \n    }\n}\n```\n\nUnlike the `WindowBinder` example there is no guard for `window`. This is because in the case there is no host window the button `action:` closure will not be called. Given the architecture of SwiftUI/UIKit/AppKit you would need to be doing something out of the ordinary to interact with a view that's not in a host window's view hierarchy. \n\n## Wait, There WindowButton But No Event View Modifiers?\n\nCorrect! For good reasons. Checkout the [Wrapping Up](Playgrounds/WindowBinderPlayground.playground/Pages/WrappingUp.xcplaygroundpage/Contents.swift) for those reasons and if you really, really want it, a code example.\n\n # Enjoy!\n\nSwiftUI is evolving, getting a ton of new features each release. This package represents a bit of a [polyfill](https://en.wikipedia.org/wiki/Polyfill_(programming)) assistance (hello JavaScript + Babel nomenclature) until the time when we have official wrappers for the things we want.\n\nI don't see UIKit or AppKit disappearing from beneath SwiftUI anytime soon (likely never), and a few of us will continue to find a need for such things like this package.\n\nOf course, bugs, issues, pull requests, corrections, suggestions, or comments fire away...\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhappycodelucky%2Fswiftuiwindowbinder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhappycodelucky%2Fswiftuiwindowbinder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhappycodelucky%2Fswiftuiwindowbinder/lists"}