macOS警告/提示视图 — NSAlert、自定义WindowController

系统的NSAlert——系统自带提示视图

在'ViewController.swift'文件中,先创建一个NSAlert实例来看看~

import Cocoa

class ViewController: NSViewController {

    @objc func clickItem(btn: NSButton) {
        print("clickItem:\(btn.title)")
        
    }
    @objc func showNotice() {
        let alert = NSAlert()
        //添加的前3个项(按钮),会响应‘点击选项时系统的回调’方法——且枚举中有对应项进行确认
        alert .addButton(withTitle: "确定")
        alert .addButton(withTitle: "取消")
        let item3 = alert .addButton(withTitle: "More") //获取对应的按钮
        //item3.target = self; item3.action = #selector(clickItem)//重写方法—覆盖‘点击选项时系统的回调’方法
        //第4项(按钮)开始,会响应‘点击选项时系统的回调’方法——但枚举中没有对应项进行确认
        let item4 = alert .addButton(withTitle: "第4项")  //获取对应的按钮
        //item4.target = self; item4.action = #selector(clickItem)//重写方法—覆盖‘点击选项时系统的回调’方法
        let item5 = alert .addButton(withTitle: "第5项")  //获取对应的按钮
        //item5.target = self; item5.action = #selector(clickItem)//重写方法—覆盖‘点击选项时系统的回调’方法
        alert.messageText = "blabla操作的警告"//标题
        alert.informativeText = "blabla操作会导致XX后果,是否继续?"//详细描述
        //设置NSAlert的展示风格
        alert.alertStyle = NSAlert.Style.critical//warning\informational\critical
        alert .beginSheetModal(for: self.view .window!) { (returnCode: NSApplication.ModalResponse) in
            //点击选项时系统的回调
            switch returnCode {
            case .alertFirstButtonReturn:
                print("NSApplication.ModalResponse.alertFirstButtonReturn")
            case .alertSecondButtonReturn:
                print("NSApplication.ModalResponse.alertSecondButtonReturn")
            case .alertThirdButtonReturn:
                print("NSApplication.ModalResponse.alertThirdButtonReturn")
            //以下条件均不会到达
            case .stop:
                print("NSApplication.ModalResponse.stop")
            case .abort:
                print("NSApplication.ModalResponse.abort")
            case .`continue`:
                print("NSApplication.ModalResponse.`continue`")
            case .OK:
                print("NSApplication.ModalResponse.OK")
            case .cancel:
                print("NSApplication.ModalResponse.cancel")
            case .continue:
                print("NSApplication.ModalResponse.continue")
            default:
                print("Other situation——default")
                break
            }
        }
        
    }
    func addOneBtn() {//为该window,随便添加一个按钮
        let btn = NSButton(frame: NSMakeRect(100, 100, 100, 100))
        btn.target = self; btn.action = #selector(showNotice)
        self.view .addSubview(btn)
    }
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        self .addOneBtn()//添加按钮
        
    }

    override var representedObject: Any? {
        didSet {
        // Update the view, if already loaded.
        }
    }


}

效果:
1.点击'Button'按钮后弹出NSAlert视图!弹出该NSAlert视图(除了该NSAlert视图 进行选择)其他视图 不能进行操作~
2.前3项(按钮)——点击第1项('确定')/第2项('取消')/第3项('More')后NSAlert视图会消失并打印NSApplication.ModalResponse.alertFirstButtonReturn/NSApplication.ModalResponse.alertSecondButtonReturn/NSApplication.ModalResponse.alertThirdButtonReturn
3.从第4项(按钮)开始,点击第4项('第4项')/第5项('第5项')后NSAlert视图会消失并都打印Other situation——default

结论:添加的前3个项(按钮),会响应‘点击选项时系统的回调’方法——且枚举中有对应项进行确认!第4项(按钮)开始,会响应‘点击选项时系统的回调’方法——但枚举中没有对应项进行确认!

Tips:可通过open func addButton(withTitle title: String) -> NSButton方法,获取该项对应按钮!对其按钮重写方法覆盖‘点击选项时系统的回调’方法后,点击该项后NSAlert视图不会消失系统默认‘点击选项时系统的回调方法而是执行@objc func clickItem(btn: NSButton)方法)!

公开注释后的@objc func showNotice()方法:

@objc func showNotice() {
   let alert = NSAlert()
    //添加的前3个项(按钮),会响应‘点击选项时系统的回调’方法——且枚举中有对应项进行确认
    alert .addButton(withTitle: "确定")
    alert .addButton(withTitle: "取消")
    let item3 = alert .addButton(withTitle: "More") //获取对应的按钮
    item3.target = self; item3.action = #selector(clickItem)//重写方法—覆盖‘点击选项时系统的回调’方法
    //第4项(按钮)开始,会响应‘点击选项时系统的回调’方法——但枚举中没有对应项进行确认
    let item4 = alert .addButton(withTitle: "第4项")  //获取对应的按钮
    item4.target = self; item4.action = #selector(clickItem)//重写方法—覆盖‘点击选项时系统的回调’方法
   let item5 = alert .addButton(withTitle: "第5项")  //获取对应的按钮
    item5.target = self; item5.action = #selector(clickItem)//重写方法—覆盖‘点击选项时系统的回调’方法
    alert.messageText = "blabla操作的警告"//标题
    alert.informativeText = "blabla操作会导致XX后果,是否继续?"//详细描述
    //设置NSAlert的展示风格
    alert.alertStyle = NSAlert.Style.critical//warning\informational\critical
    alert .beginSheetModal(for: self.view .window!) { (returnCode: NSApplication.ModalResponse) in
        //点击选项时系统的回调
        switch returnCode {
        case .alertFirstButtonReturn:
            print("NSApplication.ModalResponse.alertFirstButtonReturn")
        case .alertSecondButtonReturn:
            print("NSApplication.ModalResponse.alertSecondButtonReturn")
        case .alertThirdButtonReturn:
            print("NSApplication.ModalResponse.alertThirdButtonReturn")
        //以下条件均不会到达
        case .stop:
            print("NSApplication.ModalResponse.stop")
        case .abort:
            print("NSApplication.ModalResponse.abort")
        case .`continue`:
            print("NSApplication.ModalResponse.`continue`")
        case .OK:
            print("NSApplication.ModalResponse.OK")
        case .cancel:
            print("NSApplication.ModalResponse.cancel")
        case .continue:
            print("NSApplication.ModalResponse.continue")
        default:
            print("Other situation——default")
            break
        }
    }
    
}

该项对应按钮的点击回调方法:

@objc func clickItem(btn: NSButton) {
    print("clickItem:\(btn.title)")
    
}

效果:
1.点击'Button'按钮后弹出NSAlert视图!弹出该NSAlert视图(除了该NSAlert视图 进行选择)其他视图 不能进行操作~
2.前2项(按钮)——点击第1项('确定')/第2项('取消')/后NSAlert视图会消失并打印NSApplication.ModalResponse.alertFirstButtonReturn/NSApplication.ModalResponse.alertSecondButtonReturn/NSApplication.ModalResponse.alertThirdButtonReturn
3.从第3项(按钮)开始—对应按钮添加了action响应,点击第3项('More')/第4项('第4项')/第5项('第5项')后NSAlert视图均不会消失系统默认‘点击选项时系统的回调方法)并执行@objc func clickItem(btn: NSButton)方法后分别打印clickItem:More/clickItem:第4项/clickItem:第5项


NSAlertalertStyle属性对应的效果——NSAlert.Style.warningNSAlert.Style.informationalNSAlert.Style.critical

NSAlert.Style.warning
NSAlert.Style.informational
NSAlert.Style.critical

由上面代码可以发现NSAlert实例对象是基于NSWindow对象创建的!
所以代码如下:为NSWindow类添加扩展(extension)~

 extension NSWindow {
    //创建macOS警告/提示视图——返回NSApplication.ModalResponse
    public func showAlertNotice(firstItemStr: String, secondItemStr: String, title: String, information: String, alertStyle: NSAlert.Style, completionHalder handler: @escaping ((NSApplication.ModalResponse) -> Void)) {//@escaping——去除“scaping closure captures non-escaping parameter 'handler'”警告
        let alert = NSAlert()
        alert .addButton(withTitle: firstItemStr)
        alert .addButton(withTitle: secondItemStr)
        alert.messageText = title//标题
        alert.informativeText = information//详细描述
        //设置NSAlert的展示风格
        alert.alertStyle = alertStyle//warning\informational\critical
        alert .beginSheetModal(for: self) { (returnCode: NSApplication.ModalResponse) in
            //点击选项时系统的回调
            handler(returnCode)
        }
    }
    //创建macOS警告/提示视图——返回bool值
    public func showAlertNoticeWithBool(firstItemStr: String, secondItemStr: String, title: String, information: String, alertStyle: NSAlert.Style, completionHalder handler: @escaping ((Bool) -> Void)) {//@escaping——去除“scaping closure captures non-escaping parameter 'handler'”警告
        let alert = NSAlert()
        alert .addButton(withTitle: firstItemStr)
        alert .addButton(withTitle: secondItemStr)
        alert.messageText = title//标题
        alert.informativeText = information//详细描述
        //设置NSAlert的展示风格
        alert.alertStyle = alertStyle//warning\informational\critical
        alert .beginSheetModal(for: self) { (returnCode: NSApplication.ModalResponse) in
            //点击选项时系统的回调
            if returnCode == .alertFirstButtonReturn {//点击"确定"
                handler(true)
            } else if returnCode == .alertSecondButtonReturn {//点击"取消"
                handler(false)
            }
        }
        
    }
}

方法的调用:在'ViewController.swift'文件中

//返回NSApplication.ModalResponse
self.view.window! .showAlertNotice(firstItemStr: "OK", secondItemStr: "取消", title: "标题", information: "警告的提示内容和信息", alertStyle: .critical) { (returnCode : NSApplication.ModalResponse) in
    if returnCode == .alertFirstButtonReturn {//点击"确定"
        print("returnCode == .alertFirstButtonReturn")
        //"确定"相应的操作
    } else if returnCode == .alertSecondButtonReturn {//点击"取消"
        print("returnCode == .alertSecondButtonReturn")
        //"取消"相应的操作
    }
}

//返回bool值
self.view.window! .showAlertNoticeWithBool(firstItemStr: "OK", secondItemStr: "取消", title: "标题", information: "警告的提示内容和信息", alertStyle: .critical) { (isOK: Bool) in
    if isOK == true {//点击"确定"
        print("isOK == true")
        //"确定"相应的操作
    } else {//点击"取消"
        print("isOK == false")
        //"取消"相应的操作
    }
}



自定义WindowController

新建一个自定义的GYHNoticeAlertWindowController类(需勾选'Also create XIB file for user interface'

勾选'Also create XIB file for user interface'

添加的图片素材:

图片素材

在'GYHNoticeAlertWindowController.swift'文件中,进行控件UI布局

import Cocoa

class GYHNoticeAlertWindowController: NSWindowController {
    var sureBtn: NSButton!
    var cancelBtn: NSButton!
    
    var _title: String?     //标题
    var _message: String?   //提示信息
    var _okStr: String?     //“确定”文字
    var _cancelStr: String? //“取消”文字
    var _backColor: NSColor?//背景色
    convenience init(title: String?, message: String?, backColor: NSColor?, okStr: String?, cancelStr: String?) {
        self.init(windowNibName: "GYHNoticeAlertWindowController")
        
        _title = title != nil ? title : "警告"
        _message = message != nil ? message : "提示信息的内容:blabla~"
        _okStr = okStr != nil ? okStr : "确定"
        _cancelStr = cancelStr != nil ? cancelStr : "取消"
        _backColor = backColor != nil ? backColor : NSColor(red: 190/255.0, green: 150/255.0, blue: 125/255.0, alpha: 1.0)
    }
    override func windowDidLoad() {
        super.windowDidLoad()
        
        // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
        //对窗口(self.window)进行配置
        self.window?.isRestorable = false//窗口不可拉伸
        self.window?.titlebarAppearsTransparent = true
        self.window?.title = _title!//标题
        self.window?.titleVisibility = NSWindow.TitleVisibility.hidden//隐藏窗口标题
        self.window?.styleMask = NSWindow.StyleMask.fullSizeContentView//窗口风格:顶部视图+窗口(融合)
        self.window?.isMovableByWindowBackground = true//点击背景,可移动
        //设置背景色
        self.window?.contentView?.wantsLayer = true
        self.window?.contentView?.layer?.backgroundColor = _backColor?.cgColor
        //self.window?.contentView?.layer?.cornerRadius = 5.0//无效:切圆角—边框未隐藏
        
        self .setUpAllViews()
        self .setUpTitleBarV()
        
    }
    func setUpAllViews() {  //视图布局——"确定"按钮、"取消"按钮、提示信息Label
        var window_W = self.window?.frame.size.width;
        window_W = window_W != nil ? window_W : 0.0;
        
        let margin_Of_TwoBtns = (200.0/3600.0)*window_W!;
        //"确定"按钮
        let sureBtn_W = (1400.0/3600.0)*window_W!;   let sureBtn_X = window_W!/2.0 - margin_Of_TwoBtns/2.0 - sureBtn_W;
        let sureBtn_H = (360.0/3600.0)*window_W!;    let sureBtn_Y = (150.0/3600.0)*window_W!;
        sureBtn = NSButton(frame: NSMakeRect(sureBtn_X, sureBtn_Y, sureBtn_W, sureBtn_H))
        self.window?.contentView?.addSubview(sureBtn)
        sureBtn.title = _okStr!
        sureBtn.alignment = NSTextAlignment.center
        sureBtn.bezelStyle = .circular
        sureBtn.setButtonType(NSButton.ButtonType.switch)//按钮类型(可多选)
        sureBtn.imagePosition = .imageOverlaps//图片放在最下面
        sureBtn.imageScaling = .scaleAxesIndependently//图片自动调整尺寸
        sureBtn.image = NSImage(named: "Item_Button_nor")//图片——普通状态
        sureBtn.alternateImage = NSImage(named: "Item_Button_sel")//图片——高亮状态
        //"取消"按钮
        let cancelBtn_W = (1400.0/3600.0)*window_W!;   let cancelBtn_X = window_W!/2.0 + margin_Of_TwoBtns/2.0;
        let cancelBtn_H = (360.0/3600.0)*window_W!;    let cancelBtn_Y = (150.0/3600.0)*window_W!;
        cancelBtn = NSButton(frame: NSMakeRect(cancelBtn_X, cancelBtn_Y, cancelBtn_W, cancelBtn_H))
        self.window?.contentView?.addSubview(cancelBtn)
        cancelBtn.title = _cancelStr!
        cancelBtn.alignment = NSTextAlignment.center
        cancelBtn.bezelStyle = .circular
        cancelBtn.setButtonType(NSButton.ButtonType.switch)//按钮类型(可多选)
        cancelBtn.imagePosition = .imageOverlaps//图片放在最下面
        cancelBtn.imageScaling = .scaleAxesIndependently//图片自动调整尺寸
        cancelBtn.image = NSImage(named: "Item_Button_nor")//图片——普通状态
        cancelBtn.alternateImage = NSImage(named: "Item_Button_sel")//图片——高亮状态
        
        //提示信息Label
        let tipLabel_X = 0.0;       let tipLabel_Y = self.sureBtn.frame.maxY + (110.0/2800.0)*window_W!
        let tipLabel_W = window_W!;  let tipLabel_H = (300.0/2800.0)*window_W!
        let tipContentLabel = NSTextField(frame: NSMakeRect(CGFloat(tipLabel_X), tipLabel_Y, tipLabel_W, tipLabel_H))
        self.window?.contentView?.addSubview(tipContentLabel)
        tipContentLabel.isEditable = false//不可编辑
        tipContentLabel.isBordered = false//不显示边框
        tipContentLabel.backgroundColor = .clear
        tipContentLabel.alignment = .center
        tipContentLabel.font = NSFont .boldSystemFont(ofSize: 15.0)
        tipContentLabel.textColor = .lightGray
        tipContentLabel.stringValue = _message!
    }
    func setUpTitleBarV() { //设置窗口的标题栏
        let titleBar_Height: CGFloat = 27.0
        let titleBarImgV = NSImageView()
        self.window?.contentView?.addSubview(titleBarImgV)
        //添加约束
        titleBarImgV.translatesAutoresizingMaskIntoConstraints = false;
        self.window?.contentView?.addConstraint(NSLayoutConstraint(item: titleBarImgV, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.window?.contentView, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1.0, constant: 0.0))
        self.window?.contentView?.addConstraint(NSLayoutConstraint(item: titleBarImgV, attribute: NSLayoutConstraint.Attribute.bottom, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.window?.contentView, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1.0, constant: titleBar_Height))
        self.window?.contentView?.addConstraint(NSLayoutConstraint(item: titleBarImgV, attribute: NSLayoutConstraint.Attribute.left, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.window?.contentView, attribute: NSLayoutConstraint.Attribute.left, multiplier: 1.0, constant: 0.0))
        self.window?.contentView?.addConstraint(NSLayoutConstraint(item: titleBarImgV, attribute: NSLayoutConstraint.Attribute.right, relatedBy: NSLayoutConstraint.Relation.equal, toItem: self.window?.contentView, attribute: NSLayoutConstraint.Attribute.right, multiplier: 1.0, constant: 0.0))
        titleBarImgV.image = NSImage(named: "titleBar_Img")
        titleBarImgV.imageScaling = NSImageScaling.scaleAxesIndependently//图片自动调整尺寸
        
        let nameLB_leftMargin: CGFloat = 3.0
        let nameLB_Height: CGFloat = 20.0
        let nameLabel = NSTextField()
        self.window?.contentView?.addSubview(nameLabel)
        //添加约束-简写
        nameLabel.translatesAutoresizingMaskIntoConstraints = false
        self.window?.contentView?.addConstraint(NSLayoutConstraint(item: nameLabel, attribute: .top, relatedBy: .equal, toItem: self.window?.contentView, attribute: .top, multiplier: 1.0, constant: (titleBar_Height - nameLB_Height)))
        self.window?.contentView?.addConstraint(NSLayoutConstraint(item: nameLabel, attribute: .bottom, relatedBy: .equal, toItem: self.window?.contentView, attribute: .top, multiplier: 1.0, constant: titleBar_Height))
        self.window?.contentView?.addConstraint(NSLayoutConstraint(item: nameLabel, attribute: .left, relatedBy: .equal, toItem: self.window?.contentView, attribute: .left, multiplier: 1.0, constant: nameLB_leftMargin))
        self.window?.contentView?.addConstraint(NSLayoutConstraint(item: nameLabel, attribute: .right, relatedBy: .equal, toItem: self.window?.contentView, attribute: .right, multiplier: 1.0, constant: 0.0))
        nameLabel.isBordered = false
        nameLabel.isEditable = false
        nameLabel.backgroundColor = NSColor.clear
        nameLabel.textColor = NSColor.gridColor
        nameLabel.font = NSFont .boldSystemFont(ofSize: 13.0)
        nameLabel.stringValue = _title!//标题
    }
    
}

在'ViewController.swift'文件中,进行使用:

import Cocoa

class ViewController: NSViewController, NSWindowDelegate {
    
    var noticeWC: GYHNoticeAlertWindowController?//自定义‘提示’窗口
    //事件的响应——自定义‘提示’窗口
    @objc func clickWindowButton(btn: NSButton) {
        btn.state = NSControl.StateValue.off//改为‘关闭’状态——非选中
        
        if btn == noticeWC?.sureBtn {//点击"确定"
            print("btn == noticeWC?.sureBtn")
            //"确定"相应的操作
            
            //关闭模态、关闭窗口
            NSApplication .shared .stopModal()
            self .dismissNotice()
        } else if btn == noticeWC?.cancelBtn {//点击"取消"
            print("btn == noticeWC?.cancelBtn")
            //"取消"相应的操作
            
            //关闭模态、关闭窗口
            NSApplication .shared .stopModal()
            self .dismissNotice()
        }
    }
    //创建并展示‘提示’窗口
    public func createShowNotice(with delegate: NSWindowDelegate?) {
        //判空,避免重复创建
        noticeWC = noticeWC != nil ? noticeWC : GYHNoticeAlertWindowController(title: nil, message: nil, backColor:nil, okStr: nil, cancelStr: nil)//自定义初始化方法
        noticeWC? .showWindow(nil)
        //if delegate != nil {
        //    noticeWC?.window?.delegate = delegate;
        //} //无需delegate
        noticeWC?.sureBtn?.target = self;   noticeWC?.sureBtn?.action = #selector(clickWindowButton)
        noticeWC?.cancelBtn?.target = self; noticeWC?.cancelBtn?.action = #selector(clickWindowButton)
        
        //开启模态
        NSApplication .shared .runModal(for: (noticeWC?.window)!)
    }
    //隐藏‘提示’窗口
    public func dismissNotice() {
        noticeWC? .close()//关闭窗口
        //noticeWC?.window?.delegate = nil  //无需delegate
        noticeWC = nil//置为nil
    }
    
    @objc func showNotice() {
        self .createShowNotice(with: nil)
    }

    func addOneBtn() {//为该window,随便添加一个按钮
        let btn = NSButton(frame: NSMakeRect(100, 100, 100, 100))
        btn.target = self; btn.action = #selector(showNotice)
        self.view .addSubview(btn)
    }
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        self .addOneBtn()//添加按钮
        
    }
        
    override var representedObject: Any? {
        didSet {
        // Update the view, if already loaded.
        }
    }


}

效果:
1.点击'Button'按钮后弹出自定义‘提示’窗口GYHNoticeAlertWindowController窗口)!弹出GYHNoticeAlertWindowController窗口(除了该GYHNoticeAlertWindowController窗口 进行选择)其他视图 不能进行操作~
2.点击'确定'/'取消'后该GYHNoticeAlertWindowController窗口会消失并进行其相应的打印!之后其他视图 可以进行操作~

在"Debug View hierarchy"中查看视图层次:

1.系统默认自带的窗口('NSWindowController'):

NSWindowController


2.自定义‘提示’窗口('GYHNoticeAlertWindowController'):

GYHNoticeAlertWindowController


思路如此,封装看个人~(不想去封装了)


Tip1:关于按钮字体居中颜色大小,可参考NSButton的使用(Mac端 按钮)》中“设置按钮的标题颜色及字体”部分:
其中“-(void)setTitleColorToColor:(NSColor *)color andFontNum:(CGFloat)FontNum isBold:(BOOL)isBold;”方法通过了设置按钮的attributedTitle属性(富文本)中使用[paraStyle setAlignment:NSTextAlignmentCenter];实现了字体居中~


Tip2:关于提示信息Label的文字可能超出控件范围的问题:

  • 1.可以设置其toolTip属性—鼠标放置上去就会展示出来!(效果如下)
  • 2. 《iOS\Mac OS 根据字体大小、控件宽度,计算字符串展示时尺寸!》中的“- (NSSize)sizeForLblContent:(NSString *)strContent fixMaxWidth:(CGFloat)w andFondSize:(int)fontSize;”方法!
    在《使用Mac OS控件,封装加载视图ProgressView》一文中也有使用——递归方法确定字体大小!



不想再写份Swift的代码~

Tip3:解决“顶部标题栏鼠标不能拖动边框不圆滑”的问题

self.window?.styleMask = NSWindow.StyleMask.fullSizeContentView//窗口风格:顶部视图+窗口(融合)

替换为如下代码:

self.window?.styleMask = [self.window!.styleMask, >NSWindow.StyleMask.fullSizeContentView]//窗口风格:圆滑边框、顶部视图+窗口(融合)
//获取 (系统)左侧的‘关闭’、‘最小化’、‘最大化’按钮
let closeBtn = self.window?.standardWindowButton(NSWindow.ButtonType.closeButton)
let zoomBtn = self.window?.standardWindowButton(NSWindow.ButtonType.zoomButton)
let miniaturizeBtn = self.window?.standardWindowButton(NSWindow.ButtonType.miniaturizeButton)
closeBtn?.isHidden = true       //隐藏 ‘关闭’按钮     //初始位置 X:7.0   Y:3.0
zoomBtn?.isHidden = true        //隐藏 ‘最大化’按钮     //初始位置 X:47.0   Y:3.0
miniaturizeBtn?.isHidden = true //隐藏 ‘最小化’按钮   //初始位置 X:27.0   Y:3.0

优化后效果:顶部标题栏鼠标可以拖动边框圆滑










goyohol's essay

你可能感兴趣的:(macOS警告/提示视图 — NSAlert、自定义WindowController)