06. Form表单相关(三) - NSImage、NSProgressIndicator 以及 NSBox、NSSplitView 分组控件

本节讲述一些相对复杂的控件,此节内容中涉及的多数控件会有双向操作。本节内容大概包括:NSProgressIndicator、NSImageView、NSBox、NSSplitView、NSCollectionView控件

Storyboard 项目设置

本节开始以及之后所有工程全部会基于storyboard,而非xib创建。下面是两个初学者比较迷糊的功能,也很容易由于误操作导致整个工程无法正常运行:

设置entry point

其作用是应用初始化要显示哪个UI界面,这个是在UI的属性面板中设置的,如下:
在这里插入图片描述

设置windowContent

设置合理的或自定义的NSViewController:
在这里插入图片描述
最后storyboard UI设计视图会显示成下图这样
在这里插入图片描述

这里涉及的controller会比较复杂,在后面会用专门章节来描述。

NSProgressIndicator

进度百分比展示控件,一般用于耗时任务的进度显示,合适时一般会根据设计要求在初始化时隐藏掉,需要的时候再显示出来。
在这里插入图片描述

基本设置

  • style:外观样式
  • size:设置外观为圆形时的大小
  • behavior:是否显示为进度百分比(indeterminate),还就是一个一直在转的圈圈;
  • value:用于设置最大,最小以及初始化值;

进度条运行控制

下面这个例子中没有设置值,只是显示了一个效果,可通过修改其doubleValue或intValue值来更新进度

    @IBOutlet weak var progressBtn: NSProgressIndicator!
    
    @IBAction func startProgress(_ sender: NSButton) {
        self.progressBtn.isHidden = false
        self.progressBtn.startAnimation(self)
    }
    
    @IBAction func stopProgress(_ sender: NSButton) {
        self.progressBtn.stopAnimation(self)
        self.progressBtn.isHidden = true
    }

编码实现

编程添加控件

   func createProgressIndicator() {
        let progressIndicator = NSProgressIndicator()
        // 设置位置
        progressIndicator.frame = NSRect(x: 50, y: 50, width: 40, height: 40)
        progressIndicator.style = .spinning
        
        self.view.addSubview(progressIndicator)
        progressIndicator.startAnimation(self)
    }

NSImageView

图像显示,可在属性面板中设置系统提供的图标,也可设置个人上传的图标或网络图片:
在这里插入图片描述

基本设置

  • border:图像边框
  • alignment:图像在frame中的对齐位置

图像显示控制

图像可以从两个地方取,一个是直接拷贝到项目路径中,另一个是从assets.xcassets中选到。不同的图片来源其获取图像的方法不太一样,详细可参考下面的示例。
在这里插入图片描述

    @IBOutlet weak var imageView: NSImageView!
    // 按钮点击事件
    @IBAction func setImageViewRoundAction(_ sender: NSButton) {
        self.imageView.wantsLayer = true
        self.imageView.layer?.masksToBounds = true
        self.imageView.layer?.cornerRadius = 30
        self.imageView.layer?.borderWidth = 1
        self.imageView.layer?.borderColor = NSColor.green.cgColor
    }

编码实现

从不同位置取时有不同的初始化方法

        //从 项目的 assets.xcassets 库中取图像
        let image = NSImage(named: "screenshot") 
        //从项目源文件目录中取图像
        let image = NSImage(named: NSImage.Name(rawValue: "screenshot.png")) 

GIF图片管理

import Cocoa

import ImageIO

class ViewController: NSViewController {

    @IBOutlet weak var imageView: NSImageView!
    
    lazy var imageArray = [NSImage]()
    
    var playIndex = 0
    /** 播放总时长 */
    var playTotalTime : Double = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()

        readGifDataAndConfigImageView()
        
        // Do any additional setup after loading the view.
    }

  


    
    override func mouseDown(with event: NSEvent) {

        let animTimer = Timer(timeInterval: playTotalTime / Double(imageArray.count), target: self, selector: #selector(starGifAnimated), userInfo: nil, repeats: true)
        RunLoop.current.add(animTimer, forMode: RunLoop.Mode.common)
        
    }
}


extension ViewController{
    func readGifDataAndConfigImageView() {
        /** 1. 获取gif 资源的路径 */
        guard let gifPath = Bundle.main.pathForImageResource(NSImage.Name.init("timg.gif"))else{return}
        /** 2. 读取gif 图片资源元数据 */
        guard let gifData = NSData(contentsOfFile: gifPath) else {return}
        /** 3. 根据图片元数据生成 cfImageSourceRef (包含了gif资源的内部数据信息) */
        guard let imageSourceRef = CGImageSourceCreateWithData(gifData, nil) else {return}
        /** 4. 获取gif 中的图片个数 */
        let imageCount = CGImageSourceGetCount(imageSourceRef)
        /** 5. 创建数组,用于存放转换后的NSImage */
        for  i in 0 ..< imageCount {
            /** 6. 获取CGImage 资源  */
            guard let cgImageRef =  CGImageSourceCreateImageAtIndex(imageSourceRef, i, nil) else {continue}
            /** 7. 根据CGImage 创建NSImage  */
            let image =  NSImage(cgImage: cgImageRef, size: CGSize(width: cgImageRef.width, height: cgImageRef.height))
            /** 8. 将NSImage 添加的数组中 */
            imageArray.append(image)
            /** 9. 获取指定帧的属性信息 */
            guard let properties =  CGImageSourceCopyPropertiesAtIndex(imageSourceRef, i, nil) as? NSDictionary else {continue}
            /** 10. 获取指定帧的gif信息字典 */
            guard let gifDictInfo = properties[kCGImagePropertyGIFDictionary] as? NSDictionary else {continue}
            /** 11. 获取一帧的时长 */
            guard let duration = gifDictInfo[kCGImagePropertyGIFDelayTime] as? NSNumber else {continue}
            
            playTotalTime += duration.doubleValue
            
        }
        
        /** 显示某一帧图片 (此示例中显示为100 ,图片总数为104)*/
        imageView.image = imageArray[100];
        
    }
    
    
    @objc func starGifAnimated() {
        imageView.image = imageArray[playIndex]
        playIndex += 1
        if playIndex == imageArray.count {
            playIndex = 0
        }
    }
}

NSBox

带标题的视图区,通常用来做分组容器使用,在其内部可以添加其它控件,比如下面的check是添加到NSBox中的一个多选控件。
在这里插入图片描述

基本设置

  • title:标题内容
  • position:标题位置

编码实现

手动创建NSBox,下面例子的代码实现为往一个名为Box的区域内添加一个文本框。

    func createBox(){
        let frame = CGRect(x: 15, y: 250, width: 250, height: 1)
        let horizontalSeparator = NSBox(frame: frame)
        horizontalSeparator.boxType = .separator
        
        self.view.addSubview(horizontalSeparator)
        
        
        let boxFrame = CGRect(x: 10, y: 10, width: 160, height: 100)
        let box = NSBox(frame: boxFrame)
        box.boxType = .primary
        let margin  = NSSize(width: 20, height: 20)
        box.contentViewMargins = margin
        box.title = "Box"
        
        let textFieldFrame = CGRect(x: 10, y: 10, width: 80, height: 20)
        let textField = NSTextField(frame: textFieldFrame)
        
        box.contentView?.addSubview(textField)
        
        self.view.addSubview(box)
    }

NSSplitView

可拖动大小区域的分隔视图,有上下和左右两种风格,中间的分割线也可以设置样式。
在这里插入图片描述

基本设置

  • vertical:控制view的分割方向;
  • setPosition:设置分区位置,dividerIndex为分区索引

编码实现

  1. 设置分割线的样式;
  2. 创建两个视图,并添加到NSSplitView面板中;
  3. 将NSSplitView添加到主视图中;
  4. 实现 NSSplitViewDelegate 协议;
  1. 创建面板
    var splitView: NSSplitView!
    
    func createVSplitView(){
        //创建分割面板
        let frame = CGRect(x: 180  , y: 126, width: 300, height: 200)
        splitView = NSSplitView(frame: frame)
        splitView.isVertical = true
        splitView.delegate = self  //这行需要实现协议,后面会描述
        splitView.dividerStyle = .thick
        //splitView.wantsLayer = true
        //splitView.layer?.borderWidth = 0.5
        //splitView.layer?.borderColor = NSColor.gray.cgColor

        //左侧子面板
        let lframe = CGRect(x: 0, y: 0, width: 30, height: 200)
        let leftView = NSView(frame: lframe)
        leftView.wantsLayer = true
        leftView.layer?.backgroundColor = NSColor.gray.cgColor

        //右侧面板
        let rframe = CGRect(x: 0, y: 0, width: 300, height: 200)
        let rightView = NSView(frame: rframe)
        rightView.wantsLayer = true
        rightView.layer?.backgroundColor = NSColor.white.cgColor

        splitView.addSubview(leftView)
        splitView.addSubview(rightView)
        
        self.view.addSubview(splitView)
        
        //设置左面板的宽
        splitView.setPosition(60, ofDividerAt: 0)
    }
  1. 实现NSSplitViewDelegate协议,这个协议主要相当于接口,主要用于增强用的,通常实现协议有两种写法:

1. 在所在的ViewController中直接实现(不推荐)

class ViewController: NSViewController, NSSplitViewDelegate 

2. 通过extension实现(推荐),这种方式的好处是可以独立维护,防止一个类的何积过大

extension  ViewController: NSSplitViewDelegate {
    
    func splitView(_ splitView: NSSplitView, canCollapseSubview subview: NSView) -> Bool {
        if subview == splitView.subviews[0] {
            return true
        }
        return false
    }
    
    func splitView(_ splitView: NSSplitView, constrainMinCoordinate proposedMinimumPosition: CGFloat, ofSubviewAt dividerIndex: Int) -> CGFloat {
        return 30
    }

    func splitView(_ splitView: NSSplitView, constrainMaxCoordinate proposedMaximumPosition: CGFloat, ofSubviewAt dividerIndex: Int) -> CGFloat {
         return 100
    }
    
    
    func splitView(_ splitView: NSSplitView, shouldAdjustSizeOfSubview view: NSView) -> Bool {
         return true
    }
   
    func splitView(_ splitView: NSSplitView, shouldHideDividerAt dividerIndex: Int) -> Bool {
        return true
    }
}

正常来讲,需要定义一个视图的子类,然后在视图子类中实现协议,或是在其父类中实现协议。

面板收缩 / 恢复

其实就是设置不同区域的大小来设置左右是否显示

    // 自定义宽度
    var leftViewWidth: CGFloat = 0
    var splitView: NSSplitView!
    
    @IBAction func spliterPanel(_ sender: NSButton) {
        //取左侧视图
        let splitViewItem = splitView.arrangedSubviews
        let leftView  = splitViewItem[0]
        
        if splitView.isSubviewCollapsed(leftView){
            splitView.setPosition(leftViewWidth, ofDividerAt: 0)
            leftView.isHidden = false
        } else {
            leftViewWidth = leftView.frame.size.width
            splitView.setPosition(0, ofDividerAt: 0)
            leftView.isHidden = true
        }
        splitView.adjustSubviews()
    }

后面章节会着重介绍一些容器控件的使用与定义

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

korgs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值