NSOutlineView 使用指南
简介
NSOutlineView
(大纲视图)继承着NSTableView,两者有许多相同的地方,同样能够展示列表数据,但是NSOutlineView
能够方便的实现数据列表的折叠和展开,就像macOS上Finder中的目录和子目录一样.所以它对于显示层次结构
数据非常有用。
使用
-
1.创建NSOutlineView
NSOutlineView
与NSTableView一样具有滑动功能,所以同样需要将其设置为NScrollView的DocumentView
.然后添加对应的列NSTableColumn
.
let tableColumn = NSTableColumn.init()
let outlineView = NSOutlineView.init()
outlineView.delegate = self;
outlineView.dataSource = self;
outlineView.headerView = nil
outlineView.addTableColumn(tableColumn)
outlineView.outlineTableColumn = tableColumn
let scrollView = NSScrollView.init(frame: NSMakeRect(0, 0, 500, 500))
scrollView.documentView = outlineView
view.addSubview(scrollView)
-
2.定义CellView
与NSTableView的TableColumn类似,创建一个CellView,作为每行的显示样式.这里比较简单添加一个ImageView和一个TextField作为展示.
class TableCellView: NSTableCellView {
let icon = NSImageView.init()
let title = NSTextField.init()
var model: TreeNodeModel? {
didSet {
title.stringValue = model?.name ?? ""
if model?.childNodes.count ?? 0 > 0 {
icon.image = NSImage.init(named: NSImage.Name.folder)
}else{
icon.image = NSImage.init(named: NSImage.Name.listViewTemplate)
}
}
}
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
setupUI()
}
func setupUI() {
// 添加图片
icon.frame = NSRect.init(x: 5, y: 5, width: 30, height: 30)
icon.image = NSImage.init(named: NSImage.Name(rawValue: "NSFolder"))
addSubview(icon)
// 添加标题
title.frame = NSRect.init(x: 45, y: 10, width: 100, height: 20)
title.font = NSFont.boldSystemFont(ofSize: 15)
title.isBordered = false
title.isEditable = false
title.textColor = NSColor.black
title.bezelStyle = .roundedBezel
addSubview(title)
}
required init?(coder decoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
}
}
-
3.数据
定义节点数据模型,包含一个名称和一个数组,数组中包含该节点中的所有子节点.项目可根据实际需求进行定义
class TreeNodeModel: NSObject {
var name: String?
lazy var childNodes: Array = {
return [TreeNodeModel]()
}()
}
var treeModel: TreeNodeModel = TreeNodeModel()
override func viewDidLoad() {
super.viewDidLoad()
configData()
}
func configData() {
let rootNode = TreeNodeModel()
rootNode.name = "网易"
let rootNode2 = TreeNodeModel()
rootNode2.name = "腾讯"
self.treeModel.childNodes.append(rootNode)
self.treeModel.childNodes.append(rootNode2)
let level11Node = TreeNodeModel()
level11Node.name = "电商"
let level12Node = TreeNodeModel()
level12Node.name = "游戏"
let level13Node = TreeNodeModel()
level13Node.name = "音乐"
rootNode.childNodes.append(level11Node)
rootNode.childNodes.append(level12Node)
rootNode.childNodes.append(level13Node)
rootNode2.childNodes.append(level13Node)
let level21Node = TreeNodeModel()
level21Node.name = "研发"
let level22Node = TreeNodeModel()
level22Node.name = "运营"
level11Node.childNodes.append(level21Node)
level11Node.childNodes.append(level22Node)
self.treeView.reloadData()
}
-
4.实现数据源代理
常用数据源
返回节点数,当item为nil时,表示根节点
-(NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(nullable id)item;
返回每个节点的数据模型model,当item为nil时,表示根节点
-(id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(nullable id)item;
节点是否允许展开
-(BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item;
示例代码
extension ViewController: NSOutlineViewDataSource {
// 返回节点数,当item为nil时,表示根节点
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
let rootNode:TreeNodeModel
if item != nil {
rootNode = item as! TreeNodeModel
}else{
rootNode = self.treeModel
}
return rootNode.childNodes.count
}
// 返回每个节点的数据模型model,当item为nil时,表示根节点
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
let rootNode:TreeNodeModel
if item != nil {
rootNode = item as! TreeNodeModel
}
else {
rootNode = self.treeModel
}
return rootNode.childNodes[index]
}
// 节点是否允许展开
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
let rootNode:TreeNodeModel = item as! TreeNodeModel
return rootNode.childNodes.count > 0
}
}
-
5.实现代理协议
常用代理
返回每个节点对应的视图,类似TableView中的Cell
-(nullable NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(nullable NSTableColumn *)tableColumn item:(id)item;
返回每个节点的行高
-(CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item;
节点被选中或取消
-(void)outlineViewSelectionDidChange:(NSNotification *)notification;(id)item;
示例代码
extension ViewController: NSOutlineViewDelegate {
// 返回节点对应的视图
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
let identifire = NSUserInterfaceItemIdentifier(rawValue: "identifire")
var view = outlineView.makeView(withIdentifier: identifire, owner: self) as? TableCellView
if (view == nil) {
view = TableCellView.init()
}
let model = item as! TreeNodeModel
view?.model = model
return view
}
// 行高
func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat {
return 40
}
// 节点选中或取消
func outlineViewSelectionDidChange(_ notification: Notification) {
let treeView = notification.object as! NSOutlineView
// 获取选中节点模型
let row = treeView.selectedRow
let model = treeView.item(atRow: row)
print(model)
}
}
完成效果
常用方法
- 1.节点的添加与删除
在数据模型中添加或删除对应节点模型后,调用outlineView的reloadData方法即可.
示例代码
let addNode = TreeNodeModel()
addNode.name = nodeName
item?.childNodes.append(addNode)
outlineView.reloadData()
- 2.节点的展开与收缩
示例代码
// 展开节点
open func expandItem(_ item: Any?)
// 展开节点,expandChildren是否展开节点内的所有子节点
open func expandItem(_ item: Any?, expandChildren: Bool)
// 收缩节点
open func collapseItem(_ item: Any?)
// 收缩节点,collapseChildren是否收缩节点内的所有子节点
open func collapseItem(_ item: Any?, collapseChildren: Bool)
// 展开所有节点
outlineView.expandItem(nil, expandChildren: true)
// 关闭第一行节点,及节点内的所有子节点
let model = oulineView.item(atRow: 1)
outlineView.collapseItem(model, collapseChildren: true)
- 3.通过代码选中行
示例代码
open func selectRowIndexes(_ indexes: IndexSet, byExtendingSelection extend: Bool)
let indexSet: IndexSet = [1,3]
outlineView.selectRowIndexs(indexSet, byExtendingSelection: false)
自定义
- 1.自定义展开收缩箭头图标
实现NSOutlineView的子类,并重写makeView(withIdentifier identifier:, owner:)方法,在该方法中自定义箭头图标
示例代码
override func makeView(withIdentifier identifier: NSUserInterfaceItemIdentifier, owner: Any?) -> NSView?{
let view = super.makeView(withIdentifier: identifier, owner: owner)
if (identifier == NSOutlineView.disclosureButtonIdentifier) {
let button:NSButton = view as! NSButton
// 自定义收缩图片
button.image = NSImage(named: NSImage.Name(rawValue: "Plus"))!
// 自定义展开图片
button.alternateImage = NSImage(named: NSImage.Name(rawValue: "Minus"))!
button.isBordered = false
button.title = ""
return button
}
return view
}
- 2.添加自定义菜单
同样实现NSOutlineView的子类,并重写menu(for event:)方法,在该方法中返回自定义的菜单NSMenu
示例代码
var nodeMenu: NSMenu?
override func menu(for event: NSEvent) -> NSMenu? {
let pt = self.convert(event.locationInWindow, from: nil)
let row = self.row(at: pt)
if row >= 0 {
// 返回自定义菜单
return self.nodeMenu
}
return super.menu(for: event)!
}