#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
..:
Комментарии:
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 откроет ваше приложение. Так что не доверяйте никакому входу, который вы получаете.