问题描述
我无法在Xcode 8(Swift 3)中创建NSOutlineView.我有一个plist文件,其中包含一些我想在OutlineView中呈现的信息. plist文件如下所示(示例):
I am having trouble creating a NSOutlineView in Xcode 8 (Swift 3). I have a plist file with some information that I would like to present in an OutlineView. The plist file looks as following (example):
Root Dictionary *(1 item)
Harry Watson Dictionary *(5 items)*
name String Harry Watson
age Int 99
birthplace String Westminster
birthdate Date 01/01/1000
hobbies Array *(2 items)*
item 0 String Tennis
item 1 String Piano
OutlineView应该看起来非常相似,如下所示:
The OutlineView should look pretty similar, like follow:
name Harry Watson
age 99
birthplace Westminster
birthdate 01/01/1000
> hobbies ... (<- this should be expandable)
我已经在Google上搜索了NSOutlineView教程,但是我发现的所有内容都是raywenderlich.com,所以我读了一点,但我认为这并不容易.因此,我想知道您是否可以通过上面的确切示例为我提供帮助,并提供一些代码示例,尤其是有关此功能的代码示例:
I already searched for NSOutlineView tutorials on Google, but everything I found was raywenderlich.com, so I read a bit but in my opinion it isn't that easy.So I am wondering whether you could help me with the exact example above and give me some code examples, especially regarding this function:
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {}
我不确定在那写些什么.
I am not sure what to write in there.
如果您有任何疑问,请告诉我.
If you have any questions, let me know.
预先感谢和亲切问候
推荐答案
我发现Ray Wenderlitch的教程在质量上千差万别.笑话,冗长的词句,逐步的假设(假设您对Swift一无所知)对我来说太令人讨厌.这是一个皮包骨头的教程,介绍了手动和通过Cocoa绑定填充轮廓视图的基础知识.
I find Ray Wenderlitch's tutorials vary wildly in quality. The in-jokes, the verbosity, the step-by-step handholding that assumes you know nothing about Swift is just too nauseating to me. Here's a skinny tutorial which covers the basics of populating an outline view, manually and via Cocoa Bindings.
理解NSOutlineView
的关键是必须为每行赋予唯一的标识符,可以是字符串,数字或代表该行的对象. NSOutlineView
称为item
.基于此item
,您将查询数据模型以用数据填充轮廓视图.
The key to understand NSOutlineView
is that you must give each row a unique identifier, be it a string, a number or an object that represents the row. NSOutlineView
calls it the item
. Based on this item
, you will query your data model to fill the outline view with data.
我们将使用非常简单的NSOutlineView
,其中只有两列:键和值.
We will use a very simple NSOutlineView
with just two columns: Key and Value.
选择第一列并将其标识符更改为keyColumn
.然后选择第二列并将其标识符更改为valueColumn
:
Select the first column and change its identifier to keyColumn
. Then select the second column and change its identifier to valueColumn
:
将单元格的标识符设置为outlineViewCell
.您只需要做一次.
Set the identifier for the cell to outlineViewCell
. You only need to do it once.
将以下内容复制并粘贴到您的ViewController.swift
:
Copy and paste the following to your ViewController.swift
:
// Data model
struct Person {
var name: String
var age: Int
var birthPlace: String
var birthDate: Date
var hobbies: [String]
}
class ViewController: NSViewController {
@IBOutlet weak var outlineView: NSOutlineView!
// I assume you know how load it from a plist so I will skip
// that code and use a constant for simplicity
let person = Person(name: "Harry Watson", age: 99, birthPlace: "Westminster",
birthDate: DateComponents(calendar: .current, year: 1985, month: 1, day: 1).date!,
hobbies: ["Tennis", "Piano"])
let keys = ["name", "age", "birthPlace", "birthDate", "hobbies"]
override func viewDidLoad() {
super.viewDidLoad()
outlineView.dataSource = self
outlineView.delegate = self
}
}
extension ViewController: NSOutlineViewDataSource, NSOutlineViewDelegate {
// You must give each row a unique identifier, referred to as `item` by the outline view
// * For top-level rows, we use the values in the `keys` array
// * For the hobbies sub-rows, we label them as ("hobbies", 0), ("hobbies", 1), ...
// The integer is the index in the hobbies array
//
// item == nil means it's the "root" row of the outline view, which is not visible
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
if item == nil {
return keys[index]
} else if let item = item as? String, item == "hobbies" {
return ("hobbies", index)
} else {
return 0
}
}
// Tell how many children each row has:
// * The root row has 5 children: name, age, birthPlace, birthDate, hobbies
// * The hobbies row has how ever many hobbies there are
// * The other rows have no children
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
if item == nil {
return keys.count
} else if let item = item as? String, item == "hobbies" {
return person.hobbies.count
} else {
return 0
}
}
// Tell whether the row is expandable. The only expandable row is the Hobbies row
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
if let item = item as? String, item == "hobbies" {
return true
} else {
return false
}
}
// Set the text for each row
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
guard let columnIdentifier = tableColumn?.identifier.rawValue else {
return nil
}
var text = ""
// Recall that `item` is the row identiffier
switch (columnIdentifier, item) {
case ("keyColumn", let item as String):
switch item {
case "name":
text = "Name"
case "age":
text = "Age"
case "birthPlace":
text = "Birth Place"
case "birthDate":
text = "Birth Date"
case "hobbies":
text = "Hobbies"
default:
break
}
case ("keyColumn", _):
// Remember that we identified the hobby sub-rows differently
if let (key, index) = item as? (String, Int), key == "hobbies" {
text = person.hobbies[index]
}
case ("valueColumn", let item as String):
switch item {
case "name":
text = person.name
case "age":
text = "\(person.age)"
case "birthPlace":
text = person.birthPlace
case "birthDate":
text = "\(person.birthDate)"
default:
break
}
default:
text = ""
}
let cellIdentifier = NSUserInterfaceItemIdentifier("outlineViewCell")
let cell = outlineView.makeView(withIdentifier: cellIdentifier, owner: self) as! NSTableCellView
cell.textField!.stringValue = text
return cell
}
}
结果
填充大纲视图的另一种方法是使用Cocoa绑定,这可以显着减少您需要编写的代码量.但是,可可绑定是一个高级主题.当它起作用时,就像魔术一样,但是当它不起作用时,很难修复.可可绑定在iOS上不可用.
Another way to populate the outline view is using Cocoa Bindings, which can significantly reduce the amount of code you need to write. However, consider Cocoa Bindings an advanced topic. When it works, it's like magic, but when it doesn't, it can be very hard to fix. Cocoa Bindings are not available on iOS.
在此示例中,让NSOutlineView
显示多个人的详细信息来结束赌注.
For this example, let's up the ante by having the NSOutlineView
showing details of multiple persons.
// Data Model
struct Person {
var name: String
var age: Int
var birthPlace: String
var birthDate: Date
var hobbies: [String]
}
// A wrapper object that represents a row in the Outline View
// Since Cocoa Binding relies on the Objective-C runtime, we need to mark this
// class with @objcMembers for dynamic dispatch
@objcMembers class OutlineViewRow: NSObject {
var key: String // content of the Key column
var value: Any? // content of the Value column
var children: [OutlineViewRow] // set to an empty array if the row has no children
init(key: String, value: Any?, children: [OutlineViewRow]) {
self.key = key
self.value = value
self.children = children
}
convenience init(person: Person) {
let hobbies = person.hobbies.map { OutlineViewRow(key: $0, value: nil, children: []) }
let children = [
OutlineViewRow(key: "Age", value: person.age, children: []),
OutlineViewRow(key: "Birth Place", value: person.birthPlace, children: []),
OutlineViewRow(key: "Birth Date", value: person.birthDate, children: []),
OutlineViewRow(key: "Hobbies", value: nil, children: hobbies)
]
self.init(key: person.name, value: nil, children: children)
}
}
class ViewController: NSViewController {
let people = [
Person(name: "Harry Watson", age: 99, birthPlace: "Westminster",
birthDate: DateComponents(calendar: .current, year: 1985, month: 1, day: 1).date!,
hobbies: ["Tennis", "Piano"]),
Person(name: "Shelock Holmes", age: 164, birthPlace: "London",
birthDate: DateComponents(calendar: .current, year: 1854, month: 1, day: 1).date!,
hobbies: ["Violin", "Chemistry"])
]
@objc lazy var rows = people.map { OutlineViewRow(person: $0) }
override func viewDidLoad() {
super.viewDidLoad()
}
}
Interface Builder设置
在故事板中:
Interface Builder setup
In your storyboard:
- 从对象库添加树控制器
- 选择树控制器,然后打开属性"检查器(
Cmd + Opt + 4
).将其子项的关键路径设置为children
. - 打开绑定"检查器(
Cmd + Opt + 7
),并为IB对象设置绑定,如下所示.
- Add a Tree Controller from the Object Library
- Select the Tree Controller and open the Attributes Inspector (
Cmd + Opt + 4
). Set its Children key path tochildren
. - Open the Bindings inspector (
Cmd + Opt + 7
) and set up bindings for the IB objects as follow.
| IB Object | Property | Bind To | Controller Key | Model Key Path |
|-----------------|--------------------|-----------------|-----------------|-------------------|
| Tree Controller | Controller Content | View Controller | | self.rows |
| Outline View | Content | Tree Controller | arrangedObjects | |
| Table View Cell | Value | Table Cell View | | objectValue.key |
| (Key column) | | | | |
| Table View Cell | Value | Table Cell View | | objectValue.value |
| (Value column) | | | | |
(不要将Table View Cell与Table Cell View混淆.我知道这是糟糕的命名)
(don't confuse Table View Cell with Table Cell View. Terrible naming, I know)
您可以在两种方法中都使用DateFormatter
获得更好的日期输出,但这对这个问题不是必需的.
You can use a DateFormatter
for nicer date output in both approaches but that's not essential for this question.
这篇关于如何编写NSOutlineView?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!