如何在 SwiftUI 中获取窗口坐标



我想制作一个视差背景视图,当窗口在屏幕上移动时,UI 后面的图像几乎保持静止。要在macOS上执行此操作,我想获取窗口的坐标。如何获取窗口的坐标?


我问这个是因为我找不到任何地方说明如何做到这一点:

  • 谷歌搜索帮助我找到了以下结果:

    SwiftUI 窗口坐标、SwiftUI 窗口位置、SwiftUI 窗口获取框架、SwiftUI 获取窗口、SwiftUI macOS 窗口
    • 坐标、SwiftUI macOS 窗口位置、SwiftUI macOS 窗口获取框架、SwiftUI macOS 获取窗口
  • 苹果开发者文档:

    • GeometryReader- 我曾希望这将包含一个 API 来为我提供系统坐标空间中的框架,但似乎它包含的所有方法都只引用窗口内的坐标
    • 创建一个macOS应用程序 - SwiftUI教程 - 我希望Apple会在这里提到窗口,但它根本没有提到,除了说你可以在Xcode预览窗格中预览主窗口的内容。
    • 无果搜索:SwiftUI 窗口坐标、SwiftUI 窗口
    • 位置、SwiftUI 窗口获取框架
  • 其他 SO 问题:

    如何在 SwiftUI
    • 视图中访问自己的窗口? - 我很乐观地认为这将有一个答案,它将为我提供一个 SwiftUI API 来访问窗口,但它使用填充程序来访问 AppKit 窗口表示形式。
    • 使用 SwiftUI 定义 macOS 窗口大小 - 与上述问题类似的希望,但这次的答案只是读取内容视图的框架,该框架始终具有(0, 0)来源
    • SwiftUI 坐标空间 - 我希望这个答案能让我知道如何将GeometryReader给出的坐标转换为屏幕坐标,但不幸的是坐标再次被限制在窗口内
  • 在网络上的其他地方:

    SwiftUI for Mac -
    • TrozWare 的第 2 部分 - 我希望这能给我一些在 Mac 上使用 SwiftUI 的技巧,例如与窗互,因为大多数教程都专注于 iOS/iPadOS。不幸的是,尽管它有很多关于 SwiftUI 如何使用窗口的好信息,但它没有关于与这些窗互或解析这些窗口本身的信息。
    • Alexander Grebenyuk的SwiftUI布局系统 - 希望在屏幕内进行窗口布局,但这都是全屏iOS应用程序
    • SwiftUI by Example by Hacking with Swift - 希望有一个关于如何获取窗口位置的示例,但在列出的示例中似乎根本没有真正提到窗口

正如我列出的,我发现所有这些要么与我的问题无关,要么只引用窗口中的坐标,而不是窗口在屏幕内的坐标。有些人提到了浸入AppKit的方法,但如果可能的话,我想避免这种情况。

我得到的最接近的是尝试使用这样的GeometryReader

GeometryReader { geometry in
Text(verbatim: "(geometry.frame(in: .global))")
}

但原点总是(0, 0),尽管当我调整窗口时大小确实发生了变化。


我设想的可能是这样的:

public struct ParallaxBackground<Background: View>: View {
var background: Background
@Environment(.windowFrame)
var windowFrame: CGRect
public var body: some View {
background
.offset(x: windowFrame.minX / 10,
y: windowFrame.minY / 10)
}
}

.windowFrame不是真实的;它不指向EnvironmentValues上的任何键路径。我找不到我会在哪里获得这样的值。

截至今天,我们已经广泛部署/安装了macOS 12,而SwiftUI尚未获得适用于macOS窗口的适当模型。从我目前对macOS 13的了解来看,这个窗口也不会有SwiftUI模型。

今天(从 macOS 11 开始),我们不再在AppDelegate中打开窗口,但现在使用WindowGroup场景修改器定义窗口:

@main
struct HandleWindowApp: App {
var body: some Scene {
WindowGroup(id: "main") {
ContentView()
}
}
}

但是没有标准的方式来控制或访问底层窗口(例如NSWindow)。要在stackoverflow上完成此操作,建议使用WindowAccessor,该在ContentView后台安装NSView,然后访问其window属性。我还编写了我的版本来控制窗口的位置。 在您的情况下,获取NSWindow实例的句柄,然后观察NSWindow.didMoveNotification就足够了。每当窗口移动时,都会调用它。

如果您的应用仅使用单个窗口(例如,您以某种方式禁止用户可以创建多个窗口),您甚至可以全局观察帧位置:

NotificationCenter.default
.addObserver(forName: NSWindow.didMoveNotification, object: nil, queue: nil) { (notification) in
if let window = notification.object as? NSWindow,
type(of: window).description() == "SwiftUI.SwiftUIWindow"
{
print(window.frame)
}
}

如果需要窗口框架:

SceneDelegate 会跟踪所有窗口,因此您可以使用它来制作具有对其帧的引用的EnvironmentObject,并将其传递给您的视图。更新委托方法中的环境对象值:func windowScene(_ windowScene: UIWindowScene, didUpdate previousCoordinateSpace: UICoordinateSpace, ...

如果它是一个单窗口应用程序,则要简单得多。您可以在视图中使用 UIScreen.main.bounds(如果全屏)或计算变量:

var frame: CGRect { (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window?.frame ?? .zero }

但是,如果您要在窗口中查找视图的框架,请尝试如下操作:

struct ContentView: View {
@State var frame: CGRect = .zero
var orientationChangedPublisher = NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)
var body: some View {
VStack {
Text("text frame georeader (frame.debugDescription)")
}
.background(GeometryReader { geometry in
Color.clear // .edgesIgnoringSafeArea(.all) // may need depending
.onReceive(self.orientationChangedPublisher.removeDuplicates()) { _ in
self.frame = geometry.frame(in: .global)
}
})
}
} 

但话虽如此,通常你不需要绝对框架。对齐参考线可让您相对于彼此放置物品。

对于 macOS 应用程序,使用帧更改通知并作为环境对象传递到 SwiftUI 视图

class WindowInfo: ObservableObject {
@Published var frame: CGRect = .zero
}
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
let windowInfo = WindowInfo()
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
.environmentObject(windowInfo)
// Create the window and set the content view. 
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window")
window.contentView = NSHostingView(rootView: contentView)
window.contentView?.postsFrameChangedNotifications = true
window.makeKeyAndOrderFront(nil)
NotificationCenter.default.addObserver(forName: NSView.frameDidChangeNotification, object: nil, queue: nil) { (notification) in
self.windowInfo.frame = self.window.frame
}
}
struct ContentView: View {
@EnvironmentObject var windowInfo: WindowInfo
var body: some View {
Group  {
Text("Hello, World! (windowInfo.frame.debugDescription)")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}

最新更新