默认情况下,在使用 SwiftUI 的 macOS 应用程序上,窗口关闭后窗口大小不会恢复。
有没有办法在关闭应用程序之前保留用户给出的任何大小和位置。本质上,我希望 关闭和打开 的行为方式与用户 退出并打开 应用程序时的行为相同?
这里有什么需要补充的吗?
import SwiftUI
@main
struct testApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
我找到了一种解决这种情况的方法,我们将显示/隐藏它,而不是关闭此窗口。
这是我在应用程序中的做法
@main
struct PulltodoApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
}
class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
let mainWindow = NSApp.windows[0]
mainWindow?.delegate = self
}
func windowShouldClose(_ sender: NSWindow) -> Bool {
NSApp.hide(nil)
return false
}
}
对我来说,这两种建议的方法都无法在应用程序重新启动时恢复窗口框架。 SwiftUI 总是会重置它。
因此,我最终手动保存并从用户默认值恢复窗口框架:
func applicationDidFinishLaunching(_ notification: Notification) {
// Set window delegate so we get close notifications
NSApp.windows.first?.delegate = self
// Restore last window frame
if let frameDescription = UserDefaults.standard.string(forKey: "MainWindowFrame") {
// To prevent the window from jumping we hide it
mainWindow.orderOut(nil)
Task { @MainActor in
// Setting the frame only works after a short delay
try? await Task.sleep(for: .seconds(0.5))
mainWindow.setFrame(from: frameDescription)
// Show the window
mainWindow.makeKeyAndOrderFront(nil)
}
}
}
func windowShouldClose(_ sender: NSWindow) -> Bool {
if let mainWindow = NSApp.windows.first {
UserDefaults.standard.set(mainWindow.frameDescriptor, forKey: "MainWindowFrame")
}
return true
}
func applicationWillTerminate(_ notification: Notification) {
if let mainWindow = NSApp.windows.first {
UserDefaults.standard.set(mainWindow.frameDescriptor, forKey: "MainWindowFrame")
}
}
好吧,我已经尝试过 Mark G 解决方案并且它有效,但我的应用程序菜单隐藏在 macOS 顶部菜单栏中。
所以,我已经找到了这个解决方案:
@main
struct TestingApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
let mainWindow = NSApp.windows.first
mainWindow?.delegate = self
}
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
let mainWindow = NSApp.windows.first
if flag {
mainWindow?.orderFront(nil)
} else {
mainWindow?.makeKeyAndOrderFront(nil)
}
return true
}
}
在这种情况下,我们需要将主 Window 委托设置为
NSWindowDelegate
,并且 windowShouldClose
的默认实现是 true
。
当您关闭应用程序并从扩展坞中选择应用程序图标时,它不会打开。
所以你需要实现 applicationShouldHandleReopen
方法。
这是一个解决方案演示,您可以看到应用程序以相同的位置和大小恢复:
我最终以这种方式解决了这个问题:
我使我的 AppDelegate 符合 NSWindowDelegate,这样我就可以访问一些窗口生命周期方法。
每当
windowWillClose
触发时,我都会将窗口位置和大小保存到 UserDefaults。
最初,我尝试使用
windowDidBecomeVisible
作为窗口再次打开(关闭后)的代理,但它没有触发。
相反,我必须使用
windowDidBecomeKey
。因此,每次 windowDidBecomeKey 运行时,我都会从 UserDefaults 中获取大小和位置,并使用 window.setFrame 来设置位置。
因为每当窗口失去焦点和重新聚焦时(例如在 cmd-tab 期间), windowDidBecomeKey 也会运行,所以我必须为
windowWasClosed
创建一个标志,以便仅在 windowDidBecomeKey 表示新窗口打开时触发我的大小更新,而不仅仅是它返回到聚焦。
对我来说,这导致窗口大小对用户来说是即时改变的。由于
windowDidBecomeKey
也在首次启动时运行,因此它也会从冷启动中恢复大小和位置。
这是完整的代码片段,以防将来其他人发现它有用:
class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
var window: NSWindow!
var windowWasClosed = false
@objc func windowDidBecomeKey(_ notification: Notification) {
if let window = notification.object as? NSWindow {
if windowWasClosed {
let windowOriginX = UserDefaults.standard.double(forKey: "windowOriginX")
let windowOriginY = UserDefaults.standard.double(forKey: "windowOriginY")
let windowWidth = UserDefaults.standard.double(forKey: "windowWidth")
let windowHeight = UserDefaults.standard.double(forKey: "windowHeight")
var frame = window.frame
frame.origin.x = windowOriginX
frame.origin.y = windowOriginY
frame.size.width = windowWidth
frame.size.height = windowHeight
window.setFrame(frame, display: true)
windowWasClosed = false
}
}
}
@objc func windowWillClose(_ notification: Notification) {
if let window = notification.object as? NSWindow {
windowWasClosed = true
saveWindowPositionAndSize(window)
}
}
func saveWindowPositionAndSize(_ window: NSWindow) {
let windowFrame = window.frame
UserDefaults.standard.set(windowFrame.origin.x, forKey: "windowOriginX")
UserDefaults.standard.set(windowFrame.origin.y, forKey: "windowOriginY")
UserDefaults.standard.set(windowFrame.size.width, forKey: "windowWidth")
UserDefaults.standard.set(windowFrame.size.height, forKey: "windowHeight")
}
}