swiftuiappkitnsoutlineviewnsviewrepresentable

NSOutlineView doesn't call delegates when wrapped with NSViewRepresentable


Due to limitations with DisclosureGroup functionality with a List, I'm looking into using NSOutlineView wrapped with NSViewRepresentable.

However, with this simple example, when I create the view I'm not seeing all of the required NSOutlineViewDataSource functions being called on the Coordinator that I assume should populate the view. Only outlineView(_:numberOfChildrenOfItem:) is called, then updateNSView(_:context:) is called after that.

I added a border to check the wrapped view is actually being drawn in the ContentView and it seems to be.

What am I missing?

Wrapping the NSOutlineView:

import SwiftUI

struct AppKitOutlineView: NSViewRepresentable {
    
    //ViewRepresentable Stuff
    
    class Coorindinator: NSObject, NSOutlineViewDataSource{
        
        var theData = ["One", "Two", "Three", "Four"]

        //NSOutlineViewDataSource Stuff
        
        func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
            print(#function)
            return theData[index]
        }
        
        func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
            print(#function)
            return false
        }
        
        func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
            print(#function)
            print (theData.count)
            return theData.count
        }
        
        func outlineView(_ outlineView: NSOutlineView, objectValueFor tableColumn: NSTableColumn?, byItem item: Any?) -> Any? {
            print(#function)
            return item    
        }
        
    }
    
    func makeCoordinator() -> Coorindinator {
        print(#function)
        return Coorindinator()
    }
    
    func makeNSView(context: Context) -> some NSView {
        print(#function)
        let outlineView = NSOutlineView()
        outlineView.dataSource = context.coordinator
        return outlineView
    }
    
    func updateNSView(_ nsView: NSViewType, context: Context) {
        print(#function)
        //Nothing yet
    }
} 

ContentView:

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack{
            Text("Thing")
            AppKitOutlineView().border(Color.red)
        }
    }
}

This is what I can see is happening in the console:

makeCoordinator()
makeNSView(context:)
outlineView(_:numberOfChildrenOfItem:)
4
updateNSView(_:context:)

And this is what I can see in the program UI: program UI


Solution

  • The others DataSource depend on outline view appearance, which return nil in your example. So that's why it's not get called. Try to conform dataCellFor tableColumn in NSOutlineViewDelegate:

    func makeNSView(context: Context) -> some NSView {
        ...
        outlineView.delegate = context.coordinator
    }
    
    class Coorindinator: NSObject, NSOutlineViewDataSource, NSOutlineViewDelegate {
        func outlineView(_ outlineView: NSOutlineView, dataCellFor tableColumn: NSTableColumn?, item: Any) -> NSCell? {
            if let item = item as? String {
                let cell = NSCell(textCell: item)
                return cell
            } else {
                return NSCell()
            }
        }
        ...
    }
    

    Then the output should be:

    makeCoordinator()
    makeNSView(context:)
    outlineView(_:numberOfChildrenOfItem:)
    4
    outlineView(_:child:ofItem:)
    outlineView(_:isItemExpandable:)
    outlineView(_:child:ofItem:)
    outlineView(_:isItemExpandable:)
    outlineView(_:child:ofItem:)
    outlineView(_:isItemExpandable:)
    outlineView(_:child:ofItem:)
    outlineView(_:isItemExpandable:)
    updateNSView(_:context:)
    updateNSView(_:context:)
    makeCoordinator()
    makeNSView(context:)
    outlineView(_:numberOfChildrenOfItem:)
    ...
    

    enter image description here