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

#swift #macos #navigation #swiftui #scene

Вопрос:

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

 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(model: bookViewModel)
 

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


Бонусный вопрос: Как я могу открыть SettingsView(model: config) код?


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

Ответ №1:

Я нашел этот ответ, который сработал для меня с точки зрения возможности открыть новое окно: 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, включенным в связанный ответ (цитируется здесь для удобства).:

Теперь в разделе Проект->Информация->>Типы URL введите >> test в поле Схемы URL (и поле идентификатора тоже), чтобы зарегистрировать наше приложение в системе.

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

Информация.plist

Комментарии:

1. Большое спасибо, я проверю это позже

2. Прежде всего — это интересное решение и спасибо за него. Но похоже, что это бесполезно, пока у нас нет возможности отправить 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)
            }
        }
    }
}
 

Комментарии:

1. его невозможно иметь, если( ) непосредственно внутри WindowGroup (), нужно перенести в другое представление; Невозможно открыть представление init из модели, допускающей обнуление — мне в любом случае нужно вернуть представление, но возвращать пустое представление-плохая идея-в результате я получу пустое окно. Возврат нуля — выдаст ошибку; это похоже на хакерский способ сделать следующим образом ( я имею в виду эти инъекции ). Но спасибо за попытку

2. @Andrew, это ошибка во время выполнения, потому что она может быть собрана на моей стороне?

3. Нет, ошибки компиляции; Но в любом случае это не очень хорошее решение, так как действительно, даже если оно будет работать… Действительно проще и чище код будет в случае использования AppKit… Действительно ли нет более приятного способа открыть совершенно новое окно в SwiftUI ?

4. Это прекрасно для меня, но у меня не появляется второго всплывающего окна. (Я использую бета-версию Big Sur, бета-версию Xcode 12.2)

5. @RobN столкнулся с той же проблемой в выпуске 12.4 Big Sur 11.1

Ответ №3:

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

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

_

В общем, этот код будет бесполезен для вас, если вам нужно работать с представлением на основе ViewModel.

Но если вам нужно работать только с видом — это хорошее решение.

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

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

введите описание изображения здесь

введите описание изображения здесь

Комментарии:

1. Вы уже пытались использовать глубокие ссылки для передачи «подробной информации»? Например, идентификатор книги, как в . taotao://MainView/ShowBook/BlaBla Попробуйте обработать глубокую ссылку, используя .onOpenUrl(...) в главном окне.

2. Нет, я этого не делал. Я использую в своем приложении другую систему, чем эта. Поскольку это все еще недостаточно гибко для меня.

3. Подробную информацию также можно отправлять, если это действительный URL-адрес. Но будьте осторожны, использование этой ссылки в safari откроет ваше приложение. Так что не доверяйте никакому входу, который вы получаете.