SwiftUI macOS NSWindow 实例

11

如果使用 xcode 12.3swift 5.3,以及 SwiftUI App 生命周期来构建一个 macOS 应用程序,那么访问和更改 NSWindow 的外观和行为的最佳方法是什么?

编辑: 我真正需要的是 NSWindow 实例。

我已经添加了一个 AppDelegate,但据我所知,NSWindow 可能会是 nil,因此无法修改,并且简单地在这里创建一个类似于 AppKit App Delegate 生命周期方法的窗口会导致启动时出现两个窗口。

其中一种解决方案是防止默认窗口出现,并将其全部交给 applicationDidFinishLaunching 方法,但不确定这是否可行或明智。

WindowStyle 协议似乎是一个可能的解决方案,但不确定如何在当前阶段以最佳方式利用它,以及它是否提供对 NSWindow 实例进行细粒度控制的访问权限。

class AppDelegate: NSObject, NSApplicationDelegate {        
    func applicationDidFinishLaunching(_ aNotification: Notification) {
      // In AppKit simply create the NSWindow and modify style.
      // In SwiftUI creating an NSWindow and styling results in 2 windows, 
      // one styled and the other default.
    }
}

@main
struct testApp: App {
    
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate : AppDelegate

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

为其分配一个NSAppearance?例如,请参阅https://developer.apple.com/documentation/appkit/nsappearancecustomization/choosing_a_specific_appearance_for_your_macos_app - fishinear
谢谢您的建议,虽然我认为NSAppearance可能不足以覆盖我的用例,因为我需要控制NSWindow行为的其他方面。我将编辑我的问题,使这个要求更加明显。 - hillmark
正在尝试做同样的事情。在谷歌上对此缺乏足够的了解。这里的符合类型似乎也不适用于我 - https://developer.apple.com/documentation/swiftui/windowstyle - Chris
是的,我希望能够找到更多关于符合类型以及如何通过实现“WindowStyle”协议来实现类似功能的内容,但目前还没有找到任何信息,尽管我已经能够使用文档中定义的那些,例如“HiddenTitleBarWindowStyle”已经足够好用了,但我需要自己实现才能达到我想要的效果。 - hillmark
2个回答

12
尽管我不完全确定这是否是正确的方法,但基于这个问题的答案: https://dev59.com/q1IG5IYBdhLWcg3wqTXe#63439982,我已经能够访问NSWindow实例并修改其外观。为了快速参考,这里有一个工作示例,基于xcode 12.3swift 5.3和SwiftUI应用程序生命周期提供的原始代码提供者Asperi的建议。
@main
struct testApp: App {    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

class Store {
    var window: NSWindow
    
    init(window: NSWindow) {
        self.window = window
        self.window.isOpaque = false
        self.window.backgroundColor = NSColor.clear
    }
}

struct ContentView: View {
    @State private var window: NSWindow?
    var body: some View {
        VStack {
            Text("Loading...")
            if nil != window {
                MainView(store: Store(window: window!))
            }
        }.background(WindowAccessor(window: $window))
    }
}

struct MainView: View {

    let store: Store
    
    var body: some View {
        VStack {
            Text("MainView with Window: \(store.window)")
        }.frame(width: 400, height: 400)
    }
}

struct WindowAccessor: NSViewRepresentable {
    @Binding var window: NSWindow?
    
    func makeNSView(context: Context) -> NSView {
        let view = NSView()
        DispatchQueue.main.async {
            self.window = view.window
        }
        return view
    }
    
    func updateNSView(_ nsView: NSView, context: Context) {}
}

嗨,Hillmark,我喜欢这个答案,但是没有问题,如果我想用其他视图替换ContentView,而不打开另一个NSWindow怎么办? - Skovie

12

@hillmark 的方法很有趣。

我也用 NSApplicationDelegateAdaptor 的方法使它工作了。

我相信下面的代码只适用于单窗口 MacOS SwiftUI 应用程序,多窗口应用程序可能需要处理所有窗口,而不仅仅是第一个窗口。

我还不确定我的方法有没有任何缺陷。

目前这解决了我的问题,但我也会尝试你的方法。

import SwiftUI

final class AppDelegate: NSObject, NSApplicationDelegate {
    func applicationDidFinishLaunching(_ notification: Notification) {
        if let window = NSApplication.shared.windows.first {
            window.titleVisibility = .hidden
            window.titlebarAppearsTransparent = true
            window.isOpaque = false
            window.backgroundColor = NSColor.clear
        }
    }
}

@main
struct AlreadyMacOSApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var delegate
    var body: some Scene {
        WindowGroup {
            NewLayoutTest()
        }
    }
}

是的,我正在走多窗口的路线,现在发现主窗口方法为我提供了灵活性,可以针对每个窗口等不同样式进行良好的分离。我的appDelegate正在逐渐清空,这让我感到乐观。实际上,我最终得到了一种类似于React的容器类型模式。 - hillmark
1
尝试了你的方法。非常好用,我已经转而使用它了。 - Chris
我很高兴看到你能够自己回答这个问题。 - Chris
如果将applicationDidFinishLaunching替换为applicationWillUpdateapplicationDidUpdate,并且迭代所有(或某些)窗口而不仅仅是第一个窗口,那么它不能与多个窗口一起工作吗? - Wowbagger and his liquid lunch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接