#macos #swiftui
#macos #swiftui
Вопрос:
Я хотел бы показать второе окно с другим содержимым в приложении SwiftUI на macOS. Я не могу найти никакой документации по этому вопросу. Попытка ниже не работает. Кто-нибудь знает, как это сделать?
class AppState: ObservableObject {
@Published var showSecondWindow: Bool = false
}
@main
struct MultipleWindowsApp: App {
@StateObject var appState = AppState()
var body: some Scene {
WindowGroup {
ContentView().environmentObject(appState)
}
WindowGroup {
if appState.showSecondWindow {
SecondContent()
}
}
}
}
struct ContentView: View {
@EnvironmentObject var appState: AppState
var body: some View {
VStack {
Text("Hello, world!")
Button("Open 2nd Window") {
appState.showSecondWindow = true
}
}.padding()
}
}
struct SecondContent: View {
var body: some View {
Text("Hello, from window #2.")
}
}
Ответ №1:
Протестировано на Xcode 13 beta, SwiftUI 3.0
После того, как я оказался в этой ситуации, я получил несколько ответов, которые были по всему Интернету, и это работает для меня:
В файле @main (MyAppApp) добавьте WindowGroup("Window Name")
необходимое вам количество:
import SwiftUI
@main
struct MyAppApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
WindowGroup("Second Window") {
SecondWindow()
}.handlesExternalEvents(matching: Set(arrayLiteral: "SecondWindow"))
WindowGroup("Third Window") {
ThirdWindow()
}.handlesExternalEvents(matching: Set(arrayLiteral: "ThirdWindow"))
}
Что разместить в каждом WindowGroup
?:
WindowGroup("SecondWindow") /*Any name you want to be displayed at the top of the window.*/ {
SecondWindow() //View you want to display.
}.handlesExternalEvents(matching: Set(arrayLiteral: "SecondWindow")) //Name of the view without ().
Теперь в конце файла MyAppApp (за пределами struct MyAppApp: App
) добавьте следующее enum
:
enum OpenWindows: String, CaseIterable {
case SecondView = "SecondView"
case ThirdView = "ThirdView"
//As many views as you need.
func open(){
if let url = URL(string: "myapp://(self.rawValue)") { //replace myapp with your app's name
NSWorkspace.shared.open(url)
}
}
}
Добавьте следующее в свой Info.plist
Замените myapp именем вашего приложения.
Использование:
Button(action: {
OpenWindows.SecondView.open()
}){
Text("Open Second Window")
}
Комментарии:
1. Есть ли способ сделать это со
Settings
сценой?handlesExternalEvents
показывает ошибку, в которой говорится, что она доступна только для оконных групп2. Почему вы используете
Set(arrayLiteral:)
? Это очень странно; этот метод должен вызываться только компилятором.3. К сожалению, это не работает на macOS 13 Beta 1.
4. Я пробовал это в Xcode 13.4.1, и все, что он делает, это каждый раз открывает основной contentView из первого окна. Я что-то упустил?
Ответ №2:
Вот расширение для создания окна NSViewController
, присвоения заголовка и его открытия.
extension View {
@discardableResult
func openInWindow(title: String, sender: Any?) -> NSWindow {
let controller = NSHostingController(rootView: self)
let win = NSWindow(contentViewController: controller)
win.contentViewController = controller
win.title = title
win.makeKeyAndOrderFront(sender)
return win
}
}
Использование:
Button("Open 2nd Window") {
SecondContent().openInWindow(title: "Win View", sender: self)
}
Чтобы закрыть окно
NSApplication.shared.keyWindow?.close()
Комментарии:
1. Используйте это, чтобы программно закрыть окно:
NSApplication.shared.keyWindow?.close()
2. Я пробовал это в Xcode 14, и я не получаю никаких ошибок, но при нажатии кнопки тоже ничего не происходит
3.Когда вы дойдете до этой строки
win.makeKeyAndOrderFront(sender)
, она должна работать как любая другаяNSWIndow
, поскольку этоNSWindow
.. Вы можете открыть любое окно? Может быть, не связано с SwiftUI? Вы можете попытаться получить доступ кcontantViewController
из окна и проверить его.
Ответ №3:
Обновление июнь 2022: теперь добавлены оконные API. Я оставлю старый ответ ниже, поскольку он все еще может быть полезен для тех, кому нужна внешняя ссылка.
Метод, который вы ищете, — это WindowGroup
and View
‘s handlesExternalEvents
. Вам также необходимо сначала создать схему URL для идентификации вашей книги, добавить ее в свой Info.plist. При вызове @Environment
‘s openURL
, если представление handlesExternalEvents
, соответствующее URL-адресу книги, уже находится в окне, оно повторно активирует это окно. В противном случае он будет использовать handlesExternalEvents
applied to WindowGroup
для открытия нового окна.
Вы можете увидеть пример в моем блоге здесь.
Комментарии:
1. Ура, код в блоге сработал для меня. Я искал такое решение. Спасибо.
Ответ №4:
Откроется новое окно:
import SwiftUI
struct ContentView: View
{
var body: some View
{
Button(action: {openMyWindow()},
label: {Image(systemName: "paperplane")})
.padding()
}
}
func openMyWindow()
{
var windowRef:NSWindow
windowRef = NSWindow(
contentRect: NSRect(x: 100, y: 100, width: 100, height: 600),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
windowRef.contentView = NSHostingView(rootView: WindowView())
windowRef.makeKeyAndOrderFront(nil)
}
struct WindowView: View
{
var body: some View
{
Text("Hello World")
.padding()
}
}
Комментарии:
1. Это старый способ до SwiftUI 2.0.
2. Если это «старый» способ, не могли бы вы добавить «новый» способ?
3. Если «новый» способ использует пользовательскую схему URL, это ужасный способ создания нового окна. Это означает, что вам нужно «жестко закодировать» каждое окно в
info.plist
файл. Это просто кажется неправильным во всех отношениях. Я скорее просто используюNSHostingView
, чем добавляю окна вinfo.plist
.4. Нет необходимости в «жестком коде» для каждого окна в
info.plist
.URL Scheme
достаточно.5. Когда я закрываю окно, которое было создано таким образом, приложение вылетает в @main, мы знаем, почему?
Ответ №5:
В macOS 13 теперь вы можете зарегистрировать и открыть windows программно:
@main
struct OpenWindowApp: App {
@StateObject private var dataStore = DataStore()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(dataStore)
}
WindowGroup("Note", for: Note.ID.self) { $noteId in
NoteView(noteId: noteId)
.environmentObject(dataStore)
}
}
}
(из исходного кода).
Мне было весело воссоздать его в macOS 11:
import SwiftUI
private class WindowStorage {
static let main = WindowStorage()
private var blocks: [ObjectIdentifier: (AnyHashable) -> AnyView] = [:]
func viewContent<E: Hashable>(for element: E) -> AnyView? {
let type = ObjectIdentifier(E.self)
guard let view = blocks[type]?(element) else { return nil }
return view
}
func registerContent<E: Hashable, V: View>(block: @escaping (E) -> V) {
let type = ObjectIdentifier(E.self)
return blocks[type] = { anyHash in
guard let element = anyHash as? E else { return AnyView(EmptyView()) }
return AnyView(block(element))
}
}
}
extension View {
func openWindow<E: Hashable>(_ element: E) {
guard let view = WindowStorage.main.viewContent(for: element) else { return }
let root = NSHostingController(rootView: view)
let window = NSWindow(contentViewController: root)
window.toolbar = NSToolbar()
window.makeKeyAndOrderFront(self)
NSApplication.shared.mainWindow?.windowController?.showWindow(window)
}
}
extension WindowGroup {
init<E: Hashable, C: View>(for element: E.Type,
content: @escaping (E) -> C) where Content == WindowWrappingView {
self.init {
WindowWrappingView()
}
WindowStorage.main.registerContent(block: content)
}
}
struct WindowWrappingView: View {
var body: some View {
EmptyView()
}
}
Это ужасно. Особенно использование таких NSHostingController
вещей, как toolbar
модификатор.
Если вы ищете какое-то обратное решение, оно все равно может помочь 😉