SwiftUI 2:在新窗口中打开视图的方式

Posted

技术标签:

【中文标题】SwiftUI 2:在新窗口中打开视图的方式【英文标题】:SwiftUI 2: the way to open view in new window 【发布时间】:2020-11-04 23:29:20 【问题描述】:

假设我有一个应用程序

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

BookStore 有一个 Grid,其中有很多书籍保存在一些 DB 中。

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

BookView(model: bookViewModel)

目标:在新的分离窗口中打开 BookView(例如单击按钮)。我该怎么做?


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


PS:NavigationLink 不是我的解决方案,因为我没有使用 NavigationView

【问题讨论】:

【参考方案1】:

我找到了这个答案,它对我来说可以打开一个新窗口: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 方案说明(为方便起见,此处引用):

现在在 Project->Info->URL Types 中的 URL Schemes 字段(以及标识符字段)中输入 test 以向系统注册我们的应用程序。

我通过编辑Info.plist 文件并在其中添加内容来实现这一点,即URL types -> URL Schemes...:

【讨论】:

非常感谢,我稍后会检查这个 首先 - 这是一个有趣的解决方案,感谢它。但看起来它没用,直到我们无法将 viewModel 发送到窗口中。你知道如何将它与 viewModel 一起使用吗?【参考方案2】:

尝试以下方法(只是想法 - 无法测试)

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
        

        WindowGroup("Book Viewer")  // other scene
            if appState.selectedBook != nil 
                Book(model: appState.selectedBook)
            
        
    

【讨论】:

不可能将if()直接放在WindowGroup()中,需要换行到另一个视图中;从可空模型打开初始化视图是不可能的——无论如何我都需要返回视图,但返回空视图是个坏主意——结果我会得到一个空窗口。返回 nil - 会抛出错误;它看起来像是一种使用以下方式的hacky方式(我的意思是那些注入)。但是感谢您的尝试 @Andrew,这是运行时错误,因为它可以在我身边编译吗? 否,编译错误;但无论如何,这并不是一个很好的解决方案,因为即使它可以工作......真的更容易和更干净的代码将是在使用 AppKit 的情况下......真的没有更好的方法来在 swiftUI 中打开全新的窗口? 这对我来说编译得很好,但我没有弹出任何第二个窗口。 (我使用的是 Big Sur 测试版,Xcode 12.2 测试版) @RobN 在 12.4 版本 + Big Sur 11.1 上面临同样的问题【参考方案3】:

我玩过 hillmark 答案中的代码,得到的结果比他的代码更好。

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

_

如果您需要使用基于 ViewModel 的视图,则此代码通常对您毫无用处。

但如果您只需要使用 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 
        @Environment(\.openURL) var openURL
        
        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)
        
    

要使用此代码,您需要具备以下条件:

【讨论】:

您是否已经尝试使用深层链接来传递“详细信息”?例如。 taotao://MainView/ShowBook/BlaBla 中的图书标识符。尝试在 MainVIew 中使用 .onOpenUrl(...) 处理深层链接。 不,我没有。我在我的应用程序中使用了另一个系统而不是这个。因为这对我来说仍然不够灵活。 详细信息也可以发送,只要它是一个有效的url。但要小心,在 safari 中使用该链接会打开您的应用程序。所以不要相信你得到的任何意见。

以上是关于SwiftUI 2:在新窗口中打开视图的方式的主要内容,如果未能解决你的问题,请参考以下文章

在新选项卡/窗口中打开链接而不丢失当前 JSF 页面的视图范围

从 AppKit NSButton 打开新的 SwiftUI 窗口

location.href 如何在在新窗口中打开页面!

如何使用asp.net MVC中的href从javascript在新窗口中打开打开的窗口

macOS 应用程序的 SwiftUI:关闭窗口并打开另一个窗口时的用户 @EnvironmentObject

超链接的目标网页在新窗口中打开的方式是