构建带有复选标记的 NSOutline 视图

2024-03-22

我希望使用正确的 Apple 推荐方法向 NSOutlineview 添加复选框 - 但文档中尚不清楚。

如何添加行为以允许用户如果单击父复选框,则它将选择子项,如果我单击它 - 它将取消选择该项目的子项?

编辑:我简化了我的问题并添加了图像以使其更清晰(希望如此)

我的方法: 我一直在使用 Code Different 的精彩答案在我的 mac 应用程序中构建大纲视图。https://stackoverflow.com/a/45384599/559760 https://stackoverflow.com/a/45384599/559760- 我选择使用手动过程而不是使用 CocoaBindings 来填充 NSoutLine 视图。

我在堆栈视图中添加了一个复选框,这似乎是正确的方法:

我的解决方案涉及创建一个数组来保存视图控制器中的所选项目,然后创建用于添加和删除的函数

var selectedItems: [Int]?

@objc func cellWasClicked(sender: NSButton) {
    let newCheckBoxState = sender.state
    let tag = sender.tag
    switch newCheckBoxState {
    case NSControl.StateValue.on:
        print("adding- \(sender.tag)")
    case NSControl.StateValue.off:
        print("removing- \(sender.tag)")
    default:
        print("unhandled button state \(newCheckBoxState)")
    }

我通过分配给复选框的标签来识别复选框


为了未来的 Google 员工的利益,我将重复我在我的文章中写过的内容其他答案 https://stackoverflow.com/questions/45373039/how-to-program-a-nsoutlineview/45384599#45384599。这里的区别在于,它有一个额外的要求,即列是可编辑的,并且我已经改进了该技术。


关键是NSOutlineView是你必须为每一行提供一个标识符,可以是唯一标识该行的字符串、数字或对象。NSOutlineView称之为item。基于此item,您将查询数据模型以填充大纲。

在此答案中,我们将设置一个包含 2 列的大纲视图:可编辑的已选择列和不可编辑的Title column.


界面生成器设置

  • 选择第一列并将其标识符设置为isSelected
  • 选择第二列并将其标识符设置为title
  • 选择第一列中的单元格并将其标识符更改为isSelectedCell
  • 选择第二列中的单元格并将其标识符更改为titleCell

一致性在这里很重要。单元格的标识符应等于其列的标识符 +Cell.


带有复选框的单元格

默认NSTableCellView包含不可编辑的文本字段。我们想要一个复选框,因此我们必须设计自己的单元格。

CheckboxCellView.swift

import Cocoa

/// A set of methods that `CheckboxCelView` use to communicate changes to another object
protocol CheckboxCellViewDelegate {
    func checkboxCellView(_ cell: CheckboxCellView, didChangeState state: NSControl.StateValue)
}

class CheckboxCellView: NSTableCellView {

    /// The checkbox button
    @IBOutlet weak var checkboxButton: NSButton!

    /// The item that represent the row in the outline view
    /// We may potentially use this cell for multiple outline views so let's make it generic
    var item: Any?

    /// The delegate of the cell
    var delegate: CheckboxCellViewDelegate?

    override func awakeFromNib() {
        checkboxButton.target = self
        checkboxButton.action = #selector(self.didChangeState(_:))
    }

    /// Notify the delegate that the checkbox's state has changed
    @objc private func didChangeState(_ sender: NSObject) {
        delegate?.checkboxCellView(self, didChangeState: checkboxButton.state)
    }
}

连接插座

  • 删除默认文本字段isSelected column
  • 从对象库中拖入复选框
  • 选择NSTableCellView并将其类更改为CheckboxCellView
  • 打开助理编辑器并连接插座

视图控制器

最后是视图控制器的代码:

import Cocoa


/// A class that represents a row in the outline view. Add as many properties as needed
/// for the columns in your outline view.
class OutlineViewRow {
    var title: String
    var isSelected: Bool
    var children: [OutlineViewRow]

    init(title: String, isSelected: Bool, children: [OutlineViewRow] = []) {
        self.title = title
        self.isSelected = isSelected
        self.children = children
    }

    func setIsSelected(_ isSelected: Bool, recursive: Bool) {
        self.isSelected = isSelected
        if recursive {
            self.children.forEach { $0.setIsSelected(isSelected, recursive: true) }
        }
    }
}

/// A enum that represents the list of columns in the outline view. Enum is preferred over
/// string literals as they are checked at compile-time. Repeating the same strings over
/// and over again are error-prone. However, you need to make the Column Identifier in
/// Interface Builder with the raw value used here.
enum OutlineViewColumn: String {
    case isSelected = "isSelected"
    case title = "title"

    init?(_ tableColumn: NSTableColumn) {
        self.init(rawValue: tableColumn.identifier.rawValue)
    }

    var cellIdentifier: NSUserInterfaceItemIdentifier {
        return NSUserInterfaceItemIdentifier(self.rawValue + "Cell")
    }
}


class ViewController: NSViewController {
    @IBOutlet weak var outlineView: NSOutlineView!

    /// The rows of the outline view
    let rows: [OutlineViewRow] = {
        var child1 = OutlineViewRow(title: "p1-child1", isSelected: true)
        var child2 = OutlineViewRow(title: "p1-child2", isSelected: true)
        var child3 = OutlineViewRow(title: "p1-child3", isSelected: true)
        let parent1 = OutlineViewRow(title: "parent1", isSelected: true, children: [child1, child2, child3])

        child1 = OutlineViewRow(title: "p2-child1", isSelected: true)
        child2 = OutlineViewRow(title: "p2-child2", isSelected: true)
        child3 = OutlineViewRow(title: "p2-child3", isSelected: true)
        let parent2 = OutlineViewRow(title: "parent2", isSelected: true, children: [child1, child2, child3])

        child1 = OutlineViewRow(title: "p3-child1", isSelected: true)
        child2 = OutlineViewRow(title: "p3-child2", isSelected: true)
        child3 = OutlineViewRow(title: "p3-child3", isSelected: true)
        let parent3 = OutlineViewRow(title: "parent3", isSelected: true, children: [child1, child2, child3])

        child3 = OutlineViewRow(title: "p4-child3", isSelected: true)
        child2 = OutlineViewRow(title: "p4-child2", isSelected: true, children: [child3])
        child1 = OutlineViewRow(title: "p4-child1", isSelected: true, children: [child2])
        let parent4 = OutlineViewRow(title: "parent4", isSelected: true, children: [child1])

        return [parent1, parent2, parent3, parent4]
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        outlineView.dataSource = self
        outlineView.delegate = self
    }
}

extension ViewController: NSOutlineViewDataSource, NSOutlineViewDelegate {
    /// Returns how many children a row has. `item == nil` means the root row (not visible)
    func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
        switch item {
        case nil: return rows.count
        case let row as OutlineViewRow: return row.children.count
        default: return 0
        }
    }

    /// Returns the object that represents the row. `NSOutlineView` calls this the `item`
    func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
        switch item {
        case nil: return rows[index]
        case let row as OutlineViewRow: return row.children[index]
        default: return NSNull()
        }
    }

    /// Returns whether the row can be expanded
    func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
        switch item {
        case nil: return !rows.isEmpty
        case let row as OutlineViewRow: return !row.children.isEmpty
        default: return false
        }
    }

    /// Returns the view that display the content for each cell of the outline view
    func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
        guard let item = item as? OutlineViewRow, let column = OutlineViewColumn(tableColumn!) else { return nil }

        switch column {
        case .isSelected:
            let cell = outlineView.makeView(withIdentifier: column.cellIdentifier, owner: self) as! CheckboxCellView
            cell.checkboxButton.state = item.isSelected ? .on : .off
            cell.delegate = self
            cell.item = item
            return cell

        case .title:
            let cell = outlineView.makeView(withIdentifier: column.cellIdentifier, owner: self) as! NSTableCellView
            cell.textField?.stringValue = item.title
            return cell
        }
    }
}

extension ViewController: CheckboxCellViewDelegate {
    /// A delegate function where we can act on update from the checkbox in the "Is Selected" column
    func checkboxCellView(_ cell: CheckboxCellView, didChangeState state: NSControl.StateValue) {
        guard let item = cell.item as? OutlineViewRow else { return }

        // The row and its children are selected if state == .on
        item.setIsSelected(state == .on, recursive: true)

        // This is more efficient than calling reload on every child since collapsed children are
        // not reloaded. They will be reloaded when they become visible
        outlineView.reloadItem(item, reloadChildren: true)
    }
}

Result

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

构建带有复选标记的 NSOutline 视图 的相关文章

随机推荐