Using NSCollectionView from SwiftUI

So far it seems that in order to use NSCollectionView or NSTableView with SwiftUI, you have to wrap it all up yourself and the experience is not fluid at all.

Am I missing something obvious? Did anyone do this already? If so, any tips, sample code?


Thanks,

Dmitry

Accepted Reply

Hi Dmitry,


For embedding AppKit into SwiftUI the correct way form what I can currently tell is using either NSViewControllerRepresentable or NSViewRepresentable protocols.


The collection view should be built with AppKit as was done before SwiftUI existed. In the case of embedding AppKit content, think of SwiftUI and just a markup language for displaying and organizing your UI instead of storyboards of nibs.


I have been able to accomplish embedding of a MetalKit view using NSViewControllerRepresentable protocol just recently and it seemed to work okay (was rendering within the SwiftUI view heirarchy just fine)


import Foundation
import SwiftUI
import AppKit

struct ModelViewController : NSViewControllerRepresentable {
    typealias NSViewControllerType = GameViewController
    
    func makeNSViewController(context: NSViewControllerRepresentableContext) -> GameViewController {
        
        let viewController = GameViewController()
        
        
        return viewController
    }
    
    func updateNSViewController(_ nsViewController: GameViewController, context: NSViewControllerRepresentableContext) {
        // Protocol stub
    }

}

Where GameViewController was the NSViewController.


import Foundation
import AppKit
import MetalKit

class GameViewController : NSViewController {

    var renderer: Renderer!
    var mtkView: MTKView!
    
    override func loadView() {
        mtkView = MTKView()
        
        self.view = mtkView
        
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Select the device to render with.  We choose the default device
        guard let defaultDevice = MTLCreateSystemDefaultDevice() else {
            print("Metal is not supported on this device")
            return
        }
        
        mtkView.device = defaultDevice
        
        guard let newRenderer = Renderer(metalKitView: mtkView) else {
            print("Renderer cannot be initialized")
            return
        }
        
        renderer = newRenderer
        
        renderer.mtkView(mtkView, drawableSizeWillChange: mtkView.drawableSize)
        
        mtkView.delegate = renderer
    }
    
}


Pretty standard nothing special.


Then you can just use the ModelViewController directly in SwiftUI


import SwiftUI

struct ContentView : View {
    var body: some View {
        ModelViewController() //LOOK: My NSViewController wrapper
    }
}


#if DEBUG
struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
#endif


Hope this helps you. As I was also struggling to find any samples related to embedding AppKit related content...

Replies

Hi Dmitry,


For embedding AppKit into SwiftUI the correct way form what I can currently tell is using either NSViewControllerRepresentable or NSViewRepresentable protocols.


The collection view should be built with AppKit as was done before SwiftUI existed. In the case of embedding AppKit content, think of SwiftUI and just a markup language for displaying and organizing your UI instead of storyboards of nibs.


I have been able to accomplish embedding of a MetalKit view using NSViewControllerRepresentable protocol just recently and it seemed to work okay (was rendering within the SwiftUI view heirarchy just fine)


import Foundation
import SwiftUI
import AppKit

struct ModelViewController : NSViewControllerRepresentable {
    typealias NSViewControllerType = GameViewController
    
    func makeNSViewController(context: NSViewControllerRepresentableContext) -> GameViewController {
        
        let viewController = GameViewController()
        
        
        return viewController
    }
    
    func updateNSViewController(_ nsViewController: GameViewController, context: NSViewControllerRepresentableContext) {
        // Protocol stub
    }

}

Where GameViewController was the NSViewController.


import Foundation
import AppKit
import MetalKit

class GameViewController : NSViewController {

    var renderer: Renderer!
    var mtkView: MTKView!
    
    override func loadView() {
        mtkView = MTKView()
        
        self.view = mtkView
        
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Select the device to render with.  We choose the default device
        guard let defaultDevice = MTLCreateSystemDefaultDevice() else {
            print("Metal is not supported on this device")
            return
        }
        
        mtkView.device = defaultDevice
        
        guard let newRenderer = Renderer(metalKitView: mtkView) else {
            print("Renderer cannot be initialized")
            return
        }
        
        renderer = newRenderer
        
        renderer.mtkView(mtkView, drawableSizeWillChange: mtkView.drawableSize)
        
        mtkView.delegate = renderer
    }
    
}


Pretty standard nothing special.


Then you can just use the ModelViewController directly in SwiftUI


import SwiftUI

struct ContentView : View {
    var body: some View {
        ModelViewController() //LOOK: My NSViewController wrapper
    }
}


#if DEBUG
struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
#endif


Hope this helps you. As I was also struggling to find any samples related to embedding AppKit related content...

Thanks a lot, it definitely helped!


Although I realized, that since ForEach component used in a List component is generating views on demand, NSCollectionView is no longer necessary for my purpose (showing lots of views). That's a nice touch.