Home > Articles > Home & Office Computing > Entertainment/Gaming/Gadgets

The iOS 5 Developer's Cookbook: Adding Reflections

  • Print
  • + Share This
Erica Sadun, author of The iOS 5 Developer's Cookbook shows how to use reflections in your iOS applications.
From the author of

Reflections enhance the reality of onscreen objects. They provide a little extra visual spice beyond the views-floating-over-a-backsplash, which prevails as the norm. Reflections have become especially easy to implement thanks to the CAReplicatorLayer class. This class clones a view's layer in real-time and allows you to apply transforms to that replication.

To use reflections in your iOS applications, create a new view class and override the layerClass class method, reporting back that the class to be used for this view is CAReplicatorLayer. This ensures that the view's layer defaults to a replicator.

In the following example, that replicator is given the instanceCount of 2 in the setupReflection method. It duplicates its original view to just a second instance.

Its instanceTransform specifies how the second instance is manipulated and placed on screen: namely flipped, squeezed, and then move vertically below the original view. This creates a small reflection at the foot of the original view.

Due to the replicator, that reflection is completely "live." Any changes to the main view immediately update in the reflection layer. You can best use a view like this by adding subviews, and letting the reflecting view handle all the mirroring for those items. For example, you can add scrolling text views, web views, switches, and so forth. Any changes to the original view are replicated, creating a completely passive reflection system.

Good reflections use a natural attrition as the "reflection" moves further away from the original view. This example code adds an optional gradient overlay (controlled with the class's usesGradientOverlay property, implemented via a CAGradientLayer) that creates a visual drop-off from the bottom of the view.

@interface ReflectingView : UIView
{
    CAGradientLayer *gradient;
}
- (void) setupReflection;
@property (nonatomic, assign) BOOL usesGradientOverlay; @end

@implementation ReflectingView
@synthesize usesGradientOverlay;

// Always use a replicator as the base layer
+ (Class) layerClass
{
    return [CAReplicatorLayer class];
}

// Clean up any existing gradient from parent
- (void) dealloc
{
    [gradient removeFromSuperlayer];
}

// Establish a gradient to shade the reflection
- (void) setupGradient
{
    // Add a new gradient layer to the parent
    UIView *parent = self.superview;
    if (!gradient)
    {
        gradient = [CAGradientLayer layer];
        CGColorRef c1 = [[UIColor blackColor] 
            colorWithAlphaComponent:0.5f].CGColor;
        CGColorRef c2 = [[UIColor blackColor] 
            colorWithAlphaComponent:0.9f].CGColor;
        [gradient setColors:[NSArray arrayWithObjects:
            (__bridge id)c1, (__bridge id)c2, nil]];
        [parent.layer addSublayer:gradient];
    }
    
    // Place the gradient just below the view using the 
    // reflection's geometry
    float desiredGap = 10.0f; // gap between view and its reflection
    CGFloat shrinkFactor = 0.25f; // reflection size
    CGFloat height = self.bounds.size.height;
    CGFloat width = self.bounds.size.width;
    CGFloat y = self.frame.origin.y;
    
    [gradient setAnchorPoint:CGPointMake(0.0f,0.0f)];
    [gradient setFrame:CGRectMake(0.0f, y + height + desiredGap, 
        width, height * shrinkFactor)];
    [gradient removeAllAnimations];
    
    [gradient setAnchorPoint:CGPointMake(0.0f,0.0f)];
    [gradient setFrame:CGRectMake(0.0f, y + height + desiredGap, 
        maxDimension, height * shrinkFactor)];
    [gradient removeAllAnimations];
}

// Setup the geometry for the reflected item
- (void) setupReflection
{
    CGFloat height = self.bounds.size.height;
    CGFloat shrinkFactor = 0.25f;
    
    CATransform3D t = CATransform3DMakeScale(1.0, -shrinkFactor, 1.0);
    
    // scaling centers the shadow in the view. translate in shrunken terms
    float offsetFromBottom = height * ((1.0f - shrinkFactor) / 2.0f);
    float inverse = 1.0 / shrinkFactor;
    float desiredGap = 10.0f;
    t = CATransform3DTranslate(t, 0.0, -offsetFromBottom * inverse 
        - height - inverse * desiredGap, 0.0f);
    
    CAReplicatorLayer *replicatorLayer = (CAReplicatorLayer*)self.layer;
    replicatorLayer.instanceTransform = t;
    replicatorLayer.instanceCount = 2;
    
    // Gradient use must be explicitely set
    if (usesGradientOverlay)
        [self setupGradient];
    else
    {
        // Darken the reflection when not using a gradient
        replicatorLayer.instanceRedOffset = -0.75;
        replicatorLayer.instanceGreenOffset = -0.75;
        replicatorLayer.instanceBlueOffset = -0.75;
    }
}
@end
  • + Share This
  • 🔖 Save To Your Account