How do I take a "screenshot" of an NSView?
Asked Answered
I

4

18

I need to take the contents of an NSView and put them in an NSImage, for an experimental project. Is this possible? I did some Googling, tried two methods that I found - but they didn't really work. Any suggestions?

Invaginate answered 14/7, 2010 at 23:11 Comment(1)
Which methods didn't work and why?Sibbie
N
23
[[NSImage alloc] initWithData:[view dataWithPDFInsideRect:[view bounds]]];
Newcomen answered 15/7, 2010 at 1:2 Comment(3)
+1 It's worth noting that, although this is the right way in general, there are some cases where it will not work, eg views like NSOpenGLView that have their own OpenGL rendering context. In that case you need to get the pixel data directly and create a bitmap rep from it, which is a bit less neat.Windbound
Obs: shadows and cornerRadius will be ignoredHickson
If view is layer backed (wantsLayer = YES) this solution will ignore some layer properties, like backgroundColor, cornerRadius etc. I recommend code from another answer, using cacheDisplayInRect function, it should work in all cases.Quiteria
L
34

From WWDC 2012 Session 245 (translated to Swift):

let viewToCapture = self.window!.contentView!
let rep = viewToCapture.bitmapImageRepForCachingDisplay(in: viewToCapture.bounds)!
viewToCapture.cacheDisplay(in: viewToCapture.bounds, to: rep)

let img = NSImage(size: viewToCapture.bounds.size)
img.addRepresentation(rep)
Lavoie answered 8/6, 2016 at 12:49 Comment(6)
swift 3 let rep = viewToCapture.bitmapImageRepForCachingDisplay(in: viewToCapture.bounds)! viewToCapture.cacheDisplay(in: viewToCapture.bounds, to: rep) let img = NSImage(size: viewToCapture.bounds.size) img.addRepresentation(rep)Fogel
Note: doesn't always work correctly. I'm having issued with a label that has a rotated coordinate system.Thymus
This was the best solution for me, unlike the pdf version, this also accounts for shadows and rounded corners. Unlike the window cap version, this only captures the current view. Obj C code: - (NSImage *)screenCap { NSBitmapImageRep *bitmap = [self bitmapImageRepForCachingDisplayInRect:self.bounds]; [self cacheDisplayInRect:self.bounds toBitmapImageRep:bitmap]; NSImage *result = [[NSImage alloc] initWithSize:self.bounds.size]; [result addRepresentation:bitmap]; return result; } Hickson
I've been using this successfully in 10.14 & 10.15 (codrut's Obj C version above) but for some reason I get no image on 10.13.Clarisclarisa
I had the problem that subviews with visible=NOwere still showing up in the PDF version. Using this version solved it. Still looking for a solution, though, which will also include an AVPlayerView's current frame. Suggestions welcome :)Lomax
I'm using an NSBox with .underPageBackgroundColor as the fillColor and this doesn't capture the color properlyKepi
N
23
[[NSImage alloc] initWithData:[view dataWithPDFInsideRect:[view bounds]]];
Newcomen answered 15/7, 2010 at 1:2 Comment(3)
+1 It's worth noting that, although this is the right way in general, there are some cases where it will not work, eg views like NSOpenGLView that have their own OpenGL rendering context. In that case you need to get the pixel data directly and create a bitmap rep from it, which is a bit less neat.Windbound
Obs: shadows and cornerRadius will be ignoredHickson
If view is layer backed (wantsLayer = YES) this solution will ignore some layer properties, like backgroundColor, cornerRadius etc. I recommend code from another answer, using cacheDisplayInRect function, it should work in all cases.Quiteria
R
6
let dataOfView = view.dataWithPDFInsideRect(view.bounds)
let imageOfView = NSImage(data: dataOfView)
Renell answered 10/5, 2015 at 2:47 Comment(0)
K
-1

NSView.bitmapImageRepForCachingDisplay() (mentioned in this answer) doesn't render the colors correctly on some views.

CGWindowListCreateImage() works perfectly for me.

Here's my implementation:

extension NSView {
    @objc func takeScreenshot() -> NSImage? {
        
        let screenRect = self.rectInQuartzScreenCoordinates()
        guard let window = self.window else { 
            assert(false); return nil 
        }
        let windowID = CGWindowID(window.windowNumber)
        guard let screenshot = CGWindowListCreateImage(screenRect, .optionIncludingWindow, windowID, []) else { 
            assert(false); return nil 
        }
        
        return NSImage(cgImage: screenshot, size: self.frame.size)
    }
}

This code uses a method NSView.rectInQuartzScreenCoordinates(). To implement it you'll first have to convert the bounds of your view to screenCoordinates using NSView and NSWindow methods and then you need to flip the coordinates like this.

Kepi answered 19/11, 2022 at 17:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.