SwiftUI 2:如何在新窗口中打开视图

16

假设我有一个应用程序

var storeVM = BookStoreViewModel(bla1: bla1, bla2: bla2, bla3: bla3)

@SceneBuilder var body: some Scene {
    WindowGroup {
        BookStoreView( model: storeVM )
    }
    
    #if os(macOS)
    Settings {
        SettingsView(model: config)
    }   
    #endif
}

书店有一个包含许多书籍的网格,这些书籍保存在某个数据库中。

可以通过以下方式启动 BookView:

BookView(model: bookViewModel)

目标:通过单击按钮在新的独立窗口中打开BookView。我该如何做到这一点?


奖励问题: 如何从代码中打开SettingsView(model:config)


附注:NavigationLink不适合我的解决方案,因为我没有使用NavigationView

4个回答

6

更新:Xcode 14 / macOS 13

现在,我们可以通过指定标识符来显式地打开所需的窗口,并在openWindow环境操作中使用它。

class BookViewModel: ObservableObject {} 

class AppState: ObservableObject {
    @Published var selectedBook: BookViewModel?
}

@main
struct SwiftUI2_MacApp: App {
    @StateObject var appState = AppState()

    @SceneBuilder 
    var body: some Scene {
        WindowGroup {         // main scene
            ContentView()
              .environmentObject(appState)   // inject to update
                                             // selected book
        }

        Window("Book Viewer", id: "book") { // << here !!
            BookView(model: appState.selectedBook)
        }

        // also possible variant with injected model for group
        // WindowGroup("Book Viewer", id: "book", for: Book.self) {  book in // << here !!
        //   BookView(model: book)
        // }
    }
}

struct ContentView: View {
    @EnvironmentObject var appState: AppState

    @Environment(\.openWindow) private var openWindow   // << !!

    var body: some View {

        // assuming selection in grid somewhere here to
        // appState.selectedBook

        Button("Open Book") {
            openWindow(value: "book")       // << here !!

        // for group variant with injected value
        // if let book = self.selectedBook {
        //    openWindow(value: "book", book)       // << here !!
        // }
        }
    }
}

无法直接在WindowGroup()中使用if( ),需要将其包装到另一个视图中;无法从可空模型打开init视图 - 无论如何都需要返回视图,但返回空视图是不好的想法 - 我会得到一个空窗口作为结果。返回nil - 将抛出错误;看起来用以下方式(我的意思是那些注入)做起来有点hacky。但还是谢谢尝试。 - Andrew_STOP_RU_WAR_IN_UA
@Andrew,那是运行时错误吗?因为在我的端上它是可编译的。 - Asperi
3
不,没有编译错误。但无论如何,这并不是一个真正好的解决方案,因为即使它能够工作...如果使用AppKit会有更加容易和简洁的代码。是否真的没有更好的方法在SwiftUI中打开全新的窗口? - Andrew_STOP_RU_WAR_IN_UA
2
这对我来说编译得很好,但是我没有看到任何第二个窗口弹出。(我正在使用Big Sur beta,Xcode 12.2 beta) - Rob N
@RobN 在12.4版本和Big Sur 11.1上遇到了同样的问题。 - Ryan

5

我找到了这个答案,对我来说有效,可以打开一个新窗口: https://developer.apple.com/forums/thread/651592?answerId=651132022#651132022

我使用的是 xcode 12.3Swift 5.3,运行 Big Sur。

以下是如何设置的示例,以便在 ContentView 中的按钮可用于打开 OtherView 窗口。

@main
struct testApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        WindowGroup("OtherView") {
            OtherView()
        }
        .handlesExternalEvents(matching: Set(arrayLiteral: "*"))
    }
}

struct ContentView: View {
    @Environment(\.openURL) var openURL

    var body: some View {
       Button("Other View") {
           if let url = URL(string: "test://otherview") {
               openURL(url)
           }
       }
    }
}

struct OtherView: View {
    var body: some View {
        Text("Other View!")
    }
}


请注意:确保按照链接答案中包含的URL Scheme说明进行操作(为方便起见在此引用):
在项目->信息->URL类型中,输入“test”作为URL Scheme字段(也要输入标识符字段),以向系统注册我们的应用程序。
我通过编辑Info.plist文件并在其中进行添加来实现此目的,即URL类型->URL Schemes...:

Info.plist


非常感谢,我稍后会检查这个。 - Andrew_STOP_RU_WAR_IN_UA
2
首先,这是一个有趣的解决方案,感谢分享。但是在我们无法将viewModel发送到窗口之前,它似乎毫无用处。你有任何想法如何与viewModel一起使用吗? - Andrew_STOP_RU_WAR_IN_UA

4

我已经尝试了hillmark的代码,并且比他的代码得到了一些更好的结果。

即使这段代码仍然不是答案,但对某些人可能会有用。

_

通常情况下,如果您需要使用基于ViewModel的View,则此代码对您来说将是无用的。

但是,如果您只需要使用View,那么这是一个很好的解决方案。

@main
struct TestAppApp: App {
    var body: some Scene {
        WindowGroup {
            MainView()
        }
        // magic here
        .handlesExternalEvents(matching: Set(arrayLiteral: Wnd.mainView.rawValue))
        
        WindowGroup {
            HelperView()
        }
        // magic here
        .handlesExternalEvents(matching: Set(arrayLiteral: Wnd.helperView.rawValue))
    }
}

extension TestAppApp {
    struct MainView: View {
        var body: some View {
            VStack {
                Button("Open Main View") {
                    // magic here
                    Wnd.mainView.open()
                }
                
                Button("Open Other View") {        
                    // magic here
                    Wnd.helperView.open()
                }
            }
            .padding(150)
        }
    }

    struct HelperView: View {
        var body: some View {
            HStack {
                Text("This is ") + Text("Helper View!").bold()
            }
            .padding(150)
        }
    }
}



// magic here
enum Wnd: String, CaseIterable {
    case mainView   = "MainView"
    case helperView = "OtherView"
    
    func open(){
        if let url = URL(string: "taotao://\(self.rawValue)") {
            print("opening \(self.rawValue)")
            NSWorkspace.shared.open(url)
        }
    }
}

为了使用该代码,您需要具备以下条件:
- 计算机:主流操作系统均可 - Python3:请安装最新版本的Python3 - IDE或文本编辑器:例如PyCharm、Sublime Text等 - 部分库文件:使用pip等工具安装相应库文件

输入图片说明

输入图片说明


1
你是否已经尝试使用深度链接来传递“详细信息”?例如,书籍标识符如 taotao://MainView/ShowBook/BlaBla。尝试使用 MainView 中的 .onOpenUrl(...) 处理深度链接。 - pd95
不,我没有。我在我的应用程序中使用的是另一个系统,而不是这个。因为这对我来说还不够灵活。 - Andrew_STOP_RU_WAR_IN_UA
1
详细信息也可以发送,只要它是有效的URL。但要小心,使用Safari中的链接将打开您的应用程序。因此,请不要相信您收到的任何输入。 - user965972
我应该如何向 Helper View 传递详细信息呢?比如说,我是否可以以某种方式访问完整的链接? - LilaQ
为了完整性而回答自己的问题:在 Helper 视图上添加 .onOpenURL 即可解决问题。 - LilaQ

0

我最近在SwiftUI中遇到了类似的问题,我想创建一个新的桌面级别窗口,最终我找到了解决方案。

Button("Show Bookmark") {
    // create a new NSPanel
    let window = NSPanel(contentRect: NSRect(x: 0, y: 0, width: 500, height: 500), styleMask: [.fullSizeContentView, .closable, .resizable, .titled], backing: .buffered, defer: false)
    
    // the styleMask can contails more, like borderless, hudWindow...
                    
    window.center()

    // put your swiftui view here in rootview
    window.contentView = NSHostingView(rootView: BookmarkView())
    window.makeKeyAndOrderFront(nil)

    // if you want to close the window after delay
    DispatchQueue.main.asyncAfter(deadline: .now()) {
        window.close()
    }
}

它是否具有所有SwiftUI功能?例如窗口分组/选项卡等? - Andrew_STOP_RU_WAR_IN_UA
是的,我也这么认为,但我还没有尝试过,也许你应该自己试一下。当时我的意图是创建一个无边框窗口,就像这里提到的那个。 - voorjaar
我认为不是这样的,因为在SwiftUI中没有关于如何分组窗口的信息 :) 这是一种hack方法,但不是SwiftUI的本地解决方案。我的解决方案也是hack,但它将与SwiftUI窗口功能一起工作。 - Andrew_STOP_RU_WAR_IN_UA
我对这个非常新。我想删除关于书店的引用,只需在窗口中以列表形式显示一个字符串数组,就像弹出窗口一样。这可以做到吗? - Nate Lockwood

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接