macOS限制窗口尺寸的实际案例
macOS限制窗口尺寸的实际案例

macOS限制窗口尺寸的实际案例

案例场景

在macOS中使用Swift的WindowGroup创建窗口时,无法直接限制窗口大小(如最小/最大尺寸),因为因为 WindowGroup 是一个比较“自动化”的窗口容器,它由系统管理窗口生命周期,缺乏对 NSWindow 的直接控制。

例如,设置ContentView限制视图的大小。

struct ContentView: View { 
    var body: some View {
        VStack {
            Text(" \(Bundle.main.displayName)")
        }
        .frame(width: 500,height: 300)
    }
}

但ContentView实际显示的尺寸是上次调整的尺寸。例如,上次将窗口调整为正方形,再次打开应用时,显示的窗口仍然是正方形。

无法通过WindowGroup实现默认尺寸以及限制窗口尺寸大小的行为。

因此,就需要借助AppDelegate来实现出窗口尺寸大小的限制。

AppDelegate设置窗口

1、在入口文件中配置AppDelegate

import SwiftUI

@main
struct ImageSlimApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        // 空 Scene,窗口由 AppDelegate 管理
        Settings {} // 占位,不弹出任何窗口
    }
}

在入口文件中导入AppDelegate,并设置空 Scene 占位。

2、AppDelegate.swift文件

import AppKit
import SwiftUI

class AppDelegate: NSObject, NSApplicationDelegate {
     var window: NSWindow!
    
    func applicationDidFinishLaunching(_ notification: Notification) { 
        let contentView = ContentView()
        window = NSWindow(
            contentRect: NSRect(x: 0, y: 0, width: 500, height: 300),
            styleMask: [.titled, .closable, .miniaturizable, .resizable], // 可以调大小
            backing: .buffered,
            defer: false)
        
        window.center()
        window.isReleasedWhenClosed = false
        window.contentView = NSHostingView(rootView: contentView)
        
        // 正确限制尺寸
        window.minSize = NSSize(width: 400, height: 250)
        window.maxSize = NSSize(width: 600, height: 350)

        window.makeKeyAndOrderFront(nil)
    }
}

通过AppDelegate创建NSWindow窗口,并配置居中、关闭窗口不被释放等参数,配置minSize和maxSize尺寸,显示窗口为主窗口。

3、SwiftUI文件

import SwiftUI
struct ContentView: View {
    
    var body: some View {
        VStack {
            Text(" \(Bundle.main.displayName)")
        }
    }
}

在ContentView中设置VStack视图的尺寸。

延伸问题

1、显示窗口尺寸过小的问题

在运行上面的代码时,实际显示的窗口尺寸非常小。

这是因为虽然窗口的宽度为500,高度为300:

contentRect: NSRect(x: 0, y: 0, width: 500, height: 100)

这是因为NSHostingView会自适应内容尺寸:

window.contentView = NSHostingView(rootView: ContentView())

因为ContentView没有设置frame强制布局,因此NSHostingView.fittingSize 就很小。

NSWindow就会根据它的尺寸,覆盖设置的minSize / contnetRect。

解决方案为:给ContentView设置固定大小:

struct ContentView: View {
    var body: some View {
        VStack {
            Text(" \(Bundle.main.displayName)")
        }
        .frame(width: 500,height: 300)
    }
}

2、尺寸未实现限制

当我们想要实现拖动窗口时,限制窗口最大、最小尺寸时,会发现在设置ContentView的frame属性后,窗口无法被拖动,同时minSize和maxSize无法实现限制。

// 正确限制尺寸
window.minSize = NSSize(width: 400, height: 250)
window.maxSize = NSSize(width: 600, height: 350)

因此,需要在ContentView中设置视图的最大和最小尺寸:

struct ContentView: View {
    var body: some View {
        VStack {
            Text(" \(Bundle.main.displayName)")
        }
        .frame(minWidth: 400,minHeight: 250)    // 限制最小尺寸
        .frame(maxWidth: 600,maxHeight: 350) // 限制最大尺寸
    }
}

注意:我这里移除了固定的frame,这是因为测试的过程中发现,如果窗口在缩小时,只能缩小到固定的frame尺寸,而不能缩小到最小尺寸,因此这里只保留了min和mac两个限制尺寸。

在AppDelegate中,也移除了window.minSize和window.maxSize两个属性,防止尺寸冲突等问题。

3、Dock栏无法弹出窗口问题

当我们使用AppDelegate手动创建窗口,而不是使用WindowGroup创建窗口时,点击Dock图标不会自动弹出窗,这是macOS应用的常规行为。

在 SwiftUI 默认使用的 WindowGroup 场景下,系统自动为管理窗口生命周期,包括:

1、应用启动自动弹出窗口。

2、从 Dock 图标点击时自动恢复上次窗口。

而当修改为使用 AppDelegate 手动创建窗口,系统就认为这是开发者自行控制窗口行为,因此不会再自动打开窗口。

解决方案:通过AppDelegate实现手动显示窗口:

func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
    if !flag {
        window.makeKeyAndOrderFront(nil)
    }
    return true
}

这段代码是 macOS App 中 AppDelegate 的一个生命周期回调方法,用于处理用户点击 Dock 图标时的行为。

代码解析

1、sender:当前的 NSApplication 实例。

2、hasVisibleWindows == false 表示当前没有显示的窗口。

3、makeKeyAndOrderFront(nil) 显示你保留的 NSWindow 实例。

实现过程

当用户点击Dock中的App图标时,macOS会调用这个方法,询问应用是否重新打开窗口。

如果用户关闭窗口后,窗口被隐藏(App未退出),调用window.makeKeyAndOrderFront(nil)显示窗口。

return true表示系统可以继续处理这个点击事件。

提示:NSWindow.isReleasedWhenClosed属性为true时,在SwiftUI中关闭窗口时窗口不被销毁。

总结

通过AppDelegate可以手动创建窗口,但是并不能管理窗口的大小和限制尺寸。

需要在SwiftUI视图中设置frame的min和max属性,实现对窗口的尺寸限制。

因为手动创建并管理创建,Dock栏无法弹出窗口,就需要使用AppDelegate 的生命周期回调方法applicationShouldHandleReopen,调用window.makeKeyAndOrderFront(nil)显示窗口。

最终实现,使用SwiftUI开发macOS应用时,设置默认窗口大小以及限制窗口尺寸的功能。

相关文章

1、macOS窗口NSWindow:https://fangjunyu.com/2025/07/01/macos%e7%aa%97%e5%8f%a3nswindow/

2、macOS应用代理AppDelegate和NSApplicationDelegate:https://fangjunyu.com/2025/06/25/macos%e5%ba%94%e7%94%a8%e4%bb%a3%e7%90%86appdelegate/

3、macOS利用NSWindowController创建窗口实现打开应用的实际案例:https://fangjunyu.com/2025/07/01/macos%e5%88%a9%e7%94%a8nswindowcontroller%e5%88%9b%e5%bb%ba%e7%aa%97%e5%8f%a3%e5%ae%9e%e7%8e%b0%e6%89%93%e5%bc%80%e5%ba%94%e7%94%a8%e7%9a%84%e5%ae%9e%e9%99%85%e6%a1%88%e4%be%8b/

完整代码

1、入口文件代码:

@main
struct ImageSlimApp: App {
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        // 空 Scene,窗口由 AppDelegate 管理
        Settings {} // 占位,不弹出任何窗口
    }
}

2、AppDelegate文件代码:

import AppKit
import SwiftUI

class AppDelegate: NSObject, NSApplicationDelegate {
    var window: NSWindow!
    var statusBarController: StatusBarController?
    
    func applicationDidFinishLaunching(_ notification: Notification) {
        statusBarController = StatusBarController()
        
        let contentView = ContentView()
        window = NSWindow(
            contentRect: NSRect(x: 0, y: 0, width: 500, height: 300),
            styleMask: [.titled, .closable, .miniaturizable, .resizable], // 可以调大小
            backing: .buffered,
            defer: false)
        
        window.center()
        window.isReleasedWhenClosed = false
        window.contentView = NSHostingView(rootView: contentView)
        
        window.makeKeyAndOrderFront(nil)
    }
    
    func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
        if !flag {
            window.makeKeyAndOrderFront(nil)
        }
        return true
    }
}

3、ContentView文件代码:

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Text(" \(Bundle.main.displayName)")
        }
        .frame(minWidth: 400,minHeight: 250)    // 限制最小尺寸
        .frame(maxWidth: 600,maxHeight: 350) // 限制最大尺寸
    }
}
   

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开发者。

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注