The goal is to have easy access to hosting window at any level of SwiftUI view hierarchy. The purpose might be different - close the window, resign first responder, replace root view or contentViewController. Integration with UIKit/AppKit also sometimes require path via window, so…
What I met here and tried before,
something like this
let keyWindow = shared.connectedScenes
.filter({$0.activationState == .foregroundActive})
.map({$0 as? UIWindowScene})
.compactMap({$0})
.first?.windows
.filter({$0.isKeyWindow}).first
or via added in every SwiftUI view UIViewRepresentable/NSViewRepresentable to get the window using view.window
looks ugly, heavy, and not usable.
Thus, how would I do that?
Step 1: Write a basic program in Swift with your favorite editor. Step 2: Open "Swift for Windows 1.6" and click 'Select File' to choose your file. Step 3: Click 'Compile' to compile your program. Step 4: Click 'Run' to run on Windows.
The default implementation of a WindowGroup allows multiple instances of the window to be created (either using ⌘N , or the "Show Tab Bar" command). Each instance of a window created from a window group contains the same SwiftUI hierarchy, but maintains an independent state.
The backdrop for your app's user interface and the object that dispatches events to your views.
Overall, there are three different ways to configure a SwiftUI view — by passing arguments to its initializer, using modifiers, and through its surrounding environment.
Here is how to open a new window in SwiftUI on macOS. Text ( "Hello, world!") In your App add another WindowGroup for your viewer and set it to enable handling of external launch events (an internal event in our case).
One of the key ways that SwiftUI is different compared to Apple’s previous UI frameworks is how its views are created and configured. In fact, it could be argued that when using SwiftUI, we never actually create any views at all — instead we simply describe what we want our UI to look like, and then the system takes care of the actual rendering.
The above is an example of direct configuration, as we’re explicitly setting up and modifying our Text view by directly calling methods on it. However, SwiftUI also supports indirect configuration, as many different modifiers and properties are automatically propagated down through each given view hierarchy.
Here is the result of my experiments that looks appropriate for me, so one might find it helpful as well. Tested with Xcode 11.2 / iOS 13.2 / macOS 15.0
The idea is to use native SwiftUI Environment concept, because once injected environment value becomes available for entire view hierarchy automatically. So
struct HostingWindowKey: EnvironmentKey {
#if canImport(UIKit)
typealias WrappedValue = UIWindow
#elseif canImport(AppKit)
typealias WrappedValue = NSWindow
#else
#error("Unsupported platform")
#endif
typealias Value = () -> WrappedValue? // needed for weak link
static let defaultValue: Self.Value = { nil }
}
extension EnvironmentValues {
var hostingWindow: HostingWindowKey.Value {
get {
return self[HostingWindowKey.self]
}
set {
self[HostingWindowKey.self] = newValue
}
}
}
// window created here
let contentView = ContentView()
.environment(\.hostingWindow, { [weak window] in
return window })
#if canImport(UIKit)
window.rootViewController = UIHostingController(rootView: contentView)
#elseif canImport(AppKit)
window.contentView = NSHostingView(rootView: contentView)
#else
#error("Unsupported platform")
#endif
struct ContentView: View {
@Environment(\.hostingWindow) var hostingWindow
var body: some View {
VStack {
Button("Action") {
// self.hostingWindow()?.close() // macOS
// self.hostingWindow()?.makeFirstResponder(nil) // macOS
// self.hostingWindow()?.resignFirstResponder() // iOS
// self.hostingWindow()?.rootViewController?.present(UIKitController(), animating: true)
}
}
}
}
backup
Add the window as a property in an environment object. This can be an existing object that you use for other app-wide data.
final class AppData: ObservableObject {
let window: UIWindow? // Will be nil in SwiftUI previewers
init(window: UIWindow? = nil) {
self.window = window
}
}
Set the property when you create the environment object. Add the object to the view at the base of your view hierarchy, such as the root view.
let window = UIWindow(windowScene: windowScene) // Or however you initially get the window
let rootView = RootView().environmentObject(AppData(window: window))
Finally, use the window in your view.
struct MyView: View {
@EnvironmentObject private var appData: AppData
// Use appData.window in your view's body.
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With