Вывод и закрытие Windows в приложении MacOS SwiftUI
У меня есть этот небольшой пример приложения, которое создает несколько окон моего приложения SwiftUI MacOS.
Является ли это возможным:
- иметь список всех открытых окон в MainView?
- закрыть одно окно из MainView?
- отправить сообщение в одно окно из MainView?
@main
struct MultiWindowApp: App {
@State var gvm = GlobalViewModel()
var body: some Scene {
WindowGroup {
MainView()
.environmentObject(gvm)
}
WindowGroup("Secondary") {
SecondaryView(bgColor: .blue)
.environmentObject(gvm)
}
.handlesExternalEvents(matching: Set(arrayLiteral: "*"))
}
}
struct MainView: View {
@Environment(\.openURL) var openURL
@EnvironmentObject var vm : GlobalViewModel
var body: some View {
VStack {
Text("MainView")
Button("Open Secondary") {
if let url = URL(string: "OpenNewWindowApp://bla") {
openURL(url)
}
//List of all open Windows
// Button to close a single window
// Button to set color of a single window to red
}
}
.padding()
}
}
struct SecondaryView: View {
var bgColor : Color
@EnvironmentObject var vm : GlobalViewModel
var body: some View {
VStack{
Spacer()
Text("Viewer")
Text("ViewModel: \(vm.name)")
Button("Set VM"){
vm.name = "Tom"
}
Spacer()
}
.background(bgColor)
.frame(minWidth: 300, minHeight: 300, idealHeight: 400, maxHeight: .infinity, alignment: .center )
}
}
class GlobalViewModel :ObservableObject {
@Published var name = "Frank"
}
1 ответ
Возможно, есть более ориентированный на SwiftUI способ сделать это. Если еще нет, я определенно надеюсь, что Apple добавит более качественные средства управления окнами для Mac - сейчас все кажется чем-то вроде взлома.
Вот что я придумал:
@main
struct MultiWindowApp: App {
@State var gvm = GlobalViewModel()
var body: some Scene {
WindowGroup {
MainView()
.environmentObject(gvm)
}
WindowGroup("Secondary") {
SecondaryView(bgColor: .blue)
.environmentObject(gvm)
}
.handlesExternalEvents(matching: Set(arrayLiteral: "*"))
}
}
struct MainView: View {
@Environment(\.openURL) var openURL
@EnvironmentObject var vm : GlobalViewModel
var body: some View {
VStack {
Text("MainView")
List {
ForEach(Array(vm.windows), id: \.windowNumber) { window in
HStack {
Text("Window: \(window.windowNumber)")
Button("Red") {
vm.setColor(.red, forWindowNumber: window.windowNumber)
}
Button("Close") {
window.close()
}
}
}
}
Button("Open Secondary") {
if let url = URL(string: "OpenNewWindowApp://bla") {
openURL(url)
}
}
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
struct SecondaryView: View {
var bgColor : Color
@EnvironmentObject var vm : GlobalViewModel
@State private var windowNumber = -1
var body: some View {
VStack{
HostingWindowFinder { window in
if let window = window {
vm.addWindow(window: window)
self.windowNumber = window.windowNumber
}
}
Spacer()
Text("Viewer")
Text("ViewModel: \(vm.name)")
Button("Set VM"){
vm.name = "Tom"
}
Spacer()
}
.background(vm.backgroundColors[windowNumber] ?? bgColor)
.frame(minWidth: 300, minHeight: 300, idealHeight: 400, maxHeight: .infinity, alignment: .center )
}
}
class GlobalViewModel : NSObject, ObservableObject {
@Published var name = "Frank"
@Published var windows = Set<NSWindow>()
@Published var backgroundColors : [Int:Color] = [:]
func addWindow(window: NSWindow) {
window.delegate = self
windows.insert(window)
}
func setColor(_ color: Color, forWindowNumber windowNumber: Int) {
backgroundColors[windowNumber] = color
}
}
extension GlobalViewModel : NSWindowDelegate {
func windowWillClose(_ notification: Notification) {
if let window = notification.object as? NSWindow {
windows = windows.filter { $0.windowNumber != window.windowNumber }
}
}
}
struct HostingWindowFinder: NSViewRepresentable {
var callback: (NSWindow?) -> ()
func makeNSView(context: Self.Context) -> NSView {
let view = NSView()
DispatchQueue.main.async { [weak view] in
self.callback(view?.window)
}
return view
}
func updateNSView(_ nsView: NSView, context: Context) {}
}
Я использую трюк из https://lostmoa.com/blog/ReadingTheCurrentWindowInANewSwiftUILifecycleApp/,
чтобы получить ссылку наNSWindow
. Это сохраняется в модели представления в наборе. Позже, чтобы получить доступ к таким вещам, как закрытие окон и т.д., я ссылаюсь на окна с
windowNumber
.
Когда появляется окно, оно добавляется в список окон модели представления. Затем, когда модель представления получит
windowWillClose
вызов в качестве делегата, он удаляет его из списка.
Настройка цвета фона осуществляется через
backgroundColors
свойство на модели представления. Если нет одного набора, он использует переданное свойство цвета фона. Есть масса разных способов спроектировать этот бит.