有一个 fullSizeContentView NSWindow 采用 SwiftUI 视图的大小

nar*_*duk 5 appkit nswindow swiftui

我想要一个带有 fullSizeContentView 的 NSWindow 来获取具有内在内容大小的 SwiftUI 视图的确切大小。我看到过类似的帖子但它们的不同之处在于,在顶层提供一个固定框架就可以了。我不想这样做,我希望窗口大小与视图的大小完全相同。我怎样才能做到这一点?

\n

这是在 Xcode 14.1 中运行的 Playground 代码片段。

\n
import AppKit\nimport SwiftUI\n\nclass MyWindow: NSWindow {\n    override func setFrame(_ frameRect: NSRect, display flag: Bool) {\n        print("\\(Date().timeIntervalSince1970) setFrame called \\(frameRect)")\n        super.setFrame(frameRect, display: flag)\n    }\n}\n\nlet window = MyWindow()\n\nwindow.styleMask = [\n    .titled,\n    .closable,\n    .resizable,\n    .fullSizeContentView\n]\n\nwindow.toolbar = nil\n\nwindow.titlebarAppearsTransparent = true\nwindow.titleVisibility = .hidden\nwindow.isMovable = true\nwindow.isMovableByWindowBackground = true\nwindow.standardWindowButton(.closeButton)?.isHidden = false\nwindow.standardWindowButton(.miniaturizeButton)?.isHidden = true\nwindow.standardWindowButton(.zoomButton)?.isHidden = true\n\nprint("\\(Date().timeIntervalSince1970) Before content \\(window.frame)")\nwindow.contentView = NSHostingView(rootView: ContentView())\nprint("\\(Date().timeIntervalSince1970) After setting content \\(window.frame)")\n\nwindow.makeKeyAndOrderFront(nil)\n\nprint("\\(Date().timeIntervalSince1970) After makeKeyAndOrderFront \\(window.frame)")\n\nDispatchQueue.main.asyncAfter(deadline: .now() + 1) {\n    print("\\(Date().timeIntervalSince1970) After 1 second \\(window.frame)")\n}\n\nstruct ContentView: View {\n    var body: some View {\n        Text("Hello")\n            .font(.system(size: 200))\n            .background(.blue)\n            .fixedSize()\n            .ignoresSafeArea()\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

问题是它在末尾留下了一些空间。为什么这段代码会有这样的行为?\n窗口捕捉

\n

它打印出这个:

\n
1674086812.362426 setFrame called (100.0, 100.0, 100.0, 100.0)\n1674086812.363435 Before content (100.0, 100.0, 100.0, 100.0)\n1674086812.373186 setFrame called (100.0, -63.0, 431.0, 263.0)\n1674086812.3741732 After setting content (100.0, -63.0, 431.0, 263.0)\n1674086812.374618 setFrame called (100.0, 85.0, 431.0, 263.0)\n1674086812.375651 After makeKeyAndOrderFront (100.0, 85.0, 431.0, 263.0)\n1674086812.4359 setFrame called (100.0, 57.0, 431.0, 291.0)\n1674086813.41998 After 1 second (198.0, 99.0, 431.0, 291.0)\n
Run Code Online (Sandbox Code Playgroud)\n

为什么 SwiftUI 在显示框架后将其设置为不同的大小?

\n

nar*_*duk 3

好吧,在花了很多时间解决这个问题之后,我想我找到了一个解决方法。我们的想法是完全避免ignoreSafeArea看起来有问题的情况。为此,我通过扩展 NSHostingView 并覆盖安全区域相关行为来抑制 AppKit 端的安全区域行为。这是代码。

import AppKit
import SwiftUI

class MyWindow: NSWindow {
    override func setFrame(_ frameRect: NSRect, display flag: Bool) {
        print("\(Date().timeIntervalSince1970) setFrame called \(frameRect)")
        super.setFrame(frameRect, display: flag)
    }
}

let window = MyWindow()

window.styleMask = [
    .titled,
    .closable,
    .resizable,
    .fullSizeContentView
]

window.titlebarAppearsTransparent = true
window.titleVisibility = .hidden
window.isMovable = true
window.isMovableByWindowBackground = true
window.standardWindowButton(.closeButton)?.isHidden = false
window.standardWindowButton(.miniaturizeButton)?.isHidden = true
window.standardWindowButton(.zoomButton)?.isHidden = true

class NSHostingViewSuppressingSafeArea<T : View>: NSHostingView<T> {
    required init(rootView: T) {
        super.init(rootView: rootView)

        addLayoutGuide(layoutGuide)
        NSLayoutConstraint.activate([
            leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor),
            topAnchor.constraint(equalTo: layoutGuide.topAnchor),
            trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor),
            bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor)
        ])
    }

    private lazy var layoutGuide = NSLayoutGuide()

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override var safeAreaRect: NSRect {
        print ("super.safeAreaRect \(super.safeAreaRect)")
        return frame
    }

    override var safeAreaInsets: NSEdgeInsets {
        print ("super.safeAreaInsets \(super.safeAreaInsets)")
        return NSEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
    }

    override var safeAreaLayoutGuide: NSLayoutGuide {
        print ("super.safeAreaLayoutGuide \(super.safeAreaLayoutGuide)")
        return layoutGuide
    }

    override var additionalSafeAreaInsets: NSEdgeInsets {
        get {
            print ("super.additionalSafeAreaInsets \(super.additionalSafeAreaInsets)")
            return NSEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
        }

        set {
            print("additionalSafeAreaInsets.set \(newValue)")
        }
    }
}

print("\(Date().timeIntervalSince1970) Before content \(window.frame)")
window.contentView = NSHostingViewSuppressingSafeArea(rootView: ContentView())
print("\(Date().timeIntervalSince1970) After setting content \(window.frame)")

window.makeKeyAndOrderFront(nil)

print("\(Date().timeIntervalSince1970) After makeKeyAndOrderFront \(window.frame)")

DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
    print("\(Date().timeIntervalSince1970) After 1 second \(window.frame)")
}

struct ContentView: View {
    var body: some View {
        Text("Hello")
            .font(.system(size: 200))
            .fixedSize()
            .background(.blue)
    }
}

Run Code Online (Sandbox Code Playgroud)

结果是: 在此输入图像描述

它打印出这个:

1675110465.322774 setFrame called (100.0, 100.0, 100.0, 100.0)
1675110465.332483 Before content (100.0, 100.0, 100.0, 100.0)
super.additionalSafeAreaInsets NSEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
super.safeAreaInsets NSEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
super.additionalSafeAreaInsets NSEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
super.safeAreaInsets NSEdgeInsets(top: 28.0, left: 0.0, bottom: 0.0, right: 0.0)
super.safeAreaInsets NSEdgeInsets(top: 28.0, left: 0.0, bottom: 0.0, right: 0.0)
1675110465.3494139 setFrame called (100.0, -35.0, 431.0, 235.0)
super.additionalSafeAreaInsets NSEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
super.safeAreaInsets NSEdgeInsets(top: 28.0, left: 0.0, bottom: 0.0, right: 0.0)
super.additionalSafeAreaInsets NSEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
super.safeAreaInsets NSEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
super.additionalSafeAreaInsets NSEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
super.safeAreaInsets NSEdgeInsets(top: 28.0, left: 0.0, bottom: 0.0, right: 0.0)
super.safeAreaInsets NSEdgeInsets(top: 28.0, left: 0.0, bottom: 0.0, right: 0.0)
super.safeAreaInsets NSEdgeInsets(top: 28.0, left: 0.0, bottom: 0.0, right: 0.0)
1675110465.3513222 After setting content (100.0, -35.0, 431.0, 235.0)
1675110465.352477 setFrame called (100.0, 85.0, 431.0, 235.0)
1675110465.3534908 After makeKeyAndOrderFront (100.0, 85.0, 431.0, 235.0)
super.additionalSafeAreaInsets NSEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
super.additionalSafeAreaInsets NSEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
additionalSafeAreaInsets.set NSEdgeInsets(top: 28.0, left: 0.0, bottom: 0.0, right: 0.0)
1675110466.401649 After 1 second (636.0, 490.0, 431.0, 235.0)

Run Code Online (Sandbox Code Playgroud)