Objectivec – 3D View Rotation

Today, how to make a 3d rotation using two UIView.

Problem

I want to rotate my view in 3d perspective, but after animation, buttons not responds to touch events.
This happens because after a 3d trasform, the point of view is different, and the frame go out for a walk…

Solution

Use of UIGraphicsGetCurrentContext()

Geometry

views
Positioning the y coordinate of v2 view to the heigth of view v1. After that apply a translation and a rotation of 90° to v2. Like the image upper (ok, i could make the image better… i know…).

How it’s made?

Using UIGraphicsGetCurrentContext(), we can grab the content of a particular view on screen, that is visible (not hidden or alpha 0) and save in memory as an UIImage.
You know that you can put an UIImage in an UIImageView and add to screen.
Before starting the animation, grabbed images are added to the main view. At the end of animations are removed to show original view.

Setup IB

Schermata 07-2456481 alle 13.45.12
What you need for this example in your Interface Builder, is this:
Specifically,

  • View: your main view
  • Container View (*1)
  • Transform Layer (*2)
  • Red View: your front cube view (*3)
  • Green View: your hidden back view (*4)
  • Buttons, sliders and labels are for debug/test
Container View (1*)

Is the view used to setup perspective transformation to all sub layers

CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity;
rotationAndPerspectiveTransform.m34 = 1.0 / -200;
_MainContainer.layer.sublayerTransform = rotationAndPerspectiveTransform;

Parameter m34 is the position 3,4 in the matrix animation (see below Apple reference).


CATransform3D

Defines the standard transform matrix used throughout Core Animation.

[code lang=”java” autolinks=”false” collapse=”false” firstline=”1″ gutter=”true” htmlscript=”false” light=”false” padlinenumbers=”false” smarttabs=”true” tabsize=”4″ toolbar=”false”]struct CATransform3D
{
CGFloat m11, m12, m13, m14;
CGFloat m21, m22, m23, m24;
CGFloat m31, m32, m33, m34;
CGFloat m41, m42, m43, m44;

// m stand for matrix and the numbers stands for
// rows and cols of the matrix.
// Example m34 is the entry at position 3,4 in the matrix.
};
typedef struct CATransform3D CATransform3D;[/code]

The transform matrix is used to rotate, scale, translate, skew, and project the layer content. Functions are provided for creating, concatenating, and modifying CATransform3D data.

Ref: apple.com


Transform Layer (1*)

A subclass of UIView that returns CALayerTransform.
CALayerTransform is mandatory if you want the 3d effect, otherwise the view became flat, without visible 3d effect.
From Interface Builder set Transform Layer class name to APTransormView.

APTransformView class

[code lang=”java” autolinks=”false” collapse=”false” firstline=”1″ gutter=”true” htmlscript=”false” light=”false” padlinenumbers=”false” smarttabs=”true” tabsize=”4″ toolbar=”false”]// header APTransformView.h
#import <uikit/UIKit.h>;

@interface APTransformView : UIView
@end

// implementation APTransformView.m

#import "APTransformView.h"
#import &lt; quartzcore /QuartzCore.h &gt;

@implementation APTransformView

+ ( Class ) layerClass
{
return [ CATransformLayer class ];
}
@end[/code]

Ok, give me the code!

Preparing the view…

[code lang=”java” autolinks=”false” collapse=”false” firstline=”1″ gutter=”true” htmlscript=”false” light=”false” padlinenumbers=”false” smarttabs=”true” tabsize=”4″ toolbar=”false”]- ( void ) setupCellTransform
{
// apply a transform to Container view
CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity;
// you can play with m34 value…
rotationAndPerspectiveTransform.m34 = 1.0 / -200;
// apply transform to all sublayers
_MainContainer.layer.sublayerTransform = rotationAndPerspectiveTransform;

//
// child 1 – grab and draw view
//
UIGraphicsBeginImageContext(_MainContainer.frame.size);
[_View.layer renderInContext:UIGraphicsGetCurrentContext()];
// image captured and saved in *grab
UIImage *grab = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
if ( _imgvcChild1 ) [_imgvcChild1 removeFromSuperview];
// add an UIImageView to the _TrasformLayer view
_imgvcChild1 = [[ UIImageView alloc ] initWithImage:grab];
[ _TransformLayer addSubview:_imgvcChild1 ];

//
// child 2 – repeating…
//
UIGraphicsBeginImageContext(_MainContainer.frame.size);
[_AddonView.layer renderInContext:UIGraphicsGetCurrentContext()];
grab = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
if ( _imgvcChild2 ) [_imgvcChild2 removeFromSuperview];
_imgvcChild2 = [[ UIImageView alloc ] initWithImage:grab];
[ _TransformLayer addSubview:_imgvcChild2 ];

// positioning views
CGRect r = _PrimaryView.bounds;
_imgvcChild1.frame = r;
r.origin.y = r.size.height;
_imgvcChild2.frame = r;

// change anchoPoint of z axis to the half of your view
_TransformLayer.layer.anchorPointZ = -(r.size.height/2);
_TransformLayer.layer.zPosition    = -(r.size.height/2);

//
// transform child-2 grabbed
//
CATransform3D trans = CATransform3DIdentity;
// apply a rotate and a translation to order view like first image uppper.
trans = CATransform3DRotate      ( trans, DegreesToRadians(-90), 1, 0, 0 );
trans = CATransform3DTranslate   ( trans, 0, r.size.height/2, 0 );
trans = CATransform3DTranslate   ( trans, 0, 0, -r.size.height/2 );
// apply transform to the view
[ _imgvcChild2.layer setTransform: trans ];
}[/code]

Rotate the layer

[code lang=”java” autolinks=”false” collapse=”false” firstline=”1″ gutter=”true” htmlscript=”false” light=”false” padlinenumbers=”false” smarttabs=”true” tabsize=”4″ toolbar=”false”]- ( IBAction ) rotate: ( id ) sender
{
[self setupAnimation];

// clean
[_Child1 removeFromSuperview];
[_Child2 removeFromSuperview];

[ UIView animateWithDuration: 1.5 animations: ^
{
// _front is a BOOL used to toggle views
CATransform3D trans = CATransform3DIdentity;
trans = CATransform3DRotate ( trans, DegreesToRadians(_front ? 90 : 0), 1, 0, 0 );
// set transform to _Parent, that is the CALayerTransform that make the rotation
[_Parent.layer setTransform:trans];
} completion:^(BOOL finished) {
// toggling
if ( _front ) {
[_imgvcChild1 removeFromSuperview];
[_Container addSubview:_Child2];
}
else {
[_imgvcChild2 removeFromSuperview];
[_Container addSubview:_Child1];
}
_front = !_front;
}];
}[/code]

Of course, DegreesToRadians is a method that convert degree to radians.

[code lang=”bash” autolinks=”false” collapse=”false” firstline=”1″ gutter=”true” htmlscript=”false” light=”false” padlinenumbers=”false” smarttabs=”true” tabsize=”4″ toolbar=”false”]degree * M_PI / 180[/code]

Final result


Want the code? Download from here.

it’a all.
Enjoy rotation.

 

Alberto Pasca

Software engineer @ Pirelli & C. S.p.A. with a strong passion for mobile  development, security, and connected things.