如何在 SwiftUI 中设置窗口在桌面上的位置?

Nik*_*nko 4 macos swift swiftui

如何在MacOS 桌面上的 SwiftUI 中设置窗口坐标?例如,窗口应该始终出现在中心还是始终出现在右上角

这是我的版本,但是,我移动代码并关闭它,当我打开它时,它首先出现在旧位置,然后跳转到新位置。

import SwiftUI

let WIDTH: CGFloat = 400
let HEIGTH: CGFloat = 200

@main
struct ForVSCode_MacOSApp: App {
    @State var window : NSWindow?
    
    var body: some Scene {
        WindowGroup {
            ContentView(win: $window)
        }
    }
}

struct WindowAccessor: NSViewRepresentable{
    @Binding var window: NSWindow?
    
    func makeNSView(context: Context) -> some NSView {
        let view = NSView()
        
        let width = (NSScreen.main?.frame.width)!
        let heigth = (NSScreen.main?.frame.height)!
        
        let resWidth: CGFloat = (width / 2) - (WIDTH / 2)
        let resHeigt: CGFloat = (heigth / 2) - (HEIGTH / 2)
        
        DispatchQueue.main.async {
            self.window = view.window
            self.window?.setFrameOrigin(NSPoint(x: resWidth, y: resHeigt))
            self.window?.setFrameAutosaveName("mainWindow")
            self.window?.isReleasedWhenClosed = false
            self.window?.makeKeyAndOrderFront(nil)

        }
        return view
    }
    
    func updateNSView(_ nsView: NSViewType, context: Context) {
        
    }
}
Run Code Online (Sandbox Code Playgroud)

和内容视图

import SwiftUI

struct ContentView: View {

    @Binding var win: NSWindow?
    var body: some View {
        VStack {
            Text("it finally works!")
        }
        .font(.largeTitle)
        .frame(width: WIDTH, height: HEIGTH, alignment: .center)
        .background(WindowAccessor(window: $win))

    }

}
struct ContentView_Previews: PreviewProvider {
    @Binding var win: NSWindow?

    static var previews: some View {
        ContentView(win: .constant(NSWindow()))
        .frame(width: 250, height: 150, alignment: .center)

    }
}
Run Code Online (Sandbox Code Playgroud)

pd9*_*d95 5

我在我的一个项目中确实遇到了同样的问题,我想我会更深入地研究一下,我发现了两种控制窗口位置的方法。


因此,我影响窗口位置的第一个方法是预先定义窗口在屏幕上的最后位置。

间接控制:帧自动保存名称

当应用程序的第一个窗口打开时,macOS 将尝试恢复上次关闭时的最后一个窗口位置。为了区分不同的窗口,每个窗口都有自己的frameAutosaveName. 窗口框架会自动以文本格式保留在应用程序首选项 ( UserDefaults.standard) 中,其密钥源自frameAutosaveName:“NSWindow Frame <frameAutosaveName>”(请参阅​​ saveFrame 的文档)。

如果您未在 中指定 ID WindowGroup,SwiftUI 将从您的主视图类名称中派生自动保存名称。前三个窗口将具有以下自动保存名称:

 <ModuleName>.ContentView-1-AppWindow-1
 <ModuleName>.ContentView-1-AppWindow-2
 <ModuleName>.ContentView-1-AppWindow-3
Run Code Online (Sandbox Code Playgroud)

例如,通过设置 ID WindowGroup(id: "main"),将使用以下自动保存名称(同样适用于前三个窗口):

main-AppWindow-1
main-AppWindow-2
main-AppWindow-3
Run Code Online (Sandbox Code Playgroud)

当您检查应用程序首选项目录(UserDefaults.standard存储位置)时,您将在 plist 中看到一项:

NSWindow Frame main-AppWindow-1          1304 545 400 228 0 0 3008 1228
Run Code Online (Sandbox Code Playgroud)

有很多数字需要消化。前 4 个整数描述窗口框架(原点和大小),接下来的 4 个整数描述屏幕框架。

手动设置这些值时需要记住以下几点:

  1. macOS 坐标系的原点 (0,0) 位于左下角。
  2. 窗口高度包括窗口标题栏(macOS Monterey 上为 28px,但在其他版本上可能有所不同)
  3. 屏幕高度不包括标题栏
  4. 我没有关于这种格式的文档,并通过反复试验来获取有关它的知识......

因此,为了伪造屏幕中心的初始位置,我使用了在应用程序(或 ContentView)初始化程序中运行的以下函数。但请记住:使用此方法只有第一个窗口会居中。所有后续窗口都将被放置在前一个窗口的右侧。

 <ModuleName>.ContentView-1-AppWindow-1
 <ModuleName>.ContentView-1-AppWindow-2
 <ModuleName>.ContentView-1-AppWindow-3
Run Code Online (Sandbox Code Playgroud)

我的第二种方法是直接操作NSWindow类似于您的底层WindowAccessor

直接控制:操作 NSWindow

您的实现WindowAccessor有一个特定的缺陷:正在读取view.window以提取NSWindow实例的块是异步运行的:在将来的某个时间(由于DispatchQueue.main.async)。
这就是为什么窗口出现在屏幕上的 SwiftUI 配置位置,然后再次消失,最终移动到您想要的位置。您需要更多的控制,这包括首先监视属性设置NSView后尽快获得通知,然后监视实例以了解视图何时变得可见。windowNSWindow

我正在使用以下WindowAccessor. 它需要一个onChange回调闭包,每当发生变化时就会调用该回调闭包window。首先,它开始监视NSViewswindow属性,以便在视图添加到窗口时获得通知。发生这种情况时,它开始监听NSWindow.willCloseNotification通知以检测窗口何时关闭。此时它将停止任何监视以避免内存泄漏。

main-AppWindow-1
main-AppWindow-2
main-AppWindow-3
Run Code Online (Sandbox Code Playgroud)

NSWindow然后可以使用此实现来尽快获取句柄。我们仍然面临的问题:我们无法完全控制窗口。我们只是监视发生的情况并可以与NSWindow实例交互。这意味着:我们可以设置位置,但我们不确切知道这应该在哪一刻发生。例如,在将视图添加到窗口后直接设置窗口框架不会产生任何影响,因为 SwiftUI 首先进行布局计算,然后决定将窗口放置在哪里。
经过一番摸索后,我开始追踪该NSWindow.isVisible房产。这允许我在窗口可见时设置位置。使用上面WindowAccessor我的ContentView实现如下所示:

NSWindow Frame main-AppWindow-1          1304 545 400 228 0 0 3008 1228
Run Code Online (Sandbox Code Playgroud)

我希望这个解决方案可以帮助其他想要在 SwiftUI 中获得更多窗口控制的人。