I am currently working on a macOS AppKit application and trying to implement an NSOutlineView with custom data using NSOutlineViewDataSource and NSOutlineViewDelegate. However, despite implementing the necessary methods, I am facing an issue where the outline view is not populating with data.
Here's a brief overview of my setup:
My Entire Code along with custom implementation:
class OutlineViewController: NSViewController, NSOutlineViewDataSource, NSOutlineViewDelegate {
// MARK: - Properties
private var outlineView: NSOutlineView!
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
let rootNode = Node(name: "Root Node")
let childNode1 = Node(name: "Child Node 1")
let childNode2 = Node(name: "Child Node 2")
rootNode.addChild(childNode1)
rootNode.addChild(childNode2)
}
override func loadView() {
view = NSView(frame: NSRect(x: 0, y: 0, width: (NSScreen.main?.frame.width)!, height: (NSScreen.main?.frame.height)!)) // Set some default values
// Create an outline view
outlineView = NSOutlineView(frame: NSRect(x: 0, y: 0, width: (NSScreen.main?.frame.width)!, height: (NSScreen.main?.frame.height)!))
// Register the OutlineCell
// Set default row height
outlineView.rowHeight = 50
// Set the outline view's delegate and data source
outlineView.delegate = self
outlineView.dataSource = self
// Reload the outline view
outlineView.reloadData()
let scrollview = NSScrollView(frame: NSRect(x: 0, y: 0, width: 800, height: 600))
scrollview.documentView = outlineView
view.addSubview(scrollview)
NSLayoutConstraint.activate([
outlineView.topAnchor.constraint(equalTo: scrollview.topAnchor),
outlineView.bottomAnchor.constraint(equalTo: scrollview.bottomAnchor),
outlineView.leadingAnchor.constraint(equalTo: scrollview.leadingAnchor),
outlineView.trailingAnchor.constraint(equalTo: scrollview.trailingAnchor)
])
}
// MARK: - NSOutlineViewDataSource
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
if let item = item as? Node {
return item.children[index]
} else {
return 0
}
}
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
if let item = item as? Node {
return item.children.count > 0
} else {
return false
}
}
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
if let item = item as? Node {
return item.children.count
} else {
return 0
}
}
// MARK: - NSOutlineViewDelegate
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
guard let cell = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: TableCellView.identifier), owner: self) as? TableCellView else {
let cell = TableCellView()
cell.profileImage.image = NSImage(named: "boy")
cell.identifier = NSUserInterfaceItemIdentifier(TableCellView.identifier)
cell.label.stringValue = "BOY"
return cell
}
cell.profileImage.image = NSImage(named: "girl")
cell.label.stringValue = "GIRL"
return cell
}
}
}
class Node {
let name: String
var children: [Node] = []
init(name: String) {
self.name = name
}
func addChild(_ child: Node) {
children.append(child)
}
}
class TableCellView: NSTableCellView {
static var identifier = "TableCell"
var profileImage = NSImageView()
var label = NSTextField(labelWithString: "")
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
addSubview(profileImage)
addSubview(label)
profileImage.wantsLayer = true
profileImage.animates = true
profileImage.layer?.cornerRadius = 45
label.isEditable = false
label.alignment = .center
label.font = .boldSystemFont(ofSize: 20)
profileImage.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
profileImage.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 50),
profileImage.widthAnchor.constraint(equalToConstant: 90),
profileImage.topAnchor.constraint(equalTo: topAnchor, constant: 20),
profileImage.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20),
])
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: profileImage.trailingAnchor),
label.topAnchor.constraint(equalTo: topAnchor, constant: 55),
label.bottomAnchor.constraint(equalTo: bottomAnchor),
label.trailingAnchor.constraint(equalTo: trailingAnchor)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I am only getting empty scroll view.
There are several issues:
outlineView.addTableColumn(NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: TableCellView.identifier)))
and at the end of loadView()
:
outlineView.sizeLastColumnToFit()
rootNode
anywhere. In the data source methods, if item
is nil
then the item is the root object.func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
if let item = item as? Node {
return item.children[index]
} else {
return rootNode.children[index]
}
}
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
if let item = item as? Node {
return item.children.count > 0
} else {
return rootNode.children.count > 0
}
}
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
if let item = item as? Node {
return item.children.count
} else {
return rootNode.children.count
}
}
viewDidLoad
is called after loadView
so reloadData()
is called before rootNode
is populated. Call reloadData()
in viewDidLoad()
.
The contraints of TableCellView
need some work.