MacOS学习(二) 常用组件

作者: 咸鱼永动机 | 来源:发表于2017-12-27 10:12 被阅读0次

    Demo地址

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


    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

    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的样式


    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
    }
    
    Snip20171228_20.png

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

    方法一

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

    方法二


    Snip20171228_21.png

    方法三


    Snip20171228_24.png

    顶部左侧菜单(菜单栏)

    NSApp.mainMenu?.items.first?.submenu?.addItem(withTitle: "666", action: #selector(menuItem_01Action(item:)), keyEquivalent: "")
    
    Snip20171228_19.png

    NSSlider

    NSSlider的样式


    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

    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

    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)
    }
    
    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学习(二) 常用组件

        本文链接:https://www.haomeiwen.com/subject/cfiygxtx.html