I found that writing the drawing code myself was the best way to:
- deal with potential transparency issues (some of the other options do add a white background to the whole image)
- performance was much better
The code below is not perfect, because it does not deal with scaling issues when going from bounds to frames, but it does take into account the isFlipped state, and works very well for what I used it for. Note that it only draws the subviews (and the subsubviews,... recursively), but getting it to also draw itself is very easy, just add a [self drawRect:[self bounds]]
in the implementation of imageWithSubviews
.
- (void)drawSubviews
{
BOOL flipped = [self isFlipped];
for ( NSView *subview in [self subviews] ) {
// changes the coordinate system so that the local coordinates of the subview (bounds) become the coordinates of the superview (frame)
// the transform assumes bounds and frame have the same size, and bounds origin is (0,0)
// handling of 'isFlipped' also probably unreliable
NSAffineTransform *transform = [NSAffineTransform transform];
if ( flipped ) {
[transform translateXBy:subview.frame.origin.x yBy:NSMaxY(subview.frame)];
[transform scaleXBy:+1.0 yBy:-1.0];
} else
[transform translateXBy:subview.frame.origin.x yBy:subview.frame.origin.y];
[transform concat];
// recursively draw the subview and sub-subviews
[subview drawRect:[subview bounds]];
[subview drawSubviews];
// reset the transform to get back a clean graphic contexts for the rest of the drawing
[transform invert];
[transform concat];
}
}
- (NSImage *)imageWithSubviews
{
NSImage *image = [[[NSImage alloc] initWithSize:[self bounds].size] autorelease];
[image lockFocus];
// it seems NSImage cannot use flipped coordinates the way NSView does (the method 'setFlipped:' does not seem to help)
// Use instead an NSAffineTransform
if ( [self isFlipped] ) {
NSAffineTransform *transform = [NSAffineTransform transform];
[transform translateXBy:0 yBy:NSMaxY(self.bounds)];
[transform scaleXBy:+1.0 yBy:-1.0];
[transform concat];
}
[self drawSubviews];
[image unlockFocus];
return image;
}