SwiftUIWindowBinder supports getting SwiftUI access to a host Window object (UIWindow or NSWindow) with zero set up. SwiftUI apps without an application delegate 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.
Installation
To use SwiftUIWindowBinder within your project see how to reference package using the Swift Package Manager or in Xcode, using this repository’s GitHub link. Once installed you can import SwiftUIWindowBinder as appropriate.
Adding to Package.swift
To add manually in the Package.swift use the following reference in your dependencies section:
Documentation 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.
This package does come with ample documentation (I hope), through a set of Swift Playgrounds pages in the package itself. Walk through the documentation, run the code, and definitely read up about the Gotchas!
The playgrounds examples are still in draft. They are all runnable, just watch out for some typos.
In good conscience I can’t have no code on a README, so look out below.
Playgrounds
To run the SwiftIWindowBinder Playground examples you will need to open the package in Xcode and run any of the playgrounds under Playgrounds.
Be 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.
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.
Examples
There 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 ;).
WindowBinder
WindowBinder is at the core of capturing a Window in your SwiftUI View. As it name implies it uses a Binding parameter to bind a Window to a @State property of the view (Window is a platform abstraction type alias for UIWindow or NSWindow).
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.
The following show the use of WindowBinder , binding to self.$window, followed by the trailing closure for the content views. The closure contains a Text view with the onTapGesture view modifier using the bound window property.
import SwiftUI
import SwiftUIWindowBinder
struct ContentView : View {
/// You will need a `@State` property in your view for the binding
@State var window: Window?
/// View body
var body: some View {
// Create a WindowBinder and bind it to the state property `window`
WindowBinder(window: $window) {
Text("Hello")
.padding()
.onTapGesture {
// `self.window` will be nil initially, until (this) View's actual view is in the
// hosted window hierarchy
guard let window = window else {
return
}
// Print the window description
print(window.description)
}
}
}
}
There 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.
Buttons are probably where window related actions may be used most. For convenience WindowButton wraps the logic of WindowBinder and provides the action: closure to with a platform dependent Window when interacted with.
Modifying the example above we get a much simpler looking ContentView:
import SwiftUI
import SwiftUIWindowBinder
struct ContentView : View {
/// View body
var body: some View {
// Our button that receives a `Window` when interacted with
WindowButton { window in
// Print the window description
print(window.description)
} label: {
Text("Hello")
}
// Just like Button, WindowButton can be styled just the same
.buttonStyle(DefaultButtonStyle())
}
}
Unlike 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.
Wait, There WindowButton But No Event View Modifiers?
Correct! For good reasons. Checkout the Wrapping Up for those reasons and if you really, really want it, a code example.
Enjoy!
SwiftUI is evolving, getting a ton of new features each release. This package represents a bit of a polyfill assistance (hello JavaScript + Babel nomenclature) until the time when we have official wrappers for the things we want.
I 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.
Of course, bugs, issues, pull requests, corrections, suggestions, or comments fire away…
SwiftUIWindowBinder
Overview
SwiftUIWindowBinder supports getting SwiftUI access to a host Window object (UIWindow or NSWindow) with zero set up. SwiftUI apps without an application delegate 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.
Installation
To use SwiftUIWindowBinder within your project see how to reference package using the Swift Package Manager or in Xcode, using this repository’s GitHub link. Once installed you can import SwiftUIWindowBinder as appropriate.
Adding to Package.swift
To add manually in the
Package.swift
use the following reference in yourdependencies
section:Usage & Examples
Documentation 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.
This package does come with ample documentation (I hope), through a set of Swift Playgrounds pages in the package itself. Walk through the documentation, run the code, and definitely read up about the Gotchas!
In good conscience I can’t have no code on a README, so look out below.
Playgrounds
To run the SwiftIWindowBinder Playground examples you will need to open the package in Xcode and run any of the playgrounds under
Playgrounds
.Be 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.
Examples
There are only two real examples of demonstrate here. Using something called
WindowBinder
and a convenience calledWindowButton
. As the playground Wrapping Up documentation eludes to, there could be more done here (such as supporting event view modifiers likeonTapGesture
), but was chosen to avoid. If you want to know more, read through the Playgrounds ;).WindowBinder
WindowBinder
is at the core of capturing aWindow
in your SwiftUIView
. As it name implies it uses aBinding
parameter to bind aWindow
to a@State
property of the view (Window
is a platform abstraction type alias forUIWindow
orNSWindow
).The following show the use of
WindowBinder
, binding toself.$window
, followed by the trailing closure for the content views. The closure contains a Text view with theonTapGesture
view modifier using the boundwindow
property.There 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.WindowButton
Buttons are probably where window related actions may be used most. For convenience
WindowButton
wraps the logic ofWindowBinder
and provides theaction:
closure to with a platform dependentWindow
when interacted with.Modifying the example above we get a much simpler looking ContentView:
Unlike the
WindowBinder
example there is no guard forwindow
. This is because in the case there is no host window the buttonaction:
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.Wait, There WindowButton But No Event View Modifiers?
Correct! For good reasons. Checkout the Wrapping Up for those reasons and if you really, really want it, a code example.
Enjoy!
SwiftUI is evolving, getting a ton of new features each release. This package represents a bit of a polyfill assistance (hello JavaScript + Babel nomenclature) until the time when we have official wrappers for the things we want.
I 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.
Of course, bugs, issues, pull requests, corrections, suggestions, or comments fire away…