系统的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项!
NSAlert的
alertStyle
属性对应的效果——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')
添加的图片素材:
在'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'):
2.自定义‘提示’窗口('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
优化后的效果:顶部标题栏鼠标可以拖动、边框圆滑