07. 容器控件(一) - NSCollectionView 网格、NSTabView 卡片、NSPopover 弹出层
本节主要讲述三个使用比较频繁的容器控件,包括tab页、表格以及集合面板三种。
NSTabView
Tab卡片视图,这个很简单就是如下样式,在使用过程中还可以动态增加和删除。
基本设置
- tabs:指定tab的个数;
- initial tab:指定默认被选中的tab;
- style:指定tab的位置;
- identifier:tab的id值(设置tabItem的);
事件响应
需要实现NSTabViewDelegate
代理协议,当tab加载时调用(默认加载或点击),本示例中因为是委托给了ViewController,所以需要outlets到View Controller。
extension ViewController: NSTabViewDelegate{func tabView(_ tabView: NSTabView, didSelect tabViewItem: NSTabViewItem?) {//点击和加载的默认事件print("title:\(tabViewItem?.label) id:\(tabViewItem?.identifier)")}
}
动态添加TabItem
编程实现tabitem的动态添加:
@IBOutlet weak var tabView: NSTabView! @IBAction func addTabItemAction(_ sender: NSButton) {let tabViewItem = NSTabViewItem(identifier: "Untitled")tabViewItem.label = "dyItemTitle"let view = NSView(frame: NSZeroRect) //系统常量,类似的还有NSZeroPoint等tabViewItem.view = viewself.tabView.addTabViewItem(tabViewItem)}
动态选择TabItem
比如添加一个 NSSegmentedControl ,然后根据索引设置tab显示,即不使用NSTabView自带的切换按钮,而是从外部控件其视图的显示。
示例代码如下:
@IBOutlet weak var tabView: NSTabView!@IBAction func segmentAction(_ sender: NSSegmentedControl) {let index = sender.selectedSegmenttabView.selectTabViewItem(at: index)}
NSPopover
弹出容器,可以指定其位置等属性,其内容来自于别一个NSViewController。
基本设置
- behavior:设置关闭行为,transient=只要点击Popover区域外就会关闭, semitransient=只要点击Popover区域外就会关闭,但点击到当前App外则不关闭
- animates:是否有动画效果;
- contentViewController:要显示的控制器;
事件响应
- 添加一个NSViewController,并设置其storyboard id值;
- 给需要弹出popover面板的控件加点击事件;
//延迟加载技术,主要为了提升性能,在被调用时才初始化lazy var sharePopover:NSPopover = {let popover = NSPopover()let controller = self.storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("Share")) as! NSViewControllerpopover.contentViewController = controllerpopover.behavior = .transient //关闭行为return popover}()@IBAction func showPopupAction(_ sender: NSButton) {//relativeTo:显示时参考的矩形, of:参考点,preferredEdge:显示位置,上面sharePopover.show(relativeTo: sender.bounds, of: sender, preferredEdge: .minY)//sharePopover.close()}
NSCollectionView
一个带有自动分布功能的Grid面板控件,类似九宫格,在使用此控件之前需要先了解两个核心对象:
- NSCollectionView:相当于一个容器,内容会被封装到一个名为
content
的属性中,content
是一个NSCollectionViewItem数组; - NSCollectionViewItem:是NSCollectionView中的内容,它也是一个容器,它继承了NSViewController,所以使用时需要binding 数据;
Demo示例
- 拖动NSCollectionView控件到主面板上;
- 添加 add Constraints 这样可以达到自动适应的一个效果;***见Auto Layout应用
- 拖动一个NSCollectionViewItem控件到空白处,同时设置其storyboard ID;
- 在NSCollectionViewItem控件中添加NSImageView和Label;
- 绑定 NSImageView和NSLabel 到 NSCollectionViewItem; ***见bindings 模型
Auto Layout应用
- 拖动一个NSCollectionView到面板中;
- 然后在导航区选择Bordered Scroll View,点击右下角第二个按钮 add Constraints;这样NSCollectionView就可以随父窗口变化而变化了,这个Container的作用是固定元素到父元素的边距大小,相当于一个内容区域自适应功能。
上面设置的等价代码实现如下:
//下面代码采用swift 4func autoLayoutConfig() {// tableScrollView 为自定义的view let topAnchor = self.tableScrollView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0)let bottomAnchor = self.tableScrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0)let leftAnchor = self.tableScrollView.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 0)let rightAnchor = self.tableScrollView.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: 0)NSLayoutConstraint.activate([topAnchor, bottomAnchor, leftAnchor, rightAnchor])}
Bindings 数据模型
这里要做的就是把NSCollectionViewItem中的相应属性和相空的控件绑定在一起,比如 NSCollectionViewItem面板中添加了一个image如下:
则点击image然后切换到bindings 面板:
这里的 Bind to 表示要绑定的控件类,Model key path 表示代码中的数据属性名称(representedObject是一个固定值),通过以上设置,就把元素和控件的类属性绑定在一起了。
组装NSCollectionView和NSCollectionViewItem
@IBOutlet weak var collectionView: NSCollectionView!override func viewDidLoad() {//collectionViewItem 为 NSCollectionViewItem的storyboardid,在identity面板中设置let itemPrototype = self.storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "collectionViewItem")) as! NSCollectionViewItem//设置NSCollectionView内容为NSCollectionViewItemself.collectionView.itemPrototype = itemPrototype//更新数据self.updateContent()}
添加内容,这样就实现了动态添加内容了
var content = [NSDictionary]()func updateContent(){let item1: NSDictionary = ["title" : "computer","image" : NSImage(named: NSImage.Name.computer)!]let item2: NSDictionary = ["title" : "folder","image" : NSImage(named: NSImage.Name.folder)!]let item3: NSDictionary = ["title" : "home","image" : NSImage(named: NSImage.Name.homeTemplate)!]let item4: NSDictionary = ["title" : "list","image" : NSImage(named: NSImage.Name.listViewTemplate)!]let item5: NSDictionary = ["title" : "network","image" : NSImage(named: NSImage.Name.network)!]let item6: NSDictionary = ["title" : "share","image" : NSImage(named: NSImage.Name.shareTemplate)!]content.append(item1)content.append(item2)content.append(item3)content.append(item4)content.append(item5)content.append(item6)self.collectionView.content = content}
其在拖动时,也可以自由变换位置,如下:
编码实现
(示例有点显示问题)
- 创建一个 NSCollectionViewItem 子类,并附带XIB界面实现,这里需要重新绑定元素,默认的自动绑定不好用
- 手动编码
创建NSCollectionViewItem
选择新建Cocos class 类,然后按下图设置
此时会生成一个CollectionViewItem.xib文件和一个名为CollectionViewItem.swift的源文件重新绑定元素
//CollectionViewItem.swift
import Cocoaclass CollectionViewItem: NSCollectionViewItem {//这两个变量要重新绑定,否则不生效,报nil找不到错误@IBOutlet weak var image: NSImageView!@IBOutlet weak var titleField: NSTextField!override func viewDidLoad() {super.viewDidLoad()}override var representedObject: Any? {didSet {let data = self.representedObject as! NSDictionaryif let image = data["image"] as? NSImage {self.image.image = image}if let title = (data["title"] as? String) {self.titleField.stringValue = title}}}
}
创建 NSCollectionView
在ViewController.swift中一步步手工编码即可
import Cocoa//1、 得到一个名为CollectionViewItem的界面元素,声明为全局变量
extension NSUserInterfaceItemIdentifier {static let collectionViewItem = NSUserInterfaceItemIdentifier("CollectionViewItem")
}class ViewController: NSViewController {//声明 NSScrollView 对象lazy var scrollView: NSScrollView = {let scrollView = NSScrollView()scrollView.focusRingType = .nonescrollView.autohidesScrollers = truescrollView.borderType = .noBorderscrollView.documentView = self.collectionViewreturn scrollView}()// 声明 NSCollectionView 对象--内容面板lazy var collectionView: NSCollectionView = {let view = NSCollectionView()view.isSelectable = trueview.delegate = selfview.dataSource = selfview.collectionViewLayout = self.flowLayoutview.register(CollectionViewItem.self, forItemWithIdentifier: .collectionViewItem) //注册view.backgroundColors[0] = NSColor.redreturn view}()var sectionInset: NSEdgeInsets = NSEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)lazy var flowLayout: NSCollectionViewFlowLayout = {let layout = NSCollectionViewFlowLayout()layout.scrollDirection = .horizontallayout.itemSize = NSSize(width: 40, height: 40)layout.sectionInset = self.sectionInsetlayout.minimumInteritemSpacing = 10layout.minimumLineSpacing = 10return layout}()//添加到window中override func viewDidLoad() {super.viewDidLoad()scrollView.frame = self.view.boundsself.view.addSubview(scrollView)self.updateContent()let dd = NSCollectionViewGridLayout()}//更新元素var content = [NSDictionary]()func updateContent(){let item1: NSDictionary = ["title" : "computer","image" : NSImage(named: NSImage.Name.computer)!]let item2: NSDictionary = ["title" : "folder","image" : NSImage(named: NSImage.Name.folder)!]let item3: NSDictionary = ["title" : "home","image" : NSImage(named: NSImage.Name.homeTemplate)!]let item4: NSDictionary = ["title" : "list","image" : NSImage(named: NSImage.Name.listViewTemplate)!]let item5: NSDictionary = ["title" : "network","image" : NSImage(named: NSImage.Name.network)!]let item6: NSDictionary = ["title" : "share","image" : NSImage(named: NSImage.Name.shareTemplate)!]content.append(item1)content.append(item2)content.append(item3)content.append(item4)content.append(item5)content.append(item6)collectionView.reloadData()}
}//以下全是扩展协议
extension ViewController: NSCollectionViewDataSource {func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {return content.count}func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {//let item = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "CollectionViewItem"), for: indexPath)let item = collectionView.makeItem(withIdentifier: .collectionViewItem, for: indexPath)let itemIndex = (indexPath as NSIndexPath).itemitem.representedObject = content[itemIndex]return item}func numberOfSections(in collectionView: NSCollectionView) -> Int {return 1}
}extension ViewController: NSCollectionViewDelegate {func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set<IndexPath>) {print(indexPaths)}
}extension ViewController: NSCollectionViewDelegateFlowLayout {func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize {return NSSize(width: 100, height: 100)}
}