How do I create a growing iOS Button? - ios

My friend gave me the following designs for iOS buttons, and I'm not sure the best way to implement this.
I need to make the reusable button shown below (in Objective-C).
I've tried:
Subclassing the button, but read that I shouldn't do that
Animating the border (while subclassed), but the border only goes inwards, so it seems like I need to animate the frame too
So how do I approach this? I'm assuming making a CustomButtonView class which has a button (composition) as well as an inner and outer circle view? How would I then animate that to grow? Would I have to animate the frame change too, or could I use insets?
What is the simplest code to make this work? Thanks!

Here Is the approach I took to create this:
Subclass UIView to create your custom button
Use UITapGestureRecognizer or touchesBegan, touchesEnded... for your interaction
Add two CALayer's for your foreground and background layers
Add your icon layer (This can be an UIImageView or any other way of displaying an image)
- (id)initWithIcon:(UIImage *)icon backgroundColor:(UIColor *)backgroundColor foregroundColor:(UIColor *)foregroundColor {
if (self = [super init]) {
// Background Layer Setup
_backgroundLayer = [CALayer new];
[_backgroundLayer setBackgroundColor:backgroundColor.CGColor];
[self.layer addSublayer:_backgroundLayer];
// Foreground Layer Setup
_foregroundLayer = [CALayer new];
[_foregroundLayer setBackgroundColor:foregroundColor.CGColor];
[self.layer addSublayer:_foregroundLayer];
// Icon Setup
_icon = [[UIImageView alloc] initWithImage:icon];
[_icon setContentMode:UIViewContentModeCenter];
[self addSubview:_icon];
UIGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(buttonTapped:)];
[self addGestureRecognizer:tapGesture];
}
return self;
}
- (void)setFrame:(CGRect)frame {
// Make sure super is called
[super setFrame:frame];
// Build the layout of backgroundLayer
[self.backgroundLayer setFrame:CGRectMake(frame.size.width*0.1, frame.size.width*0.1, frame.size.width*0.8, frame.size.width*0.8)];
[self.backgroundLayer setCornerRadius:frame.size.width*0.8/2];
// Build the layout of forgroundLayer
[self.foregroundLayer setFrame:CGRectMake(frame.size.width*0.05, frame.size.width*0.05, frame.size.width*0.9, frame.size.width*0.9)];
[self.foregroundLayer setCornerRadius:frame.size.width*0.9/2];
// Build the frame of your icon
[self.icon setFrame:CGRectMake(0, 0, frame.size.width, frame.size.width)];
}
- (void)buttonTapped:(UIGestureRecognizer*)gesture {
// Animate the foreground getting smaller
CABasicAnimation *foregroundFrameChange = [CABasicAnimation animationWithKeyPath:#"frame"];
foregroundFrameChange.fromValue = [NSValue valueWithCGRect:_foregroundLayer.frame];
foregroundFrameChange.toValue = [NSValue valueWithCGRect:CGRectMake(self.frame.size.width*0.1,self.frame.size.width*0.1, self.frame.size.width*0.8, self.frame.size.width*0.8)];
self.foregroundLayer.frame = CGRectMake(self.frame.size.width*0.1,self.frame.size.width*0.1, self.frame.size.width*0.8, self.frame.size.width*0.8);
// Animate the forground cornerRadius to stay rounded
CABasicAnimation *foregroundRadiusChange = [CABasicAnimation animationWithKeyPath:#"cornerRadius"];
foregroundRadiusChange.fromValue = [NSNumber numberWithDouble:self.foregroundLayer.cornerRadius];
foregroundRadiusChange.toValue = [NSNumber numberWithDouble:self.frame.size.width*0.8/2];
[self.foregroundLayer setCornerRadius:self.frame.size.width*0.8/2];
// Animate the background getting larger
CABasicAnimation *backgroundFrameChange = [CABasicAnimation animationWithKeyPath:#"frame"];
backgroundFrameChange.fromValue = [NSValue valueWithCGRect:self.backgroundLayer.frame];
backgroundFrameChange.toValue = [NSValue valueWithCGRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.width)];
self.backgroundLayer.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.width);
// Animate the background cornerRadius to stay rounded
CABasicAnimation *backgroundRadiusChange = [CABasicAnimation animationWithKeyPath:#"cornerRadius"];
backgroundRadiusChange.fromValue = [NSNumber numberWithDouble:self.backgroundLayer.cornerRadius];
backgroundRadiusChange.toValue = [NSNumber numberWithDouble:self.frame.size.width/2];
[self.backgroundLayer setCornerRadius:self.frame.size.width/2];
// Group all the animations to run simultaneously
CAAnimationGroup *allAnimations = [CAAnimationGroup animation];
allAnimations.duration = 2;
allAnimations.animations = #[foregroundFrameChange, foregroundRadiusChange, backgroundFrameChange, backgroundRadiusChange];
allAnimations.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[self.layer addAnimation:allAnimations forKey:#"animate"];
// Create your button action callback here
}
This was a quick mock up and not a complete solution but it will give you something to play with.

Related

CALayer - remove from view

I'm adding CAShapeLayer with some CABasicAnimation. I want to be able to remove the layer and draw it again but I can't succeed with this.
- (void)drawRect:(CGRect)rect {
[self drawLayer];
}
- (void)drawLayer {
if (_alayer && _layer.superlayer) {
[_alayer removeFromSuperlayer];
_alayer = nil;
}
_alayer = [[CAShapeLayer alloc] init];
_alayer.path = [self myPath].CGPath;
_alayer.strokeColor = [UIColor redColor].CGColor;
_alayer.fillColor = [UIColor clearColor].CGColor;
_alayer.lineWidth = 2.f;
_alayer.strokeStart = 0.f;
_alayer.strokeEnd = 1.f;
[self.layer addSublayer:_alayer];
// animate
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
animation.duration = 2.f;
animation.fromValue = #0.f;
animation.toValue = #1.f;
[_alayer addAnimation:animateStrokeEnd forKey:#"strokeEndAnimation"];
}
}
However a moment after calling [self setNeedsDisplay]; even though it stops at the breakpoint in drawLayer method, the drawing doesn't disappear and animate in again. alayer is declared as nonatomic, strong property. What am I doing wrong?
You should not call [self drawLayer]; fromm drawRect: because it is called periodically.
Use other viewDidLoad or call it from other method instead
Edited:
drawRect: should only be used for drawing, I don't know if it will even work if you add animation from there.
If you want it to disappear and appear, you should try to add delay using [self performSelector:withObject:afterDelay:].
I think changing hidden or opacity should suffice, you only need to removeAllAnimations to make sure no more animation attached to that layer.
However I think you should try other way than calling drawLayer from drawRect: because I don't it its good practice anyway.
The UIView has its own property layer by default. Please try to change a variable name to shapeLayer or another one.

Circle masked view animating too fast, and not tappable

I'm trying to make a circle-masked image view with animated mask, and played with different solutions. The example below does the job but I've got two problems with it:
1) Why is it that I cannot make the image tappable? Adding eg. a UITapGestureRecognizer does not work. My guess is that the mask prevents the touch actions being propagated to the lower level in the view hierarchy.
2) Animating the mask is running very fast and I cannot adjust the duration using UIView block animation
How can I solve these?
- (void) addCircle {
// this is the encapsulating view
//
base = [[UIView alloc] init];
//
// this is the button background
//
base_bgr = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"c1_bgr.png"]];
base_bgr.center = CGPointMake(60, 140);
[base addSubview:base_bgr];
//
// icon image
//
base_icon = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"c1_ico.png"]];
base_icon.center = CGPointMake(186*0.3/2, 182*0.3/2);
base_icon.transform = CGAffineTransformMakeScale(0.3, 0.3);
[base addSubview:base_icon];
//
// the drawn circle mask layer
//
circleLayer = [CAShapeLayer layer];
// Give the layer the same bounds as your image view
[circleLayer setBounds:CGRectMake(0.0f, 0.0f, [base_icon frame].size.width,
[base_icon frame].size.height)];
// Position the circle
[circleLayer setPosition:CGPointMake(186*0.3/2-7, 182*0.3/2-10)];
// Create a circle path.
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:
CGRectMake(0.0f, 0.0f, 70.0f, 70.0f)];
// Set the path on the layer
[circleLayer setPath:[path CGPath]];
[[base layer] setMask:circleLayer];
[self.view addSubview:base];
base.center = CGPointMake(100, 100);
base.userInteractionEnabled = YES;
base_bgr.userInteractionEnabled = YES;
base_icon.userInteractionEnabled = YES;
//
// NOT working: UITapGestureRecognizer
//
UITapGestureRecognizer *tapgesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapit:)];
[base_icon addGestureRecognizer:tapgesture];
//
// BAD but WORKS :) properly positioned UIButton over the masked image
//
base_btn = [UIButton buttonWithType:UIButtonTypeCustom];
base_btn.frame = CGRectMake(base.frame.origin.x, base.frame.origin.y, base_icon.frame.size.width, base_icon.frame.size.height);
[base_btn addTarget:self action:#selector(tapit:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:base_btn];
}
This is the tap handler, and here's the mask animation. Whatever number I tried in duration, it is animating fast - approximately 0.25 second, and I cannot adjust it.
- (void) tapit:(id) sender {
//...
[UIView animateWithDuration:1.0
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^ {
[circleLayer setTransform:CATransform3DMakeScale(10.0, 10.0, 1.0)];
}
completion:^(BOOL finished) {
// it is not necessary if I manage to make the icon image tappable
base_btn.frame = [base convertRect:base_icon.frame toView:self.view];
}];
}
}
1) touches are not propagated down from base because it's initiated without a frame, so its frame will be CGRectZero. Views don't get touch events that start outside of their bounds. Simply set a valid frame on base that includes entire tap target.
2) setTransform: on a layer invokes an implicit animation which uses Core Animation's default duration of 0.25 (you guessed it right :)). The best solution would be to use CABasicAnimation instead of UIView-based animation. Something like this:
CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
scaleAnimation.toValue = #(10.0f);
scaleAnimation.duration = 2;
[circleLayer addAnimation:scaleAnimation forKey:nil];
Note: by default, CABasicAnimation will remove itself from a layer when complete and the layer will snap back to old values. You can prevent it by for example setting that animation's removedOnCompletion property to NO and removing it yourself later using CALayer's removeAnimationForKey: method (just set a key instead of passing nil wheen adding the animation), but that depends on what exactly you want to accomplish with this.

How do I simultaneously animate a UIImageView's image and the UIImageView itself?

The title may not be so clear, but what I want to do is to make the UIImageView display a series of images (sort of like a gif) and I do this by setting the animationImages to an array of images and then calling [imageView startAnimating];. This works fine. I also have code that moves the UIImageView around with a CABasicAnimation, and this animation code also works fine. However, when I try to both animate the images of the UIImageView and try to move the UIImageView around, the images of the UIImageView stop animating. Is there a workaround?
Here's my code for animating the content of the UIImageView:
self.playerSprite = [[UIImageView alloc] initWithImage:[self.player.animationImages objectAtIndex:0]];
[self.playerSprite setFrame:CGRectMake(self.center.x, self.center.y, self.tileSet.constants.TILE_WIDTH, self.tileSet.constants.TILE_HEIGHT)];
self.playerSprite.animationImages = self.player.animationImages;
self.playerSprite.animationDuration = self.tileSet.constants.animationDuration;
self.playerSprite.animationRepeatCount = 0; //Makes it repeat indefinitely
And here's my coding for animating the UIImageView with a CABasicAnimation:
float playerSpriteX = self.playerSprite.center.x;
float playerSpriteY = self.playerSprite.center.y;
CABasicAnimation *moveAnimation = [CABasicAnimation animation];
moveAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(playerSpriteX + TILE_WIDTH, playerSpriteY)];
[moveAnimation setDelegate:self];
[moveAnimation setFillMode:kCAFillModeForwards];
[moveAnimation setRemovedOnCompletion:NO];
[moveAnimation setDuration:MOVE_ANIMATION_DURATION];
[self.playerSprite.layer addAnimation:moveAnimation forKey:#"position"];
So the gif effect isn't working while the UIImageView's position is being animated. My question is how can I make it so the UIImageView cycles through an array of images while its position is being animated?
This is speculation and I haven't tested this idea at all, but have you considered implementing the image animation in the same fashion you're animating the position, with a CAKeyframeAnimation? You'd need to construct an array of CGImage objects from your UIImage array (to set the values property of the keyframe animation) but it looks like a pretty straightforward conversion:
CAKeyframeAnimation *imageAnimation = [CAKeyframeAnimation animation];
imageAnimation.calculationMode = kCAAnimationDiscrete; // or maybe kCAAnimationPaced
imageAnimation.duration = self.tileSet.constants.animationDuration;
imageAnimation.repeatCount = HUGE_VALF;
// the following method will need to be implemented to cast your UIImage array to CGImages
imageAnimation.values = [self animationCGImagesArray];
[self.playerSprite.layer addAnimation:imageAnimation forKey:#"contents"];
-(NSArray*)animationCGImagesArray {
NSMutableArray *array = [NSMutableArray arrayWithCapacity:[self.player.animationImages count]];
for (UIImage *image in self.player.animationImages) {
[array addObject:(id)[image CGImage]];
}
return [NSArray arrayWithArray:array];
}
I've just done something similar. The code I used looks like this:
CGRect r = ziv.frame;
r.origin.x += WrongDistance;
[ziv startAnimating];
[UIView animateWithDuration:3.0 animations:^(void){
[ziv setFrame:r];
} completion:^(BOOL finished){
[ziv stopAnimating];
if (finished){
// not canceled in flight
if (NumWrong == MaxWrong)
[self endOfGame:NO];
else
[self nextRound:self];
}
}];
Perhaps the issue you're running into is because both animations are on the same thread?
Your "basic" problem starts on this line:
CABasicAnimation *moveAnimation = [CABasicAnimation animation];
A CABasicAnimation object uses only a single keyframe. That means that the content of the layer that you're animating is drawn once, and then that image is used for the duration of the animation.
Using a CAKeyframeAnimation as Seamus suggests is one way to deal with the problem -- a keyframe animation will redraw the content of the animated layer multiple times, so drawing successive images for each keyframe will animate the content as well as position.
This is not a good way to make a sprite. I could insist that you should be using OpenGL, but there may be no need to go that far; you can do it all with Core Animation of a layer, and I think you'll be better off doing that than trying to use a full-fledged UIImageView.
Using Core Animation of a layer, I was able to make this PacMan sprite animate across the screen while opening and closing his mouth; isn't that the sort of thing you had in mind?
Here is a video showing the animation!
http://www.youtube.com/watch?v=WXCLc9ww8MI
And yet the actual code creating the animation is extremely simple: just two layer animations (Core Animation), one for the changing image, the other for the position.
You should not award this answer the bounty! I am now merely echoing what Seamus Campbell said; I'm just filling out the details of his answer a little.
Okay, so here's the code that generates the movie linked above:
- (void)viewDidLoad {
[super viewDidLoad];
// construct arr, an array of CGImage (omitted)
// it happens I've got 5 images, the last being the same as the first
self.images = [arr copy];
// place sprite into the interface
self.sprite = [CALayer new];
self.sprite.frame = CGRectMake(30,30,24,24);
self.sprite.contentsScale = [UIScreen mainScreen].scale;
[self.view.layer addSublayer:self.sprite];
self.sprite.contents = self.images[0];
}
- (void)animate {
CAKeyframeAnimation* anim =
[CAKeyframeAnimation animationWithKeyPath:#"contents"];
anim.values = self.images;
anim.keyTimes = #[#0,#0.25,#0.5,#0.75,#1];
anim.calculationMode = kCAAnimationDiscrete;
anim.duration = 1.5;
anim.repeatCount = HUGE_VALF;
CABasicAnimation* anim2 =
[CABasicAnimation animationWithKeyPath:#"position"];
anim2.duration = 10;
anim2.toValue = [NSValue valueWithCGPoint: CGPointMake(350,30)];
CAAnimationGroup* group = [CAAnimationGroup animation];
group.animations = #[anim, anim2];
group.duration = 10;
[self.sprite addAnimation:group forKey:nil];
}

CAKeyframeAnimation not animating until I rotate device

I seem to be missing the obvious when animating a key frame. I have looked at many code samples including Apple's MoveMe, referenced in the CAKeyframeAnimation documentation, yet, I cant find a discrepancy that would cause what I'm seeing.
I create a CGMutablePathRef, then a CAKeyframeAnimation and set it to animate an image view along the path. An animation group is created so I can remove the view when done.
Yet, my animation never shows up. UNTIL I rotate the device. It seems a relayout of the view causes the animation to kickstart. I tried the obvious like [theImageView setNeedsDisplay] or even setNeedsLayout, and on the container view as well. Yet, still cant get it to work when I need to. They only show up when I rotate the device.
In the following, -cgPathFromArray: takes an NSArray of internal opcodes which is converted into a CGPathRef. Its verified to be correct because when I rotate the device, the animation does show along the programmed path.
- (void) animateImage: (NSString*) imageName
onPath: (NSArray*) path
withDuration: (NSString*) duration
{
if (self.sceneView)
{
CGMutablePathRef animationPath = [self cgPathFromArray: path];
if (animationPath)
{
UIImage* image = [self findImage: imageName];
if (image)
{
UIImageView* imageView = [[UIImageView alloc] initWithFrame: CGRectMake(0, 0, image.size.width, image.size.height)];
if (imageView)
{
CAKeyframeAnimation* keyFrameAnimation = [CAKeyframeAnimation animationWithKeyPath: #"position"];
imageView.image = image;
[self.sceneView addSubview: imageView];
keyFrameAnimation.removedOnCompletion = YES;
keyFrameAnimation.fillMode = kCAFillModeForwards;
keyFrameAnimation.duration = duration.floatValue;
keyFrameAnimation.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionLinear];
keyFrameAnimation.repeatCount = 0;
keyFrameAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
[keyFrameAnimation setPath: animationPath];
//group animation with termination block to cleanup
CAAnimationGroup* group = [CAAnimationGroup animation];
group.duration = keyFrameAnimation.duration;
group.removedOnCompletion = YES;
group.fillMode = kCAFillModeForwards;
group.animations = #[keyFrameAnimation];
CorpsAnimationCompletionBlock theBlock = ^void(void)
{
[imageView removeFromSuperview];
};
[group setValue: theBlock
forKey: kCorpsAnimationCompletionBlock];
group.delegate = self;
[imageView.layer addAnimation: group
forKey: nil];
}
}
}
}
}
Anyone can help with this?
You are probably having trouble because you're adding an animation to a layer in the same transaction where the layer is added to the visible layer tree. Core Animation doesn't like to attach animations to layers that haven't been committed yet. You may be able to work around this by doing [CATransaction flush] after adding the layer.
Your code is rather hard to look at because of the excessive nesting. Consider using early returns to make it more readable.
Also, you're explicitly creating the same frame that the -[UIImage initWithImage:] initializer would create for you.
If you're using an animation group and setting a delegate simply so you can execute a block at the end of the animation, there is an easier way. You can begin a CATransaction, set the transaction's completion block, then add the animation, then commit the transaction.
Thus:
- (void) animateImage:(NSString *)imageName onPath: (NSArray *)path
withDuration: (NSString *)duration
{
if (!self.sceneView)
return;
CGMutablePathRef animationPath = [self cgPathFromArray:path];
if (!animationPath)
return;
UIImage *image = [self findImage:imageName];
if (!image)
return;
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
[self.sceneView addSubview: imageView];
// commit the implicit transaction so we can add an animation to imageView.
[CATransaction flush];
[CATransaction begin]; {
[CATransaction setCompletionBlock:^{
[imageView removeFromSuperview];
}];
CAKeyframeAnimation *animation = [CAKeyframeAnimation
animationWithKeyPath:#"position"];
animation.duration = duration.floatValue;
animation.timingFunction = [CAMediaTimingFunction
functionWithName:kCAMediaTimingFunctionLinear];
animation.path = animationPath;
[imageView.layer addAnimation:animation forKey:animation.keyPath];
} [CATransaction commit];
}

How to apply partial fade in-out in IOS?

I have 2 images. One is in the back of the other. I want to fade-out the top image starting from the left corner. How can I do this?
Should I use gradient mask?
Here's a technique that will fade out whatever's in a CALayer, from top-left to bottom-right, revealing whatever is underneath the CALayer.
Let's say we're going to fade out the layer of a UIImageView called self.fadeView.
We'll create a CAGradientLayer and use it as self.fadeView.layer.mask. The gradient will go from alpha=0 (transparent) to alpha=1 (opaque) using four stops: 0, 0, 1, 1. Yes, two zeros and then two ones. When we want fadeView to be opaque, we'll set the stop locations to -1, -.5, 0, 1. That way the two alpha=0 stops are completely outside of the layer bounds. When we want fadeView to be transparent, we'll set the stop locations to 0, 1, 1.5, 2. That way the two alpha=1 stops are completely outside of the layer bounds. The CAGradientLayer will automatically animate changes to its stop locations, creating a cross-fade effect.
Here's the code:
#import "ViewController.h"
#implementation ViewController
#synthesize fadeView = _fadeView;
static NSArray *locations(float a, float b, float c, float d)
{
return [NSArray arrayWithObjects:
[NSNumber numberWithFloat:a],
[NSNumber numberWithFloat:b],
[NSNumber numberWithFloat:c],
[NSNumber numberWithFloat:d],
nil];
}
// In my test project, I put a swipe gesture recognizer on fadeView in my XIB
// with direction = Up and connected it to this action.
- (IBAction)fadeIn
{
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithDouble:2.0] forKey:kCATransactionAnimationDuration];
((CAGradientLayer *)self.fadeView.layer.mask).locations = locations(-1, -.5, 0, 1);
[CATransaction commit];
}
// In my test project, I put a swipe gesture recognizer on fadeView in my XIB
// with direction = Down and connected it to this action.
- (IBAction)fadeOut
{
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithDouble:2.0] forKey:kCATransactionAnimationDuration];
((CAGradientLayer *)self.fadeView.layer.mask).locations = locations(0, 1, 1.5, 2);
[CATransaction commit];
}
- (void)viewDidLoad
{
[super viewDidLoad];
CAGradientLayer *mask = [CAGradientLayer layer];
mask.frame = self.fadeView.bounds;
mask.colors = [NSArray arrayWithObjects:
(__bridge id)[UIColor clearColor].CGColor,
(__bridge id)[UIColor clearColor].CGColor,
(__bridge id)[UIColor whiteColor].CGColor,
(__bridge id)[UIColor whiteColor].CGColor,
nil];
mask.startPoint = CGPointZero; // top left corner
mask.endPoint = CGPointMake(1, 1); // bottom right corner
self.fadeView.layer.mask = mask;
[self fadeIn]; // initialize mask.locations
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
#end
Define your own CALayer subclass with its own actionForKey animation for the key contents.
#interface CustomLayer: CALayer
#end
#implementation CustomLayer
+ (id<CAAction>)actionForKey:(NSString*)event {
if ([event isEqualToString:#"contents"]) {
CATransition* basicAnimation = [CATransition animation];
basicAnimation.type = kCATransitionMoveIn;
basicAnimation.subtype = kCATransitionFromLeft;
return basicAnimation;
} else {
return nil;
}
}
#end
Doing like this, whenever you set the contents property of your CustomLayer object, the transition will be shown:
CustomLayer* layer = [CustomLayer layer];
layer.contents = <FIRST_CGIMAGEREF_HERE>;
...
layer.contents = <SECOND_CGIMAGEREF_HERE>;
As usual, you can wrap the property assignment in a transaction:
[CATransaction begin];
....
[CATransaction end];
if you want more control on the duration of the transition, but in any case the transition will be applied.
EDIT:
For the kind of transition you would like to have, you can have a look at this sample on GitHub. Look for the ShutterTransition. It is not exactly what you are looking for, but it could lead you along the right path.

Resources