UIOverlayView – painting on top of your subviews

My first baby steps are becoming toddler steps and the first classes of a generic library are forming. I have found that making my infamous calendar picker part of my technology shake-down projects, usually forces me to code parts that are very educational. So by now there is a Swing version (JCalendarPicker), a JavaFX 1.3 version (FXCalendarPicker, part of JFXtras) and soon an iOS version (KPCalendarPicker). The Swing version actually was very straight forward. The JavaFX version required me to build a spinner component for the year and month selection. The iOS had the same requirements as JFX, because the default UIPickerView takes up way too much space on the screen to be used as a subcomponent for CalendarPicker. So I developed KPMiniPickerView which basically is UIPickerView on the scale of a text field.

KPMiniPicker has the looks of a text field, but I needed some way to visually make it different, so users know that it is not a text field. I decided to do this by painting blue arrows on top of the childeren, showing the direction in which the minipicker could be slided.

This required me to paint on top of the childeren and some trail-and-error learned me that the regular drawRect: method cannot do this; it will paint beneath them as a background.

So introducing the first toddle steps class: KPOverlayView. A simple class doing nothing more than relaying a drawRect: method, so that there is no need for a separate class only to paint the overlay.

KPOverlayView.h:

#import <UIKit/UIKit.h>
@interface KPOverlayView : UIView {
    UIView* _view;
    SEL _selector;
}
+ (id)overlayWithView:(UIView*)view drawMethod:(SEL)selector;
- (id)initWithView:(UIView*)view drawMethod:(SEL)selector;
@end

KPOverlayView.m:

//
//  KPOverlayView.m
//
//  Created by TBEE on 2011.02.11.
//  Copyright 2011 KnowlegdePlaza. All rights reserved.
//
// This is a small utility class that allows a control to paint on top of its childeren.
// Normally drawRect is executed before the childeren are painted, so anything done is there is placed on the background.
// Sometimes the parent would like to paint over it's childeren, this class will facilitate that.
//
// Usage is quite simple:
//		_kpOverlayView = [[KPOverlayView alloc] initWithView:self drawMethod:@selector(drawOverlay:)]; // to be released in dealloc
//		[self addSubview: _kpOverlayView];
//
// Or:
//		_kpOverlayView = [KPOverlayView overlayWithView:self drawMethod:@selector(drawOverlay:)]; // released when the view is removed from the subviews
//
// drawOverlay: has exactly the same signature as drawRect:
//		- (void)drawOverlay:(CGRect)rect
//
// Adding OverlayView usually is done last in the initWithFrame of the parent, so that the overlay is the last child and therefore painted on top.
//

#import "KPOverlayView.h"

@interface KPOverlayView (hidden)
- (void)overlay:(id)listener drawMethod:(SEL)selector;
@end

@implementation KPOverlayView

// allocs overlay and adds it to the view
+ (id)overlayWithView:(UIView*)view drawMethod:(SEL)selector {

	// alloc me
	id lOverlay = [[KPOverlayView alloc] initWithView:view drawMethod:selector];

	// add to view
	[view addSubview:lOverlay];

	// the overlay is added to the view, which does a retain, so we can release
	[lOverlay autorelease];

	// done
	return lOverlay;
}

//
- (id)initWithView:(UIView*)view drawMethod:(SEL)selector {

    self = [super initWithFrame:view.bounds];
    if (self) {
        // Initialization code.
		self.userInteractionEnabled = NO;
		self.backgroundColor = [UIColor clearColor];

		// remember
		[self overlay:view drawMethod:selector];

		// make sure we scale
		self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    }
    return self;
}

- (void)dealloc {
    [super dealloc];
}

// handle rotations, etc
- (void)layoutSubviews {
	self.frame = _view.bounds;
}

// forward the drawRect to the actual draw method
- (void)drawRect:(CGRect)rect {
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature: [_view methodSignatureForSelector:_selector]];
    [invocation setTarget:_view];
    [invocation setSelector:_selector];
    // Note: Indexes 0 and 1 correspond to the implicit arguments self and _cmd, which are set using setTarget and setSelector.
    [invocation setArgument:"rect atIndex:2];
    [invocation invoke];
}

// register a draw method as the overlay painted
- (void)overlay:(id)listener drawMethod:(SEL)selector {

	// remember
	id lOldListener = _view;
	_view = [listener retain];
	if (lOldListener != nil) [lOldListener release];

	// remember
	_selector = selector;
}

@end

Usage is extremely simple. First you add it as the last subview and then you paint:

- (id)initWithFrame:(CGRect)frame {
	...
	// add an overlay
	_kpOverlayView = [KPOverlayView overlayWithView:self drawMethod:@selector(drawOverlay:)]; // released when the view is removed from the subviews
}

- (void)drawOverlay:(CGRect)rect {
	// get context
	CGContextRef ctx = UIGraphicsGetCurrentContext();
	...
}

More as the other classes reach maturity.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.