使用 SwiftUI WindowsGroup 关闭后恢复 macOS 窗口大小

问题描述 投票:0回答:4

默认情况下,在使用 SwiftUI 的 macOS 应用程序上,窗口关闭后窗口大小不会恢复。

有没有办法在关闭应用程序之前保留用户给出的任何大小和位置。本质上,我希望 关闭和打开 的行为方式与用户 退出并打开 应用程序时的行为相同?

enter image description here

这里有什么需要补充的吗?

import SwiftUI

@main
struct testApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
swift macos swiftui
4个回答
6
投票

我找到了一种解决这种情况的方法,我们将显示/隐藏它,而不是关闭此窗口。

这是我在应用程序中的做法

@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
    }
}

enter image description here


2
投票

对我来说,这两种建议的方法都无法在应用程序重新启动时恢复窗口框架。 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")
    }
}

1
投票

好吧,我已经尝试过 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
方法。

这是一个解决方案演示,您可以看到应用程序以相同的位置和大小恢复:

使用默认 Xcode 项目 Hello World 链接到 Demo


0
投票

我最终以这种方式解决了这个问题:

我使我的 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")
   }
}
© www.soinside.com 2019 - 2024. All rights reserved.