objective-cmacoscore-graphicsnsviewdrawrect

Draw image (as an overlay) on top of drawRect NSView (macOS & ObjC)


I have a drawing that I'm happy with on a drawRect, I'm now trying to overlay an image from a specific path, I'd like it to go on top of the current drawing as an overlay. Does anyone know what the best way to do this is, I'd like to be able to set the position and size of this image.

Can this be done with core graphics or should I use an NSImage to do this - would appreciate anyone who can point me in the right direction :)


Solution

  • Your NSView drawRect: method can be called before or after a subclassed implementation of drawRect:, which allows you to decide if additional drawing code executes earlier or later and therefore will end up above or below the drawing result of super.

    // in subclass
    - (void)drawRect:(CGRect)dirtyrect {
        [super drawRect:dirtyrect];  //to draw super below
        // ask for flip state first
        CGFloat yFlip = self.flipped ? -1.0 : 1.0;
        // your additional
        // drawing code here..
        // and do something with yFlip
        // ...
        //[super drawRect:dirtyrect]; //to draw super on top
    }
    

    as the flip state of the view or layer is likely respected when drawing you have to examine the flip state before making your own draw calls. To do this ask for self.flipped or super.flipped to adapt to the views flip state like seen above.

    but there are a lot different ways to mix flipped content with non-flipped.
    The following code respects self.flipped state when creating an NSImage by locking focus with lockFocusFlipped: method instead of just lockFocus. Also showing other APIs, ps: you don't have to implement imageRepresentation and the following contains more or less pseudo code

    - (NSImage *)imageRepresentation
    {
        //BOOL wasHidden = self.isHidden;
        //CGFloat wantedLayer = self.wantsLayer;
        //self.hidden = NO;
        //self.wantsLayer = YES;
        
        NSImage *image = [[NSImage alloc] initWithSize:self.bounds.size];
        
        // to flip an image according to the views flip state
        //[image lockFocus]; 
        [image lockFocusFlipped:self.flipped]; 
    
        CGContextRef ctx = [NSGraphicsContext currentContext].CGContext;
    
        // to flip with CoreGraphics API
        //CGContextScaleCTM(ctx, 1.0, -1.0); // <-- see -1.0?
        //CGContextTranslateCTM(ctx, 1.0, 1.0);
        
        // or Affine Transform API, pseudo code
        //NSAffineTransform *trans = [NSAffineTransform transform];
        //[trans translateXBy:0 yBy:existingSize.height];
        //[trans scaleXBy:1 yBy:-1];
        //[trans concat];
        //[image drawAtPoint:NSZeroPoint fromRect:NSMakeRect(0, 0, newSize.width, newSize.height) operation:NSCompositingOperationSourceOver fraction:1.0];
    
        //[self.layer renderInContext:ctx]; // <-- CALayer backed view drawing
    
        [self drawRect:self.bounds.size]; // <-- classic View drawing
    
        [image unlockFocus];
    
        //self.wantsLayer = wantedLayer;
        //self.hidden = wasHidden;
        return image;
    }