英文:
NSWindowController NSHostingView based contentView: Frame size only updated with delay
问题
我有一个简单的设置,代表了我在我的MacOS应用程序中所做的事情:
// AppDelegate.swift
import Cocoa
@main
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
TestWindow()
}
func applicationWillTerminate(_ aNotification: Notification) {}
func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
}
// BareView.swift
import SwiftUI
struct BareView: View {
@ObservedObject var testState: TestState
var body: some View {
Color.white.frame(width: testState.width, height: testState.height)
}
}
// TestWindow.swift
import Foundation
import AppKit
import SwiftUI
class TestState: ObservableObject {
@Published var width: CGFloat = 100
@Published var height: CGFloat = 100
}
class TestWindow: NSWindowController {
let testState = TestState()
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init() {
super.init(window: NSWindow(
contentRect: .zero,
styleMask: [ .borderless ],
backing: .buffered,
defer: false))
self.window!.contentView = NSHostingView(rootView: BareView(testState: testState))
self.window!.setFrameOrigin(.init(x: NSScreen.main!.frame.width / 2, y: NSScreen.main!.frame.height / 2))
self.window!.makeKeyAndOrderFront(nil)
NSEvent.addLocalMonitorForEvents(matching: [.mouseMoved]) { event in
self.testState.width += 10
self.testState.height += 10
self.window!.makeKeyAndOrderFront(nil)
// behaviour only as expected if this line is added:
// self.window!.setContentSize(.init(width: self.testState.width, height: self.testState.height))
print("contentSize: \(self.testState.width)")
print("viewFrame: \(self.window!.contentView!.frame)")
return nil
}
}
}
The size of the SwiftUI View inside the NSHostingView is updated based on an EventHandler. **The SwiftUI View is actually resized and increases its size, but the contentViews frame size is not directly notified about this change**. This happens one iteration later (after the window has moved?).
So the console prints the following:
contentSize: 110.0
viewFrame: (0.0, 0.0, 100.0, 100.0)
contentSize: 120.0
viewFrame: (0.0, 0.0, 110.0, 110.0)
Once I manually update the contentViews size with setContentSize(_:) function the console output behaves as expected:
contentSize: 110.0
viewFrame: (0.0, 0.0, 110.0, 110.0)
contentSize: 120.0
viewFrame: (0.0, 0.0, 120.0, 120.0)
So it seems like Swift is recalculating the window only after the code has returned. Since the SwiftUI view has increased in size, but the Window does not know about this, the origin is not valid. This creates a bug in my app.
I can solve this problem by calling setContentSize(_:), but the information about its actual size is contained in a leave view all the way down the tree. I would need to use an ObservableObject to communicate the calculated size back to the NSWindowController.
Can someone explain to me if this is expected behaviour and if so, what happens here? There must be some kind of "window redraw cycle" that I don't know about.
<details>
<summary>英文:</summary>
I have a simple setup here representative of what I do in my MacOS app:
```Swift
// AppDelegate.swift
import Cocoa
@main
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
TestWindow()
}
func applicationWillTerminate(_ aNotification: Notification) {}
func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
}
// BareView.swift
import SwiftUI
struct BareView: View {
@ObservedObject var testState: TestState
var body: some View {
Color.white.frame(width: testState.width, height: testState.height)
}
}
// TestWindow.swift
import Foundation
import AppKit
import SwiftUI
class TestState: ObservableObject {
@Published var width: CGFloat = 100
@Published var height: CGFloat = 100
}
class TestWindow: NSWindowController {
let testState = TestState()
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init() {
super.init(window: NSWindow(
contentRect: .zero,
styleMask: [ .borderless ],
backing: .buffered,
defer: false))
self.window!.contentView = NSHostingView(rootView: BareView(testState: testState))
self.window!.setFrameOrigin(.init(x: NSScreen.main!.frame.width / 2, y: NSScreen.main!.frame.height / 2))
self.window!.makeKeyAndOrderFront(nil)
NSEvent.addLocalMonitorForEvents(matching: [.mouseMoved]) { event in
self.testState.width += 10
self.testState.height += 10
self.window!.makeKeyAndOrderFront(nil)
// behaviour only as expected if this line is added:
// self.window!.setContentSize(.init(width: self.testState.width, height: self.testState.height))
print("contentSize: \(self.testState.width)")
print("viewFrame: \(self.window!.contentView!.frame)")
return nil
}
}
}
The size of the SwiftUI View inside the NSHostingView
is updated based on an EventHandler. The SwiftUI View is actually resized and increases its size, but the contentViews frame size is not directly notified about this change. This happens one iteration later (after the window has moved?).
So the console prints the following:
contentSize: 110.0
viewFrame: (0.0, 0.0, 100.0, 100.0)
contentSize: 120.0
viewFrame: (0.0, 0.0, 110.0, 110.0)
Once I manually update the contentViews size with setContentSize(_:)
function the console output behaves as expected:
contentSize: 110.0
viewFrame: (0.0, 0.0, 110.0, 110.0)
contentSize: 120.0
viewFrame: (0.0, 0.0, 120.0, 120.0)
So it seems like Swift is recalculating the window only after the code has returned. Since the SwiftUI view has increased in size, but the Window does not know about this, the origin is not valid. This creates a bug in my app.
I can solve this problem by calling setContentSize(_:)
, but the information about its actual size is contained in a leave view all the way down the tree. I would need to use an ObservableObject
to communicate the calculated size back to the NSWindowController
.
Can someone explain to me if this is expected behaviour and if so, what happens here? There must be some kind of "window redraw cycle" that I don't know about.
答案1
得分: 2
如果您希望在窗口调整大小时立即捕获它,您可以尝试:
.onReceive(NotificationCenter.default.publisher(for: NSWindow.didResizeNotification)) { newValue in
if let size = window?.frame.size {
windowWidth = size.width
windowHeight = size.height
}
}
英文:
If you're looking to capture window resizing as it happens, you could try:
.onReceive(NotificationCenter.default.publisher(for: NSWindow.didResizeNotification)) { newValue in
if let size = window?.frame.size {
windowWidth = size.width
windowHeight = size.height
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论