jns_ai_unr
jns_ai_unr

Reputation: 903

How to convert a Swift UI View to a NSImage

I am trying to use a SwiftUI View as a NSCursor on MacOS.

Using SwiftUI I am constructing a view that I then convert to an NSView using NSHostingView. Now I am trying to convert that to a NSImage via a NSBitmapImageRep. For some reason I always get an empty png when I inspect the variables while setting breakpoints.

(For now the setup of the cursor is done in the AppDelegate because I am currently just trying to get this to work.)

class AppDelegate: NSObject, NSApplicationDelegate {

    var window: NSWindow!


    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Create the SwiftUI view that provides the window contents.
        let contentView = ContentView()
        let contentRect = NSRect(x: 0, y: 0, width: 480, height: 480)

        // Create the window and set the content view. 
        window = NSWindow(
            contentRect: contentRect,
            styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
            backing: .buffered, defer: false)
        window.center()
        window.setFrameAutosaveName("Main Window")
        window.contentView = NSHostingView(rootView: contentView)
        window.makeKeyAndOrderFront(nil)

        let myNSView = NSHostingView(rootView: ContentView()).bitmapImageRepForCachingDisplay(in: contentRect)!

        NSHostingView(rootView: ContentView()).cacheDisplay(in: contentRect, to: myNSView)

        let myNSImage = NSImage(size: myNSView.size)
        myNSImage.addRepresentation(myNSView)

        self.window.disableCursorRects()

//        var myName = NSImage.Name("ColorRing")
//        var myCursorImg = NSImage(named: myName)!
//
//        var myCursor = NSCursor(image: myCursorImg, hotSpot: NSPoint(x: 10, y: 10))


        var myCursor = NSCursor(image: myNSImage, hotSpot: NSPoint(x: 0, y: 0))
        myCursor.set()
    }

    func applicationWillTerminate(_ aNotification: Notification) {
        // Insert code here to tear down your application
    }


}

When executing the part that is commented out, I get the mouse cursor from an external png file.

When executing the code shown above I always get an empty mouse cursor with the size 480x480. In debug mode I can see, that myNSView is an empty png. I am pretty sure I misunderstand the documentation and therefore misuse cacheDisplay(in:to:) and bitmapImageRepForCachingDisplay(in:). The documentation tells me to use the NSBitmapImageRep from bitmapImageRepForCachingDisplay(in:) in cacheDisplay(in:to:). But for some reason I simply can not get this to work.

Does anyone have an idea what I am doing wrong?

Upvotes: 1

Views: 1227

Answers (1)

jns_ai_unr
jns_ai_unr

Reputation: 903

To answer my own question:

It seems like the view that one wants to convert to an NSImage needs to be layed out inside of a window. Otherwise it probably does not know how large the view needs to be inside the rect.

The following code works for me:

func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Create the SwiftUI view that provides the window contents.
        let contentView = ContentView()
        let contentRect = NSRect(x: 0, y: 0, width: 480, height: 480)

        // Create the window and set the content view. 
        window = NSWindow(
            contentRect: contentRect,
            styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
            backing: .buffered, defer: false)
        window.center()
        window.setFrameAutosaveName("Main Window")
        window.contentView = NSHostingView(rootView: contentView)
        window.makeKeyAndOrderFront(nil)

        // CHANGED ----------
        let newWindow = NSWindow(
            contentRect: contentRect,
            styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
            backing: .buffered, defer: false)

        newWindow.contentView = NSHostingView(rootView: contentView)

        let myNSBitMapRep = newWindow.contentView!.bitmapImageRepForCachingDisplay(in: contentRect)!

        newWindow.contentView!.cacheDisplay(in: contentRect, to: myNSBitMapRep)
        // CHANGED ----------

        let myNSImage = NSImage(size: myNSBitMapRep.size)
        myNSImage.addRepresentation(myNSBitMapRep)

        self.window.disableCursorRects()

        var myCursor = NSCursor(image: myNSImage, hotSpot: NSPoint(x: 0, y: 0))
        myCursor.set()
}

Only lines between the two //CHANGED ----- comments have been changed for a working solution. I am simply embedding the view inside a window before applying the same methods as before. Hope this helps some people in the future.

Upvotes: 1

Related Questions