Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to access own window within SwiftUI view?

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?

like image 774
Asperi Avatar asked Feb 23 '20 06:02

Asperi


People also ask

How do I open windows in Swift?

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.

What is SwiftUI window group?

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.

What is Uiwindow?

The backdrop for your app's user interface and the object that dispatches events to your views.

How to configure a SwiftUI view?

Overall, there are three different ways to configure a SwiftUI view — by passing arguments to its initializer, using modifiers, and through its surrounding environment.

How do I open a new window in SwiftUI on macOS?

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).

How is SwiftUI different from Apple’s previous UI frameworks?

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.

What is direct and indirect configuration in SwiftUI?

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.


2 Answers

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

  1. Define Environment key. Note, it needs to remember to avoid reference cycling on kept window
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
        }
    }
}
  1. Inject hosting window in root ContentView in place of window creation (either in AppDelegate or in SceneDelegate, just once
// 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
  1. use only where needed, just by declaring environment variable
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

like image 118
Asperi Avatar answered Nov 09 '22 18:11

Asperi


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.
}
like image 43
Edward Brey Avatar answered Nov 09 '22 19:11

Edward Brey