本节讲述一些相对复杂的控件,此节内容中涉及的多数控件会有双向操作。本节内容大概包括: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为分区索引
编码实现
- 设置分割线的样式;
- 创建两个视图,并添加到NSSplitView面板中;
- 将NSSplitView添加到主视图中;
- 实现
NSSplitViewDelegate协议;
- 创建面板
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)
}
- 实现
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()
}
后面章节会着重介绍一些容器控件的使用与定义
633

被折叠的 条评论
为什么被折叠?



