如何编写NSOutlineView? [英] How to program a NSOutlineView?

查看:115
本文介绍了如何编写NSOutlineView?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我无法在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 to children.
  • 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?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆