MacOS学习(二) 常用组件

Demo地址

在了解这些控件之前,我们需要了解一下AppKit的坐标系。和UIKit中有所不同的是,AppKit的原点位于右下角。向上/右延伸。


MacOS学习(二) 常用组件_第1张图片
Snip20171225_8.png
NSView

先了解一下NSView中的常用的属性方法:

frame:返回控件相对于父控件的位置(以上图为例:frame=(10, 10, 15, 10))

bounds:返回控件相对于自身的位置(以上图为例:frame=(0, 0, 15, 10))

needsDisplay:在当前控件需要重绘时,重新绘制当前控件

window:返回当前控件所在的window对象

draw(_:):绘制当前控件(这个方法一般极少被手动调用,我们一般使用needsDisplay)

NSView和UIView中最大的不同就是系统不会默认为其创建图层(layer),可能更对的是Apple对于性能的考虑,毕竟没有就不用绘制了┑( ̄Д  ̄)┍,减轻了C/GPU的压力。但是这就意味着我们办法像UIView中一样,肆意把玩layer,做各种动画什么的。不过苹果提供了一个属性--wantsLayer,当我们需要layer做一些事情的时候,只需要将其修改为true即可(默认是false)。

下面就是一段最常用的修改View背景颜色的代码:

let v = NSView.init()
v.frame = CGRect.init(x: 10, y: 10, width: 100, height: 100)
v.wantsLayer = true
v.layer?.backgroundColor = NSColor.yellow.cgColor
view.addSubview(v)
NSButton

NSButton和UIButton使用区别还是很大的,NSButton有很多的系统自带的样式,通过ButtonType和BezelStyle来设置。但是需要配合着使用,有的搭配是无效的。其实没什么好讲的,看一下Demo中的组合列表就可以了解了。

以下列出一些常用属性:

btn.title = title                                   // 按钮文字
btn.image = image                                   // 按钮图片
btn.action = selector                               // 按钮触发的方法
btn.alternateTitle = ""                             // 开启状态文字
btn.alternateImage = image                          // 开启状态图片
btn.state = .on                                     // 按钮的状态
/**
    noImage         不显示图片
    imageOnly       仅显示图片
    imageLeft       图片在文字左侧
    imageRight      图片在文字右侧
    imageBelow      图片在文字下方
    imageAbove      图片在文字上方
    imageOverlaps   图片和文字重叠
*/
btn.imagePosition = .imageBelow                     // 图文位置
btn.imageScaling = .scaleProportionallyDown         // 设置图片缩放
btn.isBordered = true                               // 按钮是否有边框
btn.isTransparent = true                            // 按钮是否透明
// 以下设置的快捷键为: Shift + Command + I (如果设置的和系统的冲突,则不会触发)
btn.keyEquivalent = "I"                             // 快捷键
btn.keyEquivalentModifierMask = [.shift, .command]  // 快捷键掩码
btn.highlight(true)                                 // 按钮是否为高亮

NSImageView

在MacOS中,推荐ImageView只做展示,如果你要做用户交互,官方推荐使用NSButton。

/**
scaleProportionallyDown     原有尺寸
scaleAxesIndependently      图片按ImageView尺寸等比拉伸
scaleProportionallyUpOrDown 图片拉伸到ImageView尺寸
scaleNone                   默认尺寸(原有)
*/
imageView.imageScaling = .scaleProportionallyDown       // 图片缩放类型
/**
图片位置(imageScaling=scaleProportionallyUpOrDown时无效)
alignCenter         居中
alignTop            居中置顶
alignTopLeft        靠左置顶
alignTopRight       靠右置顶
alignLeft           居左
alignBottom         底部
alignBottomLeft     底部靠左
alignBottomRight    底部靠右
alignRight          居右
*/
imageView.imageAlignment = .alignBottom     // 图片对齐方式
/**
none                无样式
photo               照片样式
grayBezel
groove
button
具体的样式可以修改值看看 - - 语言不好形容
*/
imageView.imageFrameStyle = .button         // 边框样式 
imageView.isEditable = true                 // 是否支持编辑(编辑,复制,剪切,拖拽等)
imageView.allowsCutCopyPaste = true         // 图片支持剪切复制
imageView.animates = true                   // 支持动图
imageView.focusRingType = .none             // 获取焦点时状态
// NSImageView编辑时(修改,拖拽等)触发的方法
imageView.target = self
imageView.action = #selector(imageViewAction(sender:))

imageFrameStyle和背景色会产生冲突,优先级高于背景色

imageView.imageFrameStyle = .button
// 下面的代码不会生效
imageView.imageView.wantsLayer = true
imageView.layer?.backgroundColor = NSColor.yellow.cgColor

系统为我们提供了一些默认的图片可供使用:

imageView.image = NSImage.init(named: .touchBarMailTemplate)    // NSImage.Name.

imageView.imageView.wantsLayer = true
imageView.layer?.backgroundColor = NSColor.yellow.cgColor


系统为我们提供了一些默认的图片可供使用:

```swift
imageView.image = NSImage.init(named: .touchBarMailTemplate)    // NSImage.Name.

NSTextField

在MacOS中没有类似于iOS的UILable控件,而是使用NSTextField实现。

接下来我们先模拟出一个MacOS中的`UILabel`:

lbl.stringValue = "I`m value"                   // 设置文本
lbl.isEditable = false                          // 是否支持编辑
lbl.isBordered = false                          // 是否有边框
lbl.backgroundColor = NSColor.clear             // 背景色
lbl.textColor = NSColor.black                   // 文字颜色
lbl.maximumNumberOfLines = 0                    // 是否支持多行 0为不限制行数

其他的常用属性

lbl.placeholderString = "占位文字"
// 属性文字 这个会在后面系统研究 这里仅做了解
let attr = NSMutableAttributedString.init(string: "噜噜噜")
attr.addAttributes([NSAttributedStringKey.foregroundColor:NSColor.red], range: NSRange.init(location: 0, length: 2))
lbl.attributedStringValue = attr
// 限制文本格式 如果输入的文本与定义的格式不符,焦点会始终停留在该TextField上
let formatter = NumberFormatter.init()
formatter.numberStyle = .decimal
lbl.formatter = formatter

lbl.delegate = self

这里介绍一下NSTextFieldDelegate的代理方法

// TextField 获取到焦点并开始编辑
override func controlTextDidBeginEditing(_ obj: Notification) {}
// TextField 文本发生变化
override func controlTextDidChange(_ obj: Notification) {}
// TextField 失去焦点结束编辑
override func controlTextDidEndEditing(_ obj: Notification) {}
// 验证内容 会在TextField失去焦点的时候触发
func control(_ control: NSControl, isValidObject obj: Any?) -> Bool {
// 监听 回车,删除,ESC 等 的输入
func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {}
// 文本不符合规则时 是否允许失去焦点 true 允许 false 不允许
func control(_ control: NSControl, didFailToFormatString string: String, errorDescription error: String?) -> Bool

在iOS开发中,我们需要密码输入框只需要设置一个属性即可,但是在MacOS中,密码输入框是NSTextField的子类:NSSecureTextField

let slbl = NSSecureTextField.init(string: "123456")
slbl.frame = CGRect.init(x: 10, y: 100, width: 120, height: 40)
view.addSubview(slbl)

NSTextView

和NSTextField有很多类似的地方,我们先来看看他和NSTextField的区别

  • 父类不同

    NSTextField继承自NSControl

    NSTextView继承自NSText

  • 对特定键盘符号响应不同

    • Enter键

      NSTextField 结束编辑

      NSTextView 换行

    • Tab键

      NSTextField 焦点进入下一个控件

      NSTextView 退格

  • 对特定符号显示不同

    • "符号(以下的正常显示为中英文情况都可正常显示)

      NSTextField 可正常显示

      NSTextView 英文的"会转换为中文的

总结来说,NSTextField提供的是简单的文本输入,而NSTextView提供更复杂的文本输入。

下面介绍一下NSTextView的常用属性

txtV.isAutomaticQuoteSubstitutionEnabled = false                // 关闭自动转换引号
txtV.font = NSFont.systemFont(ofSize: 14)                       // 文字样式
txtV.textColor = NSColor.red                                    // 文字颜色
txtV.backgroundColor = NSColor.yellow                           // 背景色
txtV.textContainerInset = NSSize.init(width: 10, height: 10)    // 设置上下左右边距(width:左右 height:上下)
// 属性文字
let attr = NSMutableAttributedString.init(string: "噜噜噜")
attr.addAttributes([NSAttributedStringKey.foregroundColor:NSColor.red], range: NSRange.init(location: 0, length: 2))
txtV.textStorage?.setAttributedString(attr)

txtV.delegate = self                                            // 代理

下面介绍一下NSTextViewDelegate代理的方法

// 监听文本改变
func textDidChange(_ notification: Notification) {}
// 监听 回车,删除,ESC 等 的输入
func textView(_ textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {}

NSAlert

这个弹窗就是NSAlert

MacOS学习(二) 常用组件_第2张图片
Snip20171228_28.png

来看看常见属性

alertV.icon = NSImage.init(named: NSImage.Name(rawValue: "1"))          // 弹出图片
alertV.messageText = "messageText"                                      // 弹窗标题
alertV.informativeText = "informativeText"                              // 弹窗信息
/**
critical        警告
informational   描述
warning         严重
*/
alertV.alertStyle = .informational                                      // 弹窗类型
alertV.showsHelp = true                                                 // 左下角显示帮助按钮
alertV.showsSuppressionButton = true                                    // 显示默认的勾选按钮
// alertV.suppressionButton?.state  这个可以获取到勾选按钮状态
alertV.beginSheetModal(for: NSApp.mainWindow!) { (reutrnCode) in }      // 用户点击弹窗按钮
// 添加按钮 注:当按钮过多,AlertView会自动增加宽度,按钮点击回调`reutrnCode`从1000开始计
alertV.addButton(withTitle: "巴扎黑")                                    
// 我们可以自定义AlertView:    alertV.window.contentView? 这个可以获取到弹框面板,但最好不要移除自带控件,否则会报约束错误,建议只做隐藏

NSAlertDelegate没有实现什么方法,只有一个监听点击帮助按钮的

func alertShowHelp(_ alert: NSAlert) -> Bool {}

NSPopover

这个弹窗就是Popover的样式


MacOS学习(二) 常用组件_第3张图片
Snip20171228_30.png

下面看看常见属性

popover.appearance = NSAppearance.init(named: .vibrantLight) // 弹窗样式
popover.contentViewController = popovc
/**
behavior在样式上没什么区别,只是对不同的用户操作有不同的响应
applicationDefined popover怎么拖拽点击都不会消失
semitransient 点击除contentViewController之外的区域,popover会消失,拖动窗口不会消失
transient  点击除contentViewController之外的区域,popover会消失,拖动窗口会消失
*/
popover.behavior = .transient
/**
计算方法
sender.(minX,minY,maxX,maxY) + sender.bounds.(minX,minY,maxX,maxY)
*/
popover.show(relativeTo: view.bounds, of: view, preferredEdge: .maxX)

介绍一下NSPopoverDelegate

// popover是否能被关闭
func popoverShouldClose(_ popover: NSPopover) -> Bool {}
// popover即将显示
func popoverWillShow(_ notification: Notification) {}
// popover已经显示
func popoverDidShow(_ notification: Notification) {}
// popover即将关闭
func popoverWillClose(_ notification: Notification) {}
// popover已经关闭
func popoverDidClose(_ notification: Notification) {}
// 拖拽popover能出现单独窗口 (一个又透明度的View)
func popoverShouldDetach(_ popover: NSPopover) -> Bool {}
// 拖拽popover能出现单独窗口 (拖拽后会展示返回的Window)
func detachableWindow(for popover: NSPopover) -> NSWindow? {}

NSMenu

先来实现一个按钮右键菜单

let rbtn = NSButton.init(title: "rBtn", target: self, action: #selector(rbtnAction(sender:)))
// 创建菜单
let menu = NSMenu.init(title: "menu_01")
// 创建菜单项
let menuItem_01 = NSMenuItem.init(title: "menu_01_01", action: #selector(menuItem_01Action(item:)), keyEquivalent: "")
let menuItem_02 = NSMenuItem.init(title: "menu_01_02", action: #selector(menuItem_02Action(item:)), keyEquivalent: "")
// 在菜单中添加菜单项
menu.addItem(menuItem_01)
menu.addItem(menuItem_02)
// 按钮的菜单指向我们创建的菜单
rbtn.menu = menu

接着是一个左键菜单

// 在btn上弹出cusMenu,NSApp.currentEvent表示用户当前触发的事件
NSMenu.popUpContextMenu(cusMenu, with: NSApp.currentEvent!, for: btn)

我们常见的Dock上的右键菜单

// 我们需要在AppDelegate中 添加
func applicationDockMenu(_ sender: NSApplication) -> NSMenu? {
    return dockMenu()   // 这里返回的就是我们自定义的Menu
}
// dockMenu
func dockMenu() -> NSMenu {
    // 一级主菜单
    let m_1 = NSMenu.init(title: "m_1")
    // 一级主菜单下的菜单项
    m_1.addItem(withTitle: "m_1_I_1", action: #selector(m_1_I_1Action), keyEquivalent: "")
    let m_1_I_2 = m_1.addItem(withTitle: "m_1_I_2", action: #selector(m_1_I_2Action), keyEquivalent: "")
    // 二级子菜单
    let m_1_I_1_m = NSMenu.init(title: "m_1_I_1_m")
    m_1_I_2.submenu = m_1_I_1_m
    // 二级子菜单下的菜单项
    let m_1_I_2_M_I_1 = NSMenuItem.init(title: "m_1_I_1_m_I_1", action: #selector(m_1_I_2_M_I_1Action), keyEquivalent: "")
    let m_1_I_2_M_I_2 = NSMenuItem.init(title: "m_1_I_1_m_I_2", action: #selector(m_1_I_2_M_I_2Action), keyEquivalent: "")
    m_1_I_1_m.addItem(m_1_I_2_M_I_1)
    m_1_I_1_m.addItem(m_1_I_2_M_I_2)
        
    return m_1
}
MacOS学习(二) 常用组件_第4张图片
Snip20171228_20.png

我们在AppDelegate中返回Menu就是上面的红色框的位置的菜单,其他项目都是系统默认的,我暂时不知道如何隐藏,或者说能不能隐藏,不过想隐藏上面的`√Window可以通过这几个方法:

方法一

NSApp.mainWindow?.title = ""    // 去掉window.title

方法二


Snip20171228_21.png

方法三


MacOS学习(二) 常用组件_第5张图片
Snip20171228_24.png

顶部左侧菜单(菜单栏)

NSApp.mainMenu?.items.first?.submenu?.addItem(withTitle: "666", action: #selector(menuItem_01Action(item:)), keyEquivalent: "")
MacOS学习(二) 常用组件_第6张图片
Snip20171228_19.png

NSSlider

NSSlider的样式


MacOS学习(二) 常用组件_第7张图片
Snip20171228_31.png

先看看常见属性

slider.sliderType = .circular           // 进度条样式(圆、线)
slider.isContinuous = true              // 实时监听变化(原本是鼠标停止才触发action事件
slider.numberOfTickMarks = 5            // 分割为多少份
slider.tickMarkPosition = .above        // 分割线位置
slider.allowsTickMarkValuesOnly = true  // 只停留在标尺上
slider.minValue = 0                     // 最小值
slider.maxValue = 100                   // 最大值
slider.floatValue = 25.0                // 当前的值

slider.cell = CusSliderCell.init()      // 当前Slider的视图

自定义NSSlider

class CusSliderCell: NSSliderCell {
    // 1、自定义指示标识
    override func drawKnob(_ knobRect: NSRect) {
//        // 使用图片
//        let image = NSImage.init(named: NSImage.Name(rawValue: "hand"))
//        image?.draw(in: knobRect)
        // 使用绘图方法绘制
        NSColor.cyan.set()
        let knobPath = NSBezierPath.init(ovalIn: knobRect)
        knobPath.fill()
    }
    
    // 2、自定义标志器左右两边颜色
    override func drawBar(inside rect: NSRect, flipped: Bool) {
        NSColor.red.set()
        let allPath = NSBezierPath.init(roundedRect: rect, xRadius: 2, yRadius: 2)
        allPath.fill()
        
        // 获取左边的区域
        let w = CGFloat((doubleValue - minValue) / (maxValue - minValue))  * rect.width
        
        var myRect = rect
        myRect.size.width = w
        
        let leftPath = NSBezierPath.init(rect: myRect)
        NSColor.yellow.set()
        leftPath.fill()
        
    }
}

结合一下NSSlider和NSPopover

// 创建popover
let popo = NSPopover.init()
let popvc = CusPopoverVC.init()
popo.contentViewController = popvc
popo.behavior = .semitransient
// 创建slider
let slider = NSSlider.init(frame: .init(x: 10, y: 10, width: 200, height: 40))
slider.isContinuous = true
slider.minValue = 0
slider.maxValue = 100
slider.target = self
slider.action = #selector(sliderAction(slider:))

// 监听slider变化
@objc func sliderAction(slider: NSSlider) -> Void {
    let strV = String.init(format: "%0.2lf", slider.floatValue)
    if let txtF = ((popo?.contentViewController as? CusPopoverVC)?.valueTxtF) {
        txtF.stringValue = strV
    }
        
    let radio = CGFloat.init(slider.floatValue) / CGFloat.init(slider.maxValue)
    // Knob(那个点)的宽高都为21
    let w = radio * (slider.bounds.width - slider.bounds.height)
    let sliderRect = CGRect.init(x: w, y: -10, width: slider.bounds.height, height: slider.bounds.height)
    popo?.show(relativeTo: sliderRect, of: slider, preferredEdge: .minY)
}

NSStatusBar & NSStatusItem

MacOS学习(二) 常用组件_第8张图片
Snip20171229_34.png

这个就是NSStatusBar,来看看怎么实现

// 在AppDelegate中添加
var iconItem: NSStatusItem? // 我们创建的Item必须被强引用,否则不会显示

func applicationDidFinishLaunching(_ aNotification: Notification) {
    iconItem = statusIconItem()
}
// 构建一个StatusItem
func statusIconItem() -> NSStatusItem {
  // NSStatusBar.system 获取系统的Bar    设置NSStatusItem的宽度
    let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
    statusItem.highlightMode = true
  // 设置StatusItem 图片
    statusItem.image = NSImage.init(named: NSImage.Name(rawValue: "sunny_night"))
        
    let statusMenu = NSMenu.init(title: "menu")
    let statusMenu_I_01 = NSMenuItem.init(title: "statusMenu_I_01", action: #selector(statusMenu_I_01Action), keyEquivalent: "R")
    let statusMenu_I_02 = NSMenuItem.init(title: "statusMenu_I_02", action: #selector(statusMenu_I_02Action), keyEquivalent: "T")
        
    statusMenu.addItem(statusMenu_I_01)
    statusMenu.addItem(statusMenu_I_02)
        
    statusItem.menu = statusMenu
    statusItem.toolTip = "I'm toolTip"  // 鼠标悬停在NSStatusItem上会显示
    statusItem.target = self
statusItem.action = #selector(statusItemAction(sender:))    // 设置点击方法 这里传入的是NSStatusBarButton对象
        
    return statusItem
}

toolTip

MacOS学习(二) 常用组件_第9张图片
Snip20171229_35.png

NSStatusBar & NSStatusItem + NSPopover的练习

@objc func statusItemAction(sender: NSStatusBarButton) -> Void {
    let popo = NSPopover.init()
    popo.behavior = .semitransient
    let popVC = NSStatusBarVC.init()
    popo.contentViewController = popVC
    popo.show(relativeTo: (sender.bounds), of: sender, preferredEdge: .minY)
}
MacOS学习(二) 常用组件_第10张图片
Snip20171229_37.png

注意,如果你使用的是黑丝的菜单栏,有是使用黑色的icon,你可能会看不到你的Icon


Snip20171229_38.png

像这样,在不点击时时不会显示的,这时候需要设置一下图片的属性

let icon = NSImage.init(named: NSImage.Name(rawValue: "lightning"))
icon?.isTemplate = true // 设置这个属性后,系统会自动为图片取反,以适应菜单栏
statusItem.image = icon
Snip20171229_39.png

你可能感兴趣的:(MacOS学习(二) 常用组件)