DoTryCatch
DoTryCatch

Reputation: 1082

How to pass a variable to a new window (WindowGroup)?

I'm trying to use the new WindowGroup to display a more complex view in a new window but somehow I can't figure out how to pass values into the view.
Until yet I was playing around with NSWindow but there I can't use the new toolbar with .toolbar{} and I'm somehow getting weird errors when using the latest swiftUI features.

In my old code I just could pass my values into the new view like usual:

.simultaneousGesture(

    TapGesture(count: 2)
    .onEnded {
        var window: NSWindow!

        if nil == window {
        
            // parse my struct content(name: "XServe-Test", configFile: "/FileUrl", permissions: .rootPermissions, cluster: cluster(x12, CPUMax: .cores(28), ramMax: .gb(1200)))
            let serverView = serverView(content: content)

            window = NSWindow(
                contentRect: NSRect(x: 20, y: 20, width: 580, height: 400),
                styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
                backing: .buffered,
                defer: false
            )

            window.center()
            window.setFrameAutosaveName("ServerMainView")
            window.isReleasedWhenClosed = false
            window.title = content.name
            window.contentView = NSHostingView(rootView: serverView)
            window.toolbar = NSToolbar()
            window.toolbarStyle = .unifiedCompact
        }

        window.makeKeyAndOrderFront(nil)
    }
)

Now I'm using in the app file:

import SwiftUI

@main
struct myApp: App {
    
    var body: some Scene {

        WindowGroup {
            contentView()
        }
        .windowStyle(.hiddenTitleBar)
        .commands {
            SidebarCommands()
            ToolbarCommands()
        }

        // the view that should be displayed in a new window 
        WindowGroup("serverView") {
            let inputContent : content = content(name: "XServe-Test", configFile: "/FileUrl", permissions: .rootPermissions, cluster: cluster(x12, CPUMax: .cores(28), ramMax: .gb(1200))) 
            serverView(content: inputContent) // this is static now :(
        }
        .handlesExternalEvents(matching: Set(arrayLiteral: "serverView"))
}

and the following code to open the view:

.simultaneousGesture(

    TapGesture(count: 2)
    .onEnded {
        guard let url = URL(string: "com-code-myApp://serverView") else { return }
        NSWorkspace.shared.open(url)
    }
)

How do I pass the input from the tap gesture into the new view using the WindowGroup logic?

Upvotes: 4

Views: 3201

Answers (2)

Alejandro
Alejandro

Reputation: 65

I found this example at Apple Developer

First, You need Codable & Hashable struct for your data. For example, If I would like to pass an url when opening a view:

struct File: Codable, Hashable {
    var id = UUID()
    let path: URL
}

Now we can pass this struct between windows.

@main
struct myApp: App {
     
    var body: some Scene {
        // Default View
        WindowGroup {
            ContentView()
        }
        
        // Open FileView() with given data.
        WindowGroup(id: "file.view", for: File.self) { $file in
            FileView(file: $file)
        } default: {  // fallback
            File(path: URL(string: ""))
        }
    }
}

FileView should look like below:

struct FileView: View {
    @Binding var file: File

    var body: some View{
        Text(file.path.absoluteString)
    }
}

You can open FileView() everywhere. This is an example to open FileView() with data in ContentView()

struct ContentView: View {
    @Environment(\.openWindow) private var openWindow
    @Environment(\.dismissWindow) private var dismissWindow

    var body: some View{
        Button("hit me") {
            openWindow(id: "file.view", value: File(path: URL(string: "file://")!)
        }
    }
}

Upvotes: 4

DoTryCatch
DoTryCatch

Reputation: 1082

I found two ways to solve this problem. I'm using the first one, because my application is file based. The second solution is based on the great Pulse git repo.
In both cases you need to register a custom URL in the Xcode project settings under:
Tragets -> yourApp -> Info -> URL Types, otherwise it won't work.

first solution:

import SwiftUI

@main
struct myApp: App {
     
    var body: some Scene {
        // 'default' view
        WindowGroup { contentView() }
 
        // the view that should open if someone opens your file
        WindowGroup("fileView") { fileView() }
        .handlesExternalEvents(matching: ["file"]) // does the magic
 
    }
}

struct fileView: View {
    var body: some View {
        VStack{ /* your view content */}
        .onOpenURL(perform: { url in
            // get url and read e.g.: your info file
        })
    }
}



// you can open a file using:
Button("openMyFileinAnExtraWindow"){
   let fileUrl = URL(fileURLWithPath: "~/Documents/myFile.yourExtension")
   NSWorkspace.shared.open(fileUrl)
}  



second solution:

Notice: I created this code snippet based on the great Pulse git repo.

import SwiftUI

@main
struct myApp: App {

    var body: some Scene {
        // 'default' view
        WindowGroup { contentView() }

        // the view that should open if someone opens your file
        WindowGroup { DetailsView() }
        .handlesExternalEvents(matching: Set(arrayLiteral: "newWindow")) // this url must be registerd

    }
}

struct DetailsView: View {
    var body: some View {
        ExternalEvents.open
    }
}

public struct ExternalEvents {
    /// - warning: Don't use it, it's used internally.
    public static var open: AnyView?
}

struct contentView: View {
    var body: some View {
        VStack {
            // create a button that opens a new window
            Button("open a new window") {
                ExternalEvents.open = AnyView(
                    newWindow(id: 0, WindowName: "I am a new window!" )
                )
                guard let url = URL(string: "your-url://newWindow") else { return }
                NSWorkspace.shared.open(url)
            }
            
        }
    }
}

struct newWindow: View {
    var id: Int
    var WindowName: String
    var body: some View{
        VStack{
            Text(WindowName + String(id))
        }
    }
}

I'm not sure if this is the best way to pass variables to a new window, but it does the job quite convincingly. I'm happy about any solution approaches and ideas.

Upvotes: 2

Related Questions