Begin main content

Using blocks to simplify custom view drawing

Commonly when you are drawing a custom Cocoa view, you will be creating paths, filling and stroking them etc. You might even be drawing a similar shape a number of times. A naive approach to drawing five round rects with shadows might look like this:

#defineDDOFFSET 10
#defineDDWIDTH 50

- (void)drawRect:(NSRect)dirtyRect {

    [NSGraphicsContext saveGraphicsState];

    for (inti=0 ; i < 5 ; i++)
    {
        // offset and put onto pixel boundary
CGFloattop = [self frame].size.height - 0.5 - DDOFFSET;
        CGFloatleft = 0.5 + (DDWIDTH * i) + (DDOFFSET * i) + DDOFFSET;
                
        NSRectborderRect = NSMakeRect(left,
                                       DDOFFSET,
                                       DDWIDTH,
                                       top - DDOFFSET);
        
        NSBezierPath *borderPath = [NSBezierPath bezierPathWithRoundedRect:borderRect
                                                                   xRadius:5
                                                                   yRadius:5];
        
        NSColor *borderColor = [NSColor grayColor];
        
        NSShadow *borderShadow = [[NSShadow alloc] init];
        [borderShadow setShadowOffset:NSMakeSize(2.0, -2.0)];
        [borderShadow setShadowBlurRadius:2.0];
        [borderShadow setShadowColor:[[NSColor blackColor] colorWithAlphaComponent:0.3]];
        
        NSColor *fillColor = [NSColor whiteColor];
        
        // do the drawing
        [borderColor set];
        [borderShadow set];
        [borderPath stroke];
        [fillColor set];
        [borderPath fill];
    }
    
    [NSGraphicsContext restoreGraphicsState];
}

(Note these examples are all using garbage collection).

Hopefully, it should occur to you that, amongst other issues, this is needlessly inefficient because a bunch of objects are being created and destroyed each time the view is dirtied. So we want to move the creation of objects into a separate method that is called when the view is created, store the paths in an array and the drawRect would then just have to set the colours, stroke the paths, etc. Something like this:

@interfaceDrawDemo2View : NSView {
    NSColor *borderColor;
    NSShadow *borderShadow;
    NSColor *fillColor;
    NSMutableArray *borderPaths;
}

@end
@implementationDrawDemo2View#defineDDOFFSET 10
#defineDDWIDTH 50

- (id)initWithFrame:(NSRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        borderColor = [NSColor grayColor];

        borderShadow = [[NSShadow alloc] init];
        [borderShadow setShadowOffset:NSMakeSize(2.0, -2.0)];
        [borderShadow setShadowBlurRadius:2.0];
        [borderShadow setShadowColor:[[NSColor blackColor] colorWithAlphaComponent:0.3]];
        
        fillColor = [NSColor whiteColor];
        
        borderPaths = [[NSMutableArray alloc] initWithCapacity:5];
        
        for (inti=0 ; i < 5 ; i++)
        {
            // offset and put onto pixel boundary
CGFloattop = [self frame].size.height - 0.5 - DDOFFSET;
            CGFloatleft = 0.5 + (DDWIDTH * i) + (DDOFFSET * i) + DDOFFSET;
            
            NSRectborderRect = NSMakeRect(left,
                                           DDOFFSET,
                                           DDWIDTH,
                                           top - DDOFFSET);
            
            NSBezierPath *borderPath = [NSBezierPath bezierPathWithRoundedRect:borderRect
                                                                       xRadius:5
                                                                       yRadius:5];
            
            [borderPaths addObject:borderPath];
        }
    }

    returnself;
}

- (void)drawRect:(NSRect)dirtyRect {

    [NSGraphicsContext saveGraphicsState];

    for (NSBezierPath *pathin borderPaths) {
                
        // do the drawing
        [borderColor set];
        [borderShadow set];
        [path stroke];
        [fillColor set];
        [path fill];
    }
    
    [NSGraphicsContext restoreGraphicsState];
}

@end

That's mildly painful, and disconnects the meaning of the above code somewhat, but it's not too bad. What, though, if there are other shapes that require different handling - well they will need their own array and drawing code. Then we want to subclass the view and the subclass wants to draw circles, etc. etc. Your view will become messy and very specific. Also in a real example I'd probably need more save/restore graphics states, but here the shadow on the fill isn't important.

Thankfully we can use blocks, which are what some other languages call closures, to keep the efficiency of separating the execution time of the drawing code while keeping it all together in a very general way. We end up with something almost identical to the first naive implementation, but the drawing section at the end goes into a block in an array, and the drawRect simply executes all those blocks:

@interfaceDrawDemoBlocksView : NSView {
    NSMutableArray *drawingBlocks;
}

- (void)addRoundRect: (int)num;

@end
@implementationDrawDemoBlocksView#defineDDOFFSET 10
#defineDDWIDTH 50

- (id)initWithFrame:(NSRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        drawingBlocks = [[NSMutableArray alloc] initWithCapacity:5];

        for (inti=0 ; i < 5 ; i++)
            [self addRoundRect:i];
    }
    returnself;
}

- (void)drawRect:(NSRect)dirtyRect {

    for (void *(^block)(void) indrawingBlocks)
    {
        [NSGraphicsContext saveGraphicsState];
        block();
        [NSGraphicsContext restoreGraphicsState];
    }
}

- (void)addRoundRect: (int) num
{
    // offset and put onto pixel boundary
CGFloattop = [self frame].size.height - 0.5 - DDOFFSET;
    CGFloatleft = 0.5 + (DDWIDTH * num) + (DDOFFSET * num) + DDOFFSET;
    
    NSRectborderRect = NSMakeRect(left,
                                   DDOFFSET,
                                   DDWIDTH,
                                   top - DDOFFSET);
    
    NSBezierPath *borderPath = [NSBezierPath bezierPathWithRoundedRect:borderRect
                                                               xRadius:5
                                                               yRadius:5];
    
    NSColor *borderColor = [NSColor grayColor];
    
    NSShadow *borderShadow = [[NSShadow alloc] init];
    [borderShadow setShadowOffset:NSMakeSize(2.0, -2.0)];
    [borderShadow setShadowBlurRadius:2.0];
    [borderShadow setShadowColor:[[NSColor blackColor] colorWithAlphaComponent:0.3]];
    
    NSColor *fillColor = [NSColor whiteColor];
    
    [drawingBlocks addObject:[^{

        // do the drawing
        [borderColor set];
        [borderShadow set];
        [borderPath stroke];
        [fillColor set];
        [borderPath fill];
        
    } copy]];
}

@end

You can also see how this would work really well if we subclassed this view - all subclasses could simply push a block onto the array to request some work to be done during the drawRect phase.

You'll note how the block itself isn't added to the array, but a copy of it. That's because by default the storage for the context embodied in the block is only valid for the scope in which it is created. If we want to execute the block after the scope is gone we need to copy it.

If you found this useful you might also like Drew McCormack's excellent article 10 Uses for Blocks in C/Objective-C.

11:36 PM, 29 May 2010 by Mark Aufflick Permalink | Comments (0)

Afternoon Fog

Afternoon fog

There was an unusual afternoon fog in Sydney today

Series home: http://mark.aufflick.com/a-day-in-sydney

Taken with Canon EOS 400D DIGITAL and Sigma HSM 30mm f1.4 on 2010:05:25 16:27:40

  • Exposure: 1/160
  • Aperture: f2.5
  • ISO speed: 200
  • Exposure program: Aperture-priority AE
  • Exposure compensation: +1
  • Flash: Off, Did not fire
  • File: IMG_3716.jpg

08:29 AM, 25 May 2010 by Mark Aufflick Permalink | Comments (0)

Purple Bridge

Purple Bridge

Daily Shoot #ds190 'Today's color is purple.'

Taken as part of The Daily Shoot project. View all Mark's Daily Shoot photos.

Taken with Canon EOS 400D DIGITAL and Sigma HSM 30mm f1.4 on 2010:05:25 13:02:35

  • Exposure: 1/160
  • Aperture: f2.5
  • ISO speed: 200
  • Exposure program: Aperture-priority AE
  • Exposure compensation: -1
  • Flash: Off, Did not fire
  • File: IMG_3711.jpg

08:20 AM, 25 May 2010 by Mark Aufflick Permalink | Comments (0)

Stairway to Law

Stairway to Law

The new Sydney University Law Faculty building offers many interesting views. This one has a slight Jeffrey Smartt aspect (I like to think!)

Series home: http://mark.aufflick.com/a-day-in-sydney

Taken with Canon EOS 400D DIGITAL and Sigma HSM 30mm f1.4 on 2010:05:24 17:23:15

  • Exposure: 1/20
  • Aperture: f1.4
  • ISO speed: 400
  • Exposure program: Aperture-priority AE
  • Exposure compensation: -1
  • Flash: Off, Did not fire
  • File: IMG_3706.jpg

09:00 AM, 24 May 2010 by Mark Aufflick Permalink | Comments (0)

Members Only

Members Only

Can't be too many members - look how small the club house is!

Series home: http://mark.aufflick.com/a-day-in-sydney

Taken with Canon EOS 400D DIGITAL and Sigma HSM 30mm f1.4 on 2010:05:19 16:14:33

  • Exposure: 1/250
  • Aperture: f5.6
  • ISO speed: 100
  • Exposure program: Aperture-priority AE
  • Exposure compensation: 0
  • Flash: Off, Did not fire
  • File: IMG_3689.jpg

07:10 AM, 20 May 2010 by Mark Aufflick Permalink | Comments (0)

Roller Skating

Roller Skating

I was sad when they closed down my childhood roller skating rink. I'm not sure this is the same.

Series home: http://mark.aufflick.com/a-day-in-sydney

Taken with Canon EOS 400D DIGITAL and Sigma HSM 30mm f1.4 on 2010:05:19 16:07:29

  • Exposure: 1/15
  • Aperture: f5.6
  • ISO speed: 100
  • Exposure program: Aperture-priority AE
  • Exposure compensation: 0
  • Flash: Off, Did not fire
  • File: IMG_3688.jpg

04:45 AM, 19 May 2010 by Mark Aufflick Permalink | Comments (0)

Walkway to St James

Walkway to St James

I was at St James station early enough to find it nearly empty. St James is one of the (very few) genuinely attractive stations in Sydney. Another day I'll take some shots of the cavernous interior of the cut-and-cover underground station.

Series home: http://mark.aufflick.com/a-day-in-sydney

Taken with Canon EOS 400D DIGITAL and EF24-85mm f/3.5-4.5 USM on 2010:05:18 07:14:36

  • Exposure: 1/100
  • Aperture: f3.5
  • ISO speed: 400
  • Exposure program: Aperture-priority AE
  • Exposure compensation: -1
  • Flash: Off, Did not fire
  • File: IMG_3656.jpg

07:52 AM, 18 May 2010 by Mark Aufflick Permalink | Comments (0)

Gloomy

Gloomy

Daily Shoot #ds183 'Black can be dramatic. Go for as much black in a photo as you can with lots of oomph, character, and depth.'

Taken as part of The Daily Shoot project. View all Mark's Daily Shoot photos.

Taken with Canon EOS 400D DIGITAL and EF24-85mm f/3.5-4.5 USM on 2010:05:18 16:47:45

  • Exposure: 1/6
  • Aperture: f4.5
  • ISO speed: 400
  • Exposure program: Aperture-priority AE
  • Exposure compensation: -2
  • Flash: Off, Did not fire
  • File: IMG_3667.jpg

05:10 AM, 18 May 2010 by Mark Aufflick Permalink | Comments (0)

Chair Shadow

Chair Shadow

Daily Shoot #ds180 'Find an interesting shadow--any shadow--and make a photograph of it. Consider your framing as you shoot.'

Taken as part of The Daily Shoot project. View all Mark's Daily Shoot photos.

Taken with Canon EOS 400D DIGITAL and Sigma HSM 30mm f1.4 on 2010:05:15 15:02:50

  • Exposure: 1/500
  • Aperture: f5.6
  • ISO speed: 100
  • Exposure program: Aperture-priority AE
  • Exposure compensation: 0
  • Flash: Off, Did not fire
  • File: IMG_3645.jpg

01:36 AM, 15 May 2010 by Mark Aufflick Permalink | Comments (2)

Coffee Times

Coffee Times

Daily Shoot #ds178 'Pick an everyday subject and get as close as you can to it.' - This is my daily bootup - a one cup stove top espresso maker.

Taken as part of The Daily Shoot project. View all Mark's Daily Shoot photos.

Taken with Canon EOS 400D DIGITAL and EF24-85mm f/3.5-4.5 USM on 2010:05:13 13:57:10

  • Exposure: 1/30
  • Aperture: f4.5
  • ISO speed: 100
  • Exposure program: Aperture-priority AE
  • Exposure compensation: 0
  • Flash: Off, Did not fire
  • File: IMG_3594.jpg

12:44 AM, 13 May 2010 by Mark Aufflick Permalink | Comments (0)

Rusted Poles

Rusted Poles

I've been sick for a few days, hence the pause in photographs. This is another one from the archives, taken at Dee Why beach in Sydney's North. It's a mounted transparency so I can't see what type of film it is.

Series home: http://mark.aufflick.com/a-day-in-sydney

Taken with Canon EOS 30 Film and EF24-85mm f/3.5-4.5 USM

  • Transparency scanned with: Nikon SUPER COOLSCAN 5000 ED
  • File: rusted poles.jpg

08:54 PM, 12 May 2010 by Mark Aufflick Permalink | Comments (0)

Vines on the Rocks

Vines on the Rocks

I found this vine covered facade in one of the anonymous lanes of The Rocks--the oldest part of Sydney

Series home: http://mark.aufflick.com/a-day-in-sydney

Taken with Canon EOS 400D DIGITAL and EF24-85mm f/3.5-4.5 USM on 2010:05:07 14:17:01

  • Exposure: 1/50
  • Aperture: f3.5
  • ISO speed: 100
  • Exposure program: Aperture-priority AE
  • Exposure compensation: -1
  • Flash: Off, Did not fire
  • File: IMG_3540.jpg

08:22 AM, 07 May 2010 by Mark Aufflick Permalink | Comments (0)

View from Macquarie Lighthouse

View from Macquarie Lighthouse

Today I had no time for photographing so this is one from the archives. To be honest I can't remember when it was taken, but I do know it was taken with my EOS 30 with EF24-85mm f/3.5-4.5 USM lens using a 400 ISO Kodak C-31 process black and white film.

Series home: http://mark.aufflick.com/a-day-in-sydney

09:57 AM, 06 May 2010 by Mark Aufflick Permalink | Comments (0)

Clouds over the harbour

Clouds over the harbour

The weather was pretty changeable today, but even so the Sydney Harbour manages to look beautiful.

Series home: http://mark.aufflick.com/a-day-in-sydney

Taken with Canon EOS 400D DIGITAL and EF24-85mm f/3.5-4.5 USM on 2010:05:05 10:49:53

  • Exposure: 1/250
  • Aperture: f5.6
  • ISO speed: 100
  • Exposure program: Aperture-priority AE
  • Exposure compensation: -1/3
  • Flash: Off, Did not fire
  • File: IMG_3503.jpg

07:55 AM, 05 May 2010 by Mark Aufflick Permalink | Comments (0)

The Sacrifice

The Sacrifice

Rayner Hoff's amazing sculpture in the Sydney ANZAC War Memorial

The central motif of the design is ‘The Sacrifice’. It comprises a bronze group of sculptures depicting the recumbent figure of a young warrior who has made the supreme sacrifice; his naked body lies upon a shield which is supported by three womenfolk - his best loved Mother, Wife and Sister and in the arms of one is a child, the future generations for whom the sacrifice has been made.
http://www.anzacday.org.au/education/tff/memorials/nsw.html

Series home: http://mark.aufflick.com/a-day-in-sydney

Taken with Canon EOS 400D DIGITAL and Sigma HSM 30mm f1.4 on 2010:05:04 16:42:02

  • Exposure: 1/25
  • Aperture: f1.4
  • ISO speed: 200
  • Exposure program: Aperture-priority AE
  • Exposure compensation: 0
  • Flash: Off, Did not fire
  • File: IMG_3490.jpg

08:19 AM, 04 May 2010 by Mark Aufflick Permalink | Comments (0)

Alone on Eastern Avenue

Alone on Eastern Avenue

A lone student at night, walking down Eastern Avenue of Sydney University, with the beautiful Anderson Stuart building as the backdrop

Series home: http://mark.aufflick.com/a-day-in-sydney

Taken with Canon EOS 400D DIGITAL and Sigma HSM 30mm f1.4 on 2010:05:03 17:47:55

  • Exposure: 1/13
  • Aperture: f2.2
  • ISO speed: 400
  • Exposure program: Aperture-priority AE
  • Exposure compensation: 0
  • Flash: Off, Did not fire
  • File: IMG_3466.jpg

08:40 AM, 03 May 2010 by Mark Aufflick Permalink | Comments (0)

XML

Blog Categories

software (41)
..cocoa (23)
  ..heads up 'tunes (5)
..ruby (6)
..lisp (4)
..perl (4)
..openacs (1)
mac (21)
embedded (2)
..microprocessor (2)
  ..avr (1)
electronics (3)
design (1)
photography (26)
..black and white (6)
..A day in Sydney (18)
..The Daily Shoot (6)
food (2)
Book Review (2)

Notifications

Icon of envelope Request notifications

Syndication Feed

XML

Recent Comments

  1. Mark Aufflick: Re: the go/Inbox go/Sent buttons
  2. Unregistered Visitor: How do make a button to jump to folder
  3. Unregistered Visitor: Note I've updated the gist
  4. Unregistered Visitor: umbrello is now an available port on macPorts
  5. Unregistered Visitor: Updated version on Github
  6. Unregistered Visitor: Modification request.
  7. Unregistered Visitor: Accents and labels with spaces
  8. Unregistered Visitor: Mel Kaye - additional info
  9. Unregistered Visitor: mmh
  10. Mark Aufflick: Thank you