Outline View

xib-based

Drag NSOutlineView control to xib interface from control libraries, change control custom class to component class name of your exported.

Unlike other controls, there are several initialization and configuration methods in the controller's viewDidLoad method. The detailed code is as follows:

import Cocoa
                        
class ViewController: NSViewController {

    @IBOutlet weak var treeView: HDOutlineView!
    
    var treeModel: HDTreeViewNode = HDTreeViewNode()

    var fillColor: NSColor?
    var fillGradient: NSGradient?
    
    var arrowType: ExpandArrowType = .normal
    var arrowFillColor: NSColor?
    
    var rowFillColor: NSColor?
    var rowGradient: NSGradient?
    var rowHighlightColor: NSColor? = NSColor(hex: 0xDB4D52)
    var rowHighlightGradient: NSGradient?
    
    var rowStrokeColor: NSColor?
    var rowStrokeWidth: CGFloat?
    
    var rowTextColor: NSColor?
    var rowTextFont: NSFont?
    var rowTextAlignment: NSTextAlignment?
    var rowTextLineBreakMode: NSLineBreakMode?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        //Style Config
        treeViewStyleConfig()
        //Set Delegate
        treeViewDelegateConfig()
        //Test Data Config
        configViewData()
    }
   
    func treeViewStyleConfig() {
        self.treeView.fillGradient = NSGradient.gradient(colorsStr: "rgba(130,0,233,1.0)-rgba(206,0,254,1.0)", locationsStr: "0.0-1.0")
        self.arrowType = .triangle
        self.arrowFillColor = NSColor(calibratedRed: 0.76, green: 0.24, blue: 0.92, alpha: 1.0)
        self.rowFillColor = NSColor(calibratedRed: 0.20, green: 0.55, blue: 0.96, alpha: 1.0)
        self.rowHighlightColor = NSColor(calibratedRed: 0.02, green: 0.25, blue: 0.76, alpha: 1.0)
        self.rowStrokeWidth = 0.5
        self.rowTextColor = NSColor(calibratedRed: 1.00, green: 1.00, blue: 1.00, alpha: 1.0)
        self.rowTextLineBreakMode = .byTruncatingMiddle
        updateTreeExpandArrow()
    }
    
    func treeViewDelegateConfig() {
        self.treeView.delegate = self
        self.treeView.dataSource = self
    }
    
    func updateTreeExpandArrow() {
        let frame = NSRect(x: 0, y: 0, width: 13, height: 13)
        if arrowType == .simple {
            treeView.arrowButtonImage = NSImage.arrowButtonImage(in: frame, arrowSize: NSSize(width: 6, height: 8), fillColor: arrowFillColor, filled: false )
            treeView.arrowButtonAltImage = NSImage.arrowButtonAltImage(in: frame, arrowSize: NSSize(width: 8, height: 6), fillColor: arrowFillColor, filled: false )
        }
        else if arrowType == .triangle {
            treeView.arrowButtonImage = NSImage.arrowButtonImage(in: frame, arrowSize: NSSize(width: 8, height: 9), fillColor: arrowFillColor )
            treeView.arrowButtonAltImage = NSImage.arrowButtonAltImage(in: frame, arrowSize: NSSize(width: 9, height: 8), fillColor: arrowFillColor)
        }
        else {
            treeView.arrowButtonImage = nil
            treeView.arrowButtonAltImage = nil
        }
    }
    
    func configViewData() {
        self.treeModel.childNodes.removeAll()
        
        let rootNode = HDTreeViewNode()
        rootNode.name = "Row1"
        
        let rootNode2 = HDTreeViewNode()
        rootNode2.name = "Row2"
        
        self.treeModel.childNodes.append(rootNode)
        self.treeModel.childNodes.append(rootNode2)
        
        
        let level11Node = HDTreeViewNode()
        level11Node.name = "Ecommerce"
        
        let level12Node = HDTreeViewNode()
        level12Node.name = "Game"
        
        let level13Node = HDTreeViewNode()
        level13Node.name = "Music"
        
        
        rootNode.childNodes.append(level11Node)
        rootNode.childNodes.append(level12Node)
        rootNode.childNodes.append(level13Node)
        
        rootNode2.childNodes.append(level13Node)
        
        
        let level21Node = HDTreeViewNode()
        level21Node.name = "Development"
        
        let level22Node = HDTreeViewNode()
        level22Node.name = "Operation"
        
        level11Node.childNodes.append(level21Node)
        level11Node.childNodes.append(level22Node)
        
        self.treeView.reloadData()
    }
}


extension ViewController: NSOutlineViewDelegate {
    
    func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
        
        var view = outlineView.makeView(withIdentifier: (tableColumn?.identifier)!, owner: self)
        
        if view == nil {
            let containerView = NSTableCellView()
            let imageView = NSImageView()
            let textField = NSTextField.label()
            textField.alignment = .left
            containerView.addSubview(imageView)
            containerView.addSubview(textField)
            
            imageView.translatesAutoresizingMaskIntoConstraints = false
            imageView.width = 28
            imageView.height = 28
            imageView.left = 4
            imageView.centerY = containerView.centerY
            
            textField.translatesAutoresizingMaskIntoConstraints = false
            textField.left = imageView.right + 4
            textField.right = containerView.right - 4
            textField.height = 17
            textField.centerY = containerView.centerY
            
            containerView.identifier = tableColumn?.identifier
            
            view = containerView
        }
        
        guard let model = item as? HDTreeViewNode else {
            return view
        }
        
        let subviews  = view?.subviews
        var imageView: NSImageView?
        var textField: NSTextField?
        
        if let imageViewTemp = subviews?[0] as? NSImageView {
            imageView = imageViewTemp
        }
        if let textFieldTemp = subviews?[1] as? NSTextField {
            textField = textFieldTemp
        }
        if let textFieldTemp = subviews?[0] as? NSTextField {
            textField = textFieldTemp
        }
        if let imageViewTemp = subviews?[1] as? NSImageView {
            imageView = imageViewTemp
        }
        
        textField?.stringValue = model.name!
        
        if let textColor = rowTextColor {
            textField?.textColor = textColor
        }
        if let textFont = rowTextFont {
            textField?.font = textFont
        }
        if let alignment = rowTextAlignment {
            textField?.alignment = alignment
        }
        if let lineBreakMode = rowTextLineBreakMode {
            textField?.lineBreakMode = lineBreakMode
        }
        
        if model.childNodes.count <= 0 {
            imageView?.image = NSImage(named: NSImage.listViewTemplateName)
        }
        else {
            imageView?.image = NSImage(named: NSImage.folderName)
        }
        return view
    }
    
    func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat {
        return 40
    }
    
    
    func outlineView(_ outlineView: NSOutlineView, rowViewForItem item: Any) -> NSTableRowView? {
        let identifier  = NSUserInterfaceItemIdentifier(rawValue: "SelectionRowView")
        var rowView = outlineView.makeView(withIdentifier:identifier, owner: nil) as? HDOutlineRowView
        
        if rowView == nil {
            rowView = HDOutlineRowView()
            rowView?.identifier = identifier
        }
        
        rowView?.fillColor = self.rowFillColor
        rowView?.gradient = self.rowGradient
        rowView?.highlightColor = self.rowHighlightColor
        rowView?.highlightGradient = self.rowHighlightGradient
        rowView?.strokeColor = self.rowStrokeColor
        rowView?.strokeWidth = self.rowStrokeWidth
        
        return rowView
    }
    
}

extension ViewController: NSOutlineViewDataSource {
    
    func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
        let rootNode:HDTreeViewNode
        if item != nil {
            rootNode = item as! HDTreeViewNode
        }
        else {
            rootNode =  self.treeModel
        }
        return rootNode.childNodes.count
    }
    
    func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
        let rootNode:HDTreeViewNode
        if item != nil {
            rootNode = item as! HDTreeViewNode
        }
        else {
            rootNode =  self.treeModel
        }
        return rootNode.childNodes[index]
    }
    
    func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
        let rootNode:HDTreeViewNode = item as! HDTreeViewNode
        return rootNode.childNodes.count > 0
    }
}


code-based

Declare lazy load variable treeView and scrollView in the controller class , add scrollView to the view. implement several initialization and configuration methods in the controller's viewDidLoad method. The detailed code is as follows:

import Cocoa
class ViewController: NSViewController {
    var treeModel: HDTreeViewNode = HDTreeViewNode()
    
    lazy var treeView: HDOutlineView = {
        let treeView = HDOutlineView()
        treeView.focusRingType = .none
        treeView.delegate = self
        treeView.dataSource = self
        let column1 = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("name"))
        column1.title = "name"
        column1.width = 100
        column1.maxWidth = 100
        column1.minWidth = 100
        treeView.addTableColumn(column1)
        treeView.outlineTableColumn = column1
        treeView.headerView = nil
        return treeView
    }()
    
    lazy var scrollView: NSScrollView = {
        let scrollView = NSScrollView()
        scrollView.focusRingType = .none
        scrollView.autohidesScrollers = true
        scrollView.borderType = .noBorder
        scrollView.documentView = self.treeView
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        return scrollView
    }()
    
    var fillColor: NSColor?
    var fillGradient: NSGradient?
    
    var arrowType: ExpandArrowType = .normal
    var arrowFillColor: NSColor?
    
    
    var rowFillColor: NSColor?
    var rowGradient: NSGradient?
    var rowHighlightColor: NSColor? = NSColor(hex: 0xDB4D52)
    var rowHighlightGradient: NSGradient?
    
    var rowStrokeColor: NSColor?
    var rowStrokeWidth: CGFloat?
    
    var rowTextColor: NSColor?
    var rowTextFont: NSFont?
    var rowTextAlignment: NSTextAlignment?
    var rowTextLineBreakMode: NSLineBreakMode?
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        //Style Config
        treeViewStyleConfig()
        //Add Subview
        addTabView()
        //Set AutoLayout
        setupAutolayout()
        //Set Delegate
        treeViewDelegateConfig()
        //Test Data Config
        configViewData()
    }
    
    func addTabView() {
        self.view.addSubview(scrollView)
    }
    
    func setupAutolayout() {
        scrollView.left = 10
        scrollView.right = -10
        scrollView.top = 10
        scrollView.bottom = -10
    }
    
    func treeViewStyleConfig() {
        self.treeView.fillColor = NSColor(calibratedRed: 1.00, green: 1.00, blue: 1.00, alpha: 1.0)
        self.arrowType = .normal
        self.arrowFillColor = NSColor(calibratedRed: 0.43, green: 0.43, blue: 0.43, alpha: 1.0)
        self.rowFillColor = NSColor(calibratedRed: 1.00, green: 1.00, blue: 1.00, alpha: 1.0)
        self.rowHighlightColor = NSColor(calibratedRed: 0.02, green: 0.26, blue: 0.76, alpha: 1.0)
        self.rowStrokeWidth = 0.5
        self.rowTextColor = NSColor(calibratedRed: 0.00, green: 0.00, blue: 0.00, alpha: 1.0)
        self.rowTextLineBreakMode = .byTruncatingMiddle
        updateTreeExpandArrow()
    }
    
    func treeViewDelegateConfig() {
        self.treeView.focusRingType = .none
        self.treeView.delegate = self
        self.treeView.dataSource = self
    }
    
    func updateTreeExpandArrow() {
        let frame = NSRect(x: 0, y: 0, width: 13, height: 13)
        if arrowType == .simple {
            treeView.arrowButtonImage = NSImage.arrowButtonImage(in: frame, arrowSize: NSSize(width: 6, height: 8), fillColor: arrowFillColor, filled: false )
            treeView.arrowButtonAltImage = NSImage.arrowButtonAltImage(in: frame, arrowSize: NSSize(width: 8, height: 6), fillColor: arrowFillColor, filled: false )
        }
        else if arrowType == .triangle {
            treeView.arrowButtonImage = NSImage.arrowButtonImage(in: frame, arrowSize: NSSize(width: 8, height: 9), fillColor: arrowFillColor )
            treeView.arrowButtonAltImage = NSImage.arrowButtonAltImage(in: frame, arrowSize: NSSize(width: 9, height: 8), fillColor: arrowFillColor)
        }
        else {
            treeView.arrowButtonImage = nil
            treeView.arrowButtonAltImage = nil
        }
    }
    
    func configViewData() {
        
        self.treeModel.childNodes.removeAll()
        
        let rootNode = HDTreeViewNode()
        rootNode.name = "Row1"
        
        let rootNode2 = HDTreeViewNode()
        rootNode2.name = "Row2"
        
        self.treeModel.childNodes.append(rootNode)
        self.treeModel.childNodes.append(rootNode2)
        
        
        let level11Node = HDTreeViewNode()
        level11Node.name = "Ecommerce"
        
        let level12Node = HDTreeViewNode()
        level12Node.name = "Game"
        
        let level13Node = HDTreeViewNode()
        level13Node.name = "Music"
        
        
        rootNode.childNodes.append(level11Node)
        rootNode.childNodes.append(level12Node)
        rootNode.childNodes.append(level13Node)
        
        rootNode2.childNodes.append(level13Node)
        
        
        let level21Node = HDTreeViewNode()
        level21Node.name = "Development"
        
        let level22Node = HDTreeViewNode()
        level22Node.name = "Operation"
        
        level11Node.childNodes.append(level21Node)
        level11Node.childNodes.append(level22Node)
        
        self.treeView.reloadData()
    }
    
}


extension ViewController: NSOutlineViewDelegate {
    
    func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
        
        // let identifier  = NSUserInterfaceItemIdentifier(rawValue: (tableColumn?.identifier)!)
        
        var view = outlineView.makeView(withIdentifier: (tableColumn?.identifier)!, owner: self)
        
        if view == nil {
            let containerView = NSTableCellView()
            let imageView = NSImageView()
            let textField = NSTextField.label()
            textField.alignment = .left
            containerView.addSubview(imageView)
            containerView.addSubview(textField)
            
            imageView.translatesAutoresizingMaskIntoConstraints = false
            imageView.width = 28
            imageView.height = 28
            imageView.left = 4
            imageView.centerY = containerView.centerY
            
            textField.translatesAutoresizingMaskIntoConstraints = false
            textField.left = imageView.right + 4
            textField.right = containerView.right - 4
            textField.height = 17
            textField.centerY = containerView.centerY
            
            containerView.identifier = tableColumn?.identifier
            
            view = containerView
        }
        
        guard let model = item as? HDTreeViewNode else {
            return view
        }
        
        let subviews  = view?.subviews
        var imageView: NSImageView?
        var textField: NSTextField?
        
        if let imageViewTemp = subviews?[0] as? NSImageView {
            imageView = imageViewTemp
        }
        if let textFieldTemp = subviews?[1] as? NSTextField {
            textField = textFieldTemp
        }
        if let textFieldTemp = subviews?[0] as? NSTextField {
            textField = textFieldTemp
        }
        if let imageViewTemp = subviews?[1] as? NSImageView {
            imageView = imageViewTemp
        }
        
        textField?.stringValue = model.name!
        
        if let textColor = rowTextColor {
            textField?.textColor = textColor
        }
        if let textFont = rowTextFont {
            textField?.font = textFont
        }
        if let alignment = rowTextAlignment {
            textField?.alignment = alignment
        }
        if let lineBreakMode = rowTextLineBreakMode {
            textField?.lineBreakMode = lineBreakMode
        }
        
        if model.childNodes.count <= 0 {
            imageView?.image = NSImage(named: NSImage.listViewTemplateName)
        }
        else {
            imageView?.image = NSImage(named: NSImage.folderName)
        }
        return view
    }
    
    func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat {
        return 40
    }
    
    
    func outlineView(_ outlineView: NSOutlineView, rowViewForItem item: Any) -> NSTableRowView? {
        let identifier  = NSUserInterfaceItemIdentifier(rawValue: "SelectionRowView")
        var rowView = outlineView.makeView(withIdentifier:identifier, owner: nil) as? HDOutlineRowView
        
        if rowView == nil {
            rowView = HDOutlineRowView()
            rowView?.identifier = identifier
        }
        
        rowView?.fillColor = self.rowFillColor
        rowView?.gradient = self.rowGradient
        rowView?.highlightColor = self.rowHighlightColor
        rowView?.highlightGradient = self.rowHighlightGradient
        rowView?.strokeColor = self.rowStrokeColor
        rowView?.strokeWidth = self.rowStrokeWidth
        
        return rowView
    }
    
    func outlineView(_ outlineView: NSOutlineView, didDrag tableColumn: NSTableColumn) {
        Swift.print("didDrag outlineView")
    }
}

extension ViewController: NSOutlineViewDataSource {
    
    func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
        let rootNode:HDTreeViewNode
        if item != nil {
            rootNode = item as! HDTreeViewNode
        }
        else {
            rootNode =  self.treeModel
        }
        return rootNode.childNodes.count
    }
    
    func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
        let rootNode:HDTreeViewNode
        if item != nil {
            rootNode = item as! HDTreeViewNode
        }
        else {
            rootNode =  self.treeModel
        }
        return rootNode.childNodes[index]
    }
    
    func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
        let rootNode:HDTreeViewNode = item as! HDTreeViewNode
        return rootNode.childNodes.count > 0
    }
}