macOS: Source List
程序员文章站
2022-04-26 22:11:18
...
最近在学习 MacOS Cocoa 编程,一直想实现和Finder(访达)左侧菜单一样的效果。通过查资料知道Xcode自带的一个组合控件可以达到这种效果,它就是 Source List
。
注意,
Source List
并不是 Cocoa 控件 而是Xcode 里面提供的一个组合控件。
前言
我想实现的参照(访达的左边的导航)
本文实现的效果
本文并不涉及左右分离布局相关的内容
拖控件,绑定属性
- 新建一个 Cocoa app 工程
- 从控件栏搜索 source list 并拖入到 左边视图上。
- 将 当前视图树中唯一的 OutlineView 绑定到 ViewController.swfit 中
代码
我在代码中写了很多详细的中文注视,所以博客中就不写了。
我将示例工程放在了 Gitee上,需要查阅的朋友可以拉取运行就可以了。
//
// ViewController.swift
// Left Navigation bar
//
// Created by Joel on 2020/7/3.
// Copyright © 2020 Joel. All rights reserved.
//
import Cocoa
// MARK:- Group and Item definitions
// 子标签类
class GroupItem {
var name: String // 名字
var icon: String // 图标
init(name: String, icon: String) {
self.name = name
self.icon = icon
}
}
class Group {
// 检查一个对象是否是该类的有效实例
static func isInstance(any: Any)-> Bool {
if let _ = any as? Group {
return true
}
return false
}
var name: String // 组名
var items: [GroupItem] // 子标签
// 获取当前组有多少个子标签
var numberOfItems: Int {
get {
return self.items.count
}
}
init(name: String) {
self.name = name
self.items = []
}
// 为当前组添加一个子标签
func addChild(item: GroupItem) {
self.items.append(item)
}
// 根据下标获取一个子标签
// 通常来说下标一定不会越界,但是为了安全我们在下表越界之后返回一个无效的标签对象
func childAt(index: Int) -> GroupItem {
if index >= 0 && index < numberOfItems {
return self.items[index]
} else {
return GroupItem(name: "ERROR", icon: NSImage.stopProgressTemplateName)
}
}
}
// MARK:- View controller
class ViewController: NSViewController {
@IBOutlet var outline: NSOutlineView!
var data: [Group]!
override func viewDidLoad() {
super.viewDidLoad()
initData()
// 将 floatsGroupRows 设置为false, 可以禁止第一行点击展开时往上浮动的现象
outline.floatsGroupRows = false
outline.dataSource = self
outline.delegate = self
}
override func viewDidAppear() {
// 在界面即将显示的时候展开所有标签组
outline.expandItem(nil, expandChildren: true)
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func initData() {
// Favorites
let favorites = Group(name: "Favorites")
favorites.addChild(item: GroupItem(name: "Google", icon: NSImage.bookmarksTemplateName))
favorites.addChild(item: GroupItem(name: "百度", icon: NSImage.quickLookTemplateName))
favorites.addChild(item: GroupItem(name: "CSDN", icon: NSImage.refreshTemplateName))
favorites.addChild(item: GroupItem(name: "Segmentfault", icon: NSImage.slideshowTemplateName))
favorites.addChild(item: GroupItem(name: "Joel", icon: NSImage.listViewTemplateName))
favorites.addChild(item: GroupItem(name: "C++", icon: NSImage.goForwardTemplateName))
let users = Group(name: "名字")
users.addChild(item: GroupItem(name: "谷歌", icon: NSImage.lockLockedTemplateName))
users.addChild(item: GroupItem(name: "Baidu", icon: NSImage.lockUnlockedTemplateName))
users.addChild(item: GroupItem(name: "Apple", icon: NSImage.enterFullScreenTemplateName))
users.addChild(item: GroupItem(name: "小猫咪", icon: NSImage.exitFullScreenTemplateName))
// Transfer list
let tasks = Group(name: "Tags")
tasks.addChild(item: GroupItem(name: "Available", icon: NSImage.statusAvailableName))
tasks.addChild(item: GroupItem(name: "None", icon: NSImage.statusNoneName))
tasks.addChild(item: GroupItem(name: "Partially Available", icon: NSImage.statusPartiallyAvailableName))
tasks.addChild(item: GroupItem(name: "Unavailable", icon: NSImage.statusUnavailableName))
//
let empty = Group(name: "Empty")
//
self.data = [favorites, empty, users, tasks]
}
}
// MARK:- View controller with NSOutlineViewDelegate
extension ViewController: NSOutlineViewDelegate {
// 判断当前元素是否是一个组元素。
// 如果返回false, 那么展开按钮是一个三角形并且在文字前面。
// 如果返回true,那么展开按钮是一个悬浮才可见的纯文字按钮(仅有文字没有边框)并且在组标签的后面(靠近outline view 的右边界)
func outlineView(_ outlineView: NSOutlineView, isGroupItem item: Any) -> Bool {
Group.isInstance(any: item)
}
// 判断当前元素是否可以被选中。
// 默认情况下是允许任意节点被选中的,我们希望表示分组的节点不可以被选中,只有子标签可以被选中。
func outlineView(_ outlineView: NSOutlineView, shouldSelectItem item: Any) -> Bool {
!Group.isInstance(any: item)
}
// 获取每一行的视图
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
var ret: NSView?
if let group = item as? Group {
let cell = outlineView.makeView(withIdentifier: .init("HeaderCell"), owner: self) as! NSTableCellView
// 注意:此处我们不能改变 cell.textField,调用 cell.textField = myTextField是无效的。
// 只能修改它的内容。
cell.textField!.stringValue = group.name
ret = cell
} else if let groupItem = item as? GroupItem {
let cell = outlineView.makeView(withIdentifier: .init("DataCell"), owner: self) as! NSTableCellView
// 注意:此处我们不能改变 cell.textField,只能修改它的内容。
cell.textField!.stringValue = groupItem.name
// 同上,我们只能修改 imageView 里面的图片,调用 cell.imageView = myImageView是无效的
cell.imageView?.image = NSImage(named: groupItem.icon)
ret = cell
}
return ret
}
}
// MARK:- View controller with NSOutlineViewDataSource
extension ViewController: NSOutlineViewDataSource {
// 询问当前组有多少个子节点。
// 值得注意的是 Outline 初始化时会调用这个方法,但是 item 是nill. 所以第一次调用的意思是询问有多少个分组。
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
if let group = item as? Group {
return group.numberOfItems
}
return self.data.count
}
// 根据下标获取某个分组下面的子节点数据
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
if let group = item as? Group {
return group.childAt(index: index)
}
return self.data[index]
}
// 询问当前节点是否可以被展开,会在某个标签进入可见状态时调用。
// 如果返回 false 表示此行当前不可被展开,即不显示展开按钮。
// 大多数情况下,即使当前组下面没有任何子标签我们也让他显示展开按钮
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
Group.isInstance(any: item)
}
}
上一篇: CSS+JQuery实现的性感漂亮导航
下一篇: CocoaPods使用
推荐阅读
-
Mybatis如何从数据库中获取数据存为List类型(存为model)
-
Mysql Source导入时出现乱码问题_MySQL
-
list对象去重
-
Python中字典(dict)和列表(list)的排序方法实例
-
unknown column field list解决方案
-
Python-嵌套列表list的全面解析
-
递归一个List
,可自己根据需要改造为通用型。 -
ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause and contains
-
Mysql解决SELECT list is not in GROUP BY clause and contains nonaggregated column 问题
-
Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column xxxx