SwiftUI 2: способ открыть просмотр в новом окне

Представим, что у меня есть приложение

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 есть сетка с множеством книг, сохраненных в какой-то БД.

BookView может быть инициирован следующим образом:

BookView(model: bookViewModel)

Цель: открыть BookView В НОВОМ ОТДЕЛЬНОМ ОКНЕ (например, нажав на кнопку). Как я могу это сделать?


Бонусный вопрос: как открыть SettingsView(model: config) из кода?


PS: NavigationLink не решение для меня, потому что я не использую NavigationView.

4 ответа

Попробуйте что-то вроде следующего (просто идея - не могу проверить)

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

Я нашел этот ответ, который помог мне открыть новое окно: https://developer.apple.com/forums/thread/651592?answerId=651132022#651132022

Я на xcode 12.3, Swift 5.3, работает Биг-Сур.

Ниже приведен пример того, как настроить так, чтобы кнопка в 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-адресов введите test в поле URL Schemes (а также поле идентификатора), чтобы зарегистрировать наше приложение в системе.

Я добился этого, отредактировав Info.plist файл и внесение туда дополнений, т.е. URL types -> URL Schemes...:

Я играл с кодом из ответа Hillmark и получил несколько лучших результатов, чем его код.

Даже если этот код все еще не является ответом, это может быть кому-то полезно.

_

Обычно этот код будет бесполезен для вас, если вам нужно работать с View на основе 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)
        }
    }
}

Для работы с этим кодом вам необходимо:

Недавно я столкнулся с аналогичной проблемой в 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()
    }
}
Другие вопросы по тегам