如何在没有绑定的情况下使用 NSOutlineView
How to use NSOutlineView without bindings
我的数据如下:
let keysDetails : [String : Any] = [
"kAddToBag" : [
"locDict" : [
"at" : "add",
"ae" : "Add"
],
"jsonDict" : [
"at" : "add to bag",
"ae" : "ADD TO BAG"
],
"path" : "somepath"
],
"kShopTab" : [
"locDict" : [
"be_fr" : "shop",
"be_nl" : "SHOP"
],
"jsonDict" : [
"be_fr" : "shop",
"be_nl" : "SHOP"
],
"path" : "somepath2"
]
]
我希望 NSOutlineView 看起来像上图。
输入数据
let keysDetails : [String : Any] = [
"kAddToBag" : [
"locDict" : [
"at" : "add",
"ae" : "Add"
],
"jsonDict" : [
"at" : "add to bag",
"ae" : "ADD TO BAG"
],
"path" : "somepath"
],
"kShopTab" : [
"locDict" : [
"be_fr" : "shop",
"be_nl" : "SHOP"
],
"jsonDict" : [
"be_fr" : "shop",
"be_nl" : "SHOP"
],
"path" : "somepath2"
]
]
字典是无序的 collection,当您想要通过索引访问元素时,它会导致问题,...让我们创建一个自定义结构,为 NSOutlineView
提供数据。
struct Item {
let title: String // First column value
let loc: String // Second column value
let json: String // Third column value
let children: [Item] // Possible children
init(title: String, loc: String, json: String, children: [Item] = []) {
self.title = title
self.loc = loc
self.json = json
self.children = children
}
init?(title: String, content: Any) {
// Check that the content is a dictionary and that it contains
// locDict & jsonDict and both are dictionaries
guard let content = content as? [String: Any],
let loc = content["locDict"] as? [String: String],
let json = content["jsonDict"] as? [String: String] else {
return nil
}
// Check that both dictionaries contains same keys
let locKeys = loc.keys.sorted()
let jsonKeys = json.keys.sorted()
guard locKeys == jsonKeys else {
return nil
}
// Initialize top level item
self.title = title
self.loc = "locDict"
self.json = "jsonDict"
self.children = locKeys.map { key in
// We can force unwrap here because we already checked that
// both dictionaries contains same keys
Item(title: key, loc: loc[key]!, json: json[key]!)
}
}
}
这个结构是一个例子,但是还有很多其他的
方法。这真的取决于你打算在这里做什么。你可以切换
到 object(而不是结构),...
这里的关键点是 children
属性 是一个有序的 collection (数组)。
视图控制器
添加 items
属性(同样,一个有序的 collection = 数组)。
class ViewController: NSViewController {
private let items: [Item] = {
// Map keysDetails to an array of our Item structures
keysDetails.compactMap { (key: String, value: Any) in
Item(title: key, content: value)
}
}()
}
NSOutlineViewDataSource
顾名思义,数据源只是提供数据。我们已经有了
items
属性,用起来吧
extension ViewController: NSOutlineViewDataSource {
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
if item == nil {
// item == nil
// We're being asked for the number of top level elements (kAddToBag, ...)
return items.count
}
// Develop time (debug) - check that the item is really Item
assert(item is Item);
// item != nil
// We're being asked for the number of children of an item
return (item as! Item).children.count
}
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
if item == nil {
// item == nil
// We're being asked for n-th (index) top level element
return items[index]
}
// Develop time (debug) - check that the item is really Item
assert(item is Item);
// item != nil
// We're being asked for n-th (index) child of an item
return (item as! Item).children[index]
}
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
// Develop time (debug) - check that the item is really Item
assert(item is Item);
// Item is expandable only if it has children
return (item as! Item).children.count > 0
}
}
NSOutlineViewDelegate
除其他外,委托还提供单元格视图以显示特定项目
和专栏。
extension ViewController: NSOutlineViewDelegate {
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
// Get the column identifier and our Item
guard let columnIdentifier = tableColumn?.identifier,
let item = item as? Item else {
return nil
}
// Get a cell view identifier and an actual value we should display
let cellViewIdentifier: String
let stringValue: String
switch columnIdentifier.rawValue {
case "TitleColumn":
cellViewIdentifier = "TitleCell"
stringValue = item.title
case "LocColumn":
cellViewIdentifier = "LocCell"
stringValue = item.loc
case "JsonColumn":
cellViewIdentifier = "JsonCell"
stringValue = item.json
default:
return nil
}
// Make a view from the cell view identifier
let view = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(cellViewIdentifier), owner: self) as? NSTableCellView
// Update text field value
view?.textField?.stringValue = stringValue
return view
}
}
示例项目
- 在 Xcode(macOS - Swift 和 Storyboard)中创建一个新应用
- 添加大纲视图
- 设置约束
- 将委托和数据源连接到视图控制器
- 点击大纲视图并设置
- 列数:3
- 取消选中 Headers,重新排序
- 将第 1、2 和 3 列标识符设置为
TitleColumn
、LocColumn
、JsonColumn
- 将第 1、2 和 3 列 table 单元格视图标识符设置为
TitleCell
、LocCell
、JsonCell
我的数据如下:
let keysDetails : [String : Any] = [
"kAddToBag" : [
"locDict" : [
"at" : "add",
"ae" : "Add"
],
"jsonDict" : [
"at" : "add to bag",
"ae" : "ADD TO BAG"
],
"path" : "somepath"
],
"kShopTab" : [
"locDict" : [
"be_fr" : "shop",
"be_nl" : "SHOP"
],
"jsonDict" : [
"be_fr" : "shop",
"be_nl" : "SHOP"
],
"path" : "somepath2"
]
]
我希望 NSOutlineView 看起来像上图。
输入数据
let keysDetails : [String : Any] = [
"kAddToBag" : [
"locDict" : [
"at" : "add",
"ae" : "Add"
],
"jsonDict" : [
"at" : "add to bag",
"ae" : "ADD TO BAG"
],
"path" : "somepath"
],
"kShopTab" : [
"locDict" : [
"be_fr" : "shop",
"be_nl" : "SHOP"
],
"jsonDict" : [
"be_fr" : "shop",
"be_nl" : "SHOP"
],
"path" : "somepath2"
]
]
字典是无序的 collection,当您想要通过索引访问元素时,它会导致问题,...让我们创建一个自定义结构,为 NSOutlineView
提供数据。
struct Item {
let title: String // First column value
let loc: String // Second column value
let json: String // Third column value
let children: [Item] // Possible children
init(title: String, loc: String, json: String, children: [Item] = []) {
self.title = title
self.loc = loc
self.json = json
self.children = children
}
init?(title: String, content: Any) {
// Check that the content is a dictionary and that it contains
// locDict & jsonDict and both are dictionaries
guard let content = content as? [String: Any],
let loc = content["locDict"] as? [String: String],
let json = content["jsonDict"] as? [String: String] else {
return nil
}
// Check that both dictionaries contains same keys
let locKeys = loc.keys.sorted()
let jsonKeys = json.keys.sorted()
guard locKeys == jsonKeys else {
return nil
}
// Initialize top level item
self.title = title
self.loc = "locDict"
self.json = "jsonDict"
self.children = locKeys.map { key in
// We can force unwrap here because we already checked that
// both dictionaries contains same keys
Item(title: key, loc: loc[key]!, json: json[key]!)
}
}
}
这个结构是一个例子,但是还有很多其他的 方法。这真的取决于你打算在这里做什么。你可以切换 到 object(而不是结构),...
这里的关键点是 children
属性 是一个有序的 collection (数组)。
视图控制器
添加 items
属性(同样,一个有序的 collection = 数组)。
class ViewController: NSViewController {
private let items: [Item] = {
// Map keysDetails to an array of our Item structures
keysDetails.compactMap { (key: String, value: Any) in
Item(title: key, content: value)
}
}()
}
NSOutlineViewDataSource
顾名思义,数据源只是提供数据。我们已经有了
items
属性,用起来吧
extension ViewController: NSOutlineViewDataSource {
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
if item == nil {
// item == nil
// We're being asked for the number of top level elements (kAddToBag, ...)
return items.count
}
// Develop time (debug) - check that the item is really Item
assert(item is Item);
// item != nil
// We're being asked for the number of children of an item
return (item as! Item).children.count
}
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
if item == nil {
// item == nil
// We're being asked for n-th (index) top level element
return items[index]
}
// Develop time (debug) - check that the item is really Item
assert(item is Item);
// item != nil
// We're being asked for n-th (index) child of an item
return (item as! Item).children[index]
}
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
// Develop time (debug) - check that the item is really Item
assert(item is Item);
// Item is expandable only if it has children
return (item as! Item).children.count > 0
}
}
NSOutlineViewDelegate
除其他外,委托还提供单元格视图以显示特定项目 和专栏。
extension ViewController: NSOutlineViewDelegate {
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
// Get the column identifier and our Item
guard let columnIdentifier = tableColumn?.identifier,
let item = item as? Item else {
return nil
}
// Get a cell view identifier and an actual value we should display
let cellViewIdentifier: String
let stringValue: String
switch columnIdentifier.rawValue {
case "TitleColumn":
cellViewIdentifier = "TitleCell"
stringValue = item.title
case "LocColumn":
cellViewIdentifier = "LocCell"
stringValue = item.loc
case "JsonColumn":
cellViewIdentifier = "JsonCell"
stringValue = item.json
default:
return nil
}
// Make a view from the cell view identifier
let view = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(cellViewIdentifier), owner: self) as? NSTableCellView
// Update text field value
view?.textField?.stringValue = stringValue
return view
}
}
示例项目
- 在 Xcode(macOS - Swift 和 Storyboard)中创建一个新应用
- 添加大纲视图
- 设置约束
- 将委托和数据源连接到视图控制器
- 点击大纲视图并设置
- 列数:3
- 取消选中 Headers,重新排序
- 将第 1、2 和 3 列标识符设置为
TitleColumn
、LocColumn
、JsonColumn
- 将第 1、2 和 3 列 table 单元格视图标识符设置为
TitleCell
、LocCell
、JsonCell