代码之家  ›  专栏  ›  技术社区  ›  j3141592653589793238

如何编写NSOutlineView?

  •  12
  • j3141592653589793238  · 技术社区  · 8 年前

    我在Xcode 8(Swift 3)中创建NSOutlineView时遇到问题。我有一个plist文件,其中有一些信息,我想在大纲视图中展示。plist文件如下所示(示例):

    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
    

    name            Harry Watson
    age             99
    birthplace      Westminster
    birthdate       01/01/1000
    > hobbies       ...             (<- this should be expandable)
    

    我已经在谷歌上搜索了NSOutlineView教程,但我找到的都是raywenderlich。com,所以我读了一点,但在我看来这并不容易。

    func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {}
    

    我不知道在那里写什么。

    2 回复  |  直到 7 年前
        1
  •  31
  •   Code Different    4 年前

    我发现雷·温德里奇的教程质量参差不齐。在笑话中,冗长,假设你对Swift一无所知的一步一步的牵手对我来说太恶心了。这是一个简单的教程,涵盖了手动和通过Cocoa绑定填充大纲视图的基本知识。


    NSOutlineView 就是必须给每一行一个唯一的标识符,可以是字符串、数字或表示该行的对象。 NSOutlineView item 项目 ,您将查询数据模型以用数据填充大纲视图。

    这个答案提出了3种方法:

    1. 但我不建议将其用于生产代码。
    2. 简化:大纲视图仍然是手动填充的,但方法更优雅。这是我用于自己的生产代码的代码。

    1.手动填充大纲视图

    NSOutlineView 只有两列:Key和Value。

    选择第一列并将其标识符更改为 keyColumn valueColumn :

    Set the identifier for the Key column. Repeat for the Value column

    将单元格的标识符设置为 outlineViewCell .你只需要做一次。 Set the identifier for the cell

    将以下内容复制并粘贴到 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
        }
    }
    

    NSOutlineView


    2、更精简的方法

    import Cocoa
    
    /// The data Model
    struct Person {
        var name: String
        var age: Int
        var birthPlace: String
        var birthDate: Date
        var hobbies: [String]
    }
    
    /// Representation of a row in the outline view
    struct OutlineViewRow {
        var key: String
        var value: Any?
        var children = [OutlineViewRow]()
        
        static func rowsFrom( person: Person) -> [OutlineViewRow] {
            let hobbiesChildren = person.hobbies.map { OutlineViewRow(key: $0) }
            return [
                OutlineViewRow(key: "Age", value: person.age),
                OutlineViewRow(key: "Birth Place", value: person.birthPlace),
                OutlineViewRow(key: "Birth Date", value: person.birthDate),
                OutlineViewRow(key: "Hobbies", children: hobbiesChildren)
            ]
        }
    }
    
    /// A listing of all available columns in the outline view.
    ///
    /// Since repeating string literals is error prone, we define them in a single location here.
    /// The literals must match the column identifiers in the Story Board
    enum OutlineViewColumn: String {
        case key = "keyColumn"
        case value = "valueColumn"
        
        init?(tableColumn: NSTableColumn) {
            self.init(rawValue: tableColumn.identifier.rawValue)
        }
    }
    
    
    class ViewController: NSViewController {
        @IBOutlet weak var outlineView: NSOutlineView!
        
        let person = Person(name: "Harry Watson", age: 99, birthPlace: "Westminster",
                            birthDate: DateComponents(calendar: .current, year: 1985, month: 1, day: 1).date!,
                            hobbies: ["Tennis", "Piano"])
        var rows = [OutlineViewRow]()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            self.rows = OutlineViewRow.rowsFrom(person: self.person)
            outlineView.dataSource = self
            outlineView.delegate = self
        }
    }
    
    extension ViewController: NSOutlineViewDataSource, NSOutlineViewDelegate {
        
        /// Return the item representing each row
        /// If item == nil, it is the root of the outline view and is invisible
        func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
            switch item {
            case nil:
                return self.rows[index]
            case let row as OutlineViewRow:
                return row.children[index]
            default:
                return NSNull()
            }
        }
        
        /// Return the number of children for each row
        func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
            switch item {
            case nil:
                return self.rows.count
            case let row as OutlineViewRow:
                return row.children.count
            default:
                return 0
            }
        }
        
        /// Determine if the row is expandable
        func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
            switch item {
            case let row as OutlineViewRow:
                return !row.children.isEmpty
            default:
                return false
            }
        }
        
        /// Return content of the cell
        func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
            guard let row = item as? OutlineViewRow,
                  let column = OutlineViewColumn(tableColumn: tableColumn!)
            else {
                fatalError("Invalid row and column combination")
            }
            
            let text: String
            switch column {
            case .key:
                text = row.key
            case .value:
                text = row.value == nil ? "" : "\(row.value!)"
            }
            
            let identifier = NSUserInterfaceItemIdentifier("outlineViewCell")
            let view = outlineView.makeView(withIdentifier: identifier, owner: self) as! NSTableCellView
            view.textField?.stringValue = text
            
            return view
        }
    }
    

    3.使用Cocoa绑定

    密码

    对于本例,让我们通过使用 NSOutlineView

    // 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()
        }
    }
    

    界面生成器设置

    • Cmd + Opt + 4 ). 将其子键路径设置为 children .
    • Cmd + Opt + 7 )并为IB对象设置绑定,如下所示。

    Tree Controller's Attributes

    | 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)  |                    |                 |                 |                   |
    

    (不要将表格视图单元格与表格单元格视图混淆。我知道命名很糟糕)

    后果

    NSOutline View with Cocoa Bindings

    DateFormatter 在这两种方法中都可以获得更好的日期输出,但这对于这个问题来说并不重要。

        2
  •  0
  •   koen    3 年前

    这是一个清晰的示例,非常适合作为使用NSOutlineView的开始。
    当我使用后来的Swift版本时,我不得不改变

    switch (columnIdentifier, item)

    switch (columnIdentifier.rawValue, item)

    Interface Builder还对设置进行了正确的调整

    let cell = outlineView.make(withIdentifier: "outlineViewCell", owner: self) as! NSTableCellView

    let cell = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "outlineViewCell"), owner: self) as! NSTableCellView