IOS achieve 3D card folding effect

introduce

Recently in the development process need to use 3D card effects. Therefore, we have studied how to use the perspective image in iOS. This article is mainly to study the changes in the iOS 3D, and then do a card folding effect, a bit similar to the laptop folding effect. In the study of the 3D transform, encountered some problems, here to record.

renderings.Gif

Basic knowledge

• UIView and CALayer

Each UIView has an internal CALayer behind the contents of the rendering and display, and the size of the UIView style provided by the internal Layer. A CALayer frame is determined by its anchorPoint, position, bounds, transform jointly decided, and a View frame simply returns CALayer frame, some of the attributes of the same UIView center and bounds Layer is returned.

• Introduction to CALayer attributes

UIView has frame, bounds, Center three attributes, CALayer also has similar properties, namely frame, bounds, position, anchorPoint. Frame and bounds better understanding, bounds can be regarded as x coordinates and Y coordinates are 0 frame, here is mainly to learn position, anchorPoint, the two attributes.

`@property CGPoint position; @property CGPoint anchorPoint;`

Position is the layer in the anchorPoint in the position of the coordinates of the superLayer, as shown in the following figure:

Positon was (100100), anchorPoint (0, 0)

pictures

Positon is (100100), anchorPoint is (0.5, 0.5)

pictures

Positon is (100100), anchorPoint is (1, 1)

pictures

Frame, position and anchorPoint have the following relationship:

`Frame.origin.x = position.x - anchorPoint.x * bounds.size.width; frame.origin.y = position.y - anchorPoint.y * bounds.size.height;`
• CALayer perspective projection transformation

CALayer uses the orthogonal projection, so far the effect, but there is no clear API can use perspective projection matrix, but we can construct their own matrix with perspective projection matrix. The perspective effect of
CATransform3D is controlled by a simple element in a matrix M34
. M34 is used to scale the values of X and Y to calculate how far away from the perspective. The default value of M34 is 0, we can set the M34 to -1.0 / disZ to apply the perspective effect, disZ represents the distance between the camera and the screen, in pixels.

`CATransform3D CATransform3DMakePerspective (CGPoint center, float disZ) {CATransform3D transToCenter = CATransform3DMakeTranslation (-center.x, -center.y, 0); CATransform3D transBack = CATransform3DMakeTranslation (center.x, center.y, 0); CATransform3D scale = CATransform3DIdentity; scale.m34 = -1.0f/disZ; return CATransform3DConcat (CATransform3DConcat (transToCenter, scale), transBack);} CATransform3D CATransform3DPerspect (CATransform3D T, CGPoint center, float disZ) {return CATransform3DConcat (T, CATransform3DMakePerspective (center, disZ));}`

Implementation process

1, to create a new QMView attempt to initialize the time to add the next two attempts. Here we set the anchor of the Topview view for CGPointMake (0.5, 0) because we want the Topview view to rotate around the top. We set the anchor for the bottomView view to CGPointMake (0.5, 1) because we want the bottomView view to rotate around the bottom. The X coordinates of the anchor points are the same, and we want the same rules as the two views in the top and bottom. CALayer affine transformation, is based on the relevant transformation of the anchor.

`- (instancetype) initWithFrame: (CGRect frame) {if (self = [super initWithFrame:frame]) {/ / [self setBackgroundColor:[UIColor greenColor]]; _originFrame = frame; / / _topView = [[UIView alloc] initWithFrame:CGRectMake to the upper (0, 0, frame.size.width, frame.size.height/2); _topView.layer.anchorPoint = CGPointMake (0.5, 0); _topView.layer.position = CGPointMake (frame.size.width/2, 0); _topView.backgroundColor = [UIColor orangeColor]; UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake (0, 0, 300, 200)]; label.text = @ "CALayer default using orthogonal projection, so far the effect, but there is no clear API can use perspective projection matrix, but we can Through the matrix with their structural perspective projection matrix. CATransform3D perspective effect to control "through a very simple element of a matrix; label.numberOfLines = 0; [_topView = addSubview:label]; / / _bottomView [[UIView alloc] initWithFrame:CGRectMake bottom view (0, 0, frame.size.width, frame.size.height/2); _bottomView.layer.anchorPoint = CGPointMake (0.5, 1); _bottomView.layer.position = CGPointMake (frame.size.width/2, frame.size.height); _bottomView.backgroundColor [UIColor = blueColor]; UILabel = *label1 + UILabel alloc] initWithFrame:CGRectMake (0, 0, 300, 200)]; label1.text = @" CALayer default using orthogonal projection, so far the effect, but there is no clear API can use perspective projection matrix, but we Through the matrix with their structural perspective projection matrix. CATransform3D perspective effect to control "through a very simple element of a matrix; label1.numberOfLines = 0; [_bottomView addSubview:label1]; [self addSubview:_bottomView]; [self addSubview:_topView];} return self;}`

2, the position offset into the angle, each layer maximum rotation angle of 90 degrees.

`CGFloat thelta = M_PI/2* (_offset/ (_originFrame.size.height/2));`

3, according to the angle around the X axis (1, 0, 0) rotation matrix. Note here that the two views rotate in different directions.

`CATransform3D transform = CATransform3DMakeRotation (-thelta, 1, 0, 0); CATransform3D transform1 = CATransform3DMakeRotation (thelta, 1, 0, 0);`

4, the perspective projection matrix, and applied to the two UIView CALayer.

`_topView.layer.transform = CATransform3DPerspect (transform, CGPointZero, kDistanceZ); _bottomView.layer.transform = CATransform3DPerspect (transform1, CGPointZero, kDistanceZ);`

5, after the modification of the position. After the affine transformation of CALayer, its width and height will change. We need to adjust the bottom view to connect to the bottom of the top view. Therefore, we need to reset the bottom view of the position. We can first get the height of the top of the transformation, which is the top of the bottom view. Then according to position.y = frame.origin.y + anchorPoint.y * bounds.size.height calculates the top view of the position. Finally fix the height of the entire QMView.

`CGFloat position = _topView.layer.frame.size.height + 1 * _bottomView.layer.frame.size.height; _bottomView.layer.position = CGPointMake (_originFrame.size.width/2, position); CGRect rect = self.frame; rect.size.height = _topView.layer.frame.size.height + _bottomView.layer.frame.size.height; self.frame = rect;`

The entire transform part of the code:

`- (void) setOffset: (CGFloat) {if (offset < offset offset > ||; 0; _originFrame.size.height/2) {return}; _offset CGFloat = offset; thelta = M_PI/2* (_offset/ (_originFrame.size.height/2)); CATransform3D transform = CATransform3DMakeRotation (-thelta, 1, 0, 0); CATransform3D transform1 = CATransform3DMakeRotation (thelta, 1. 0, 0); _topView.layer.transform = CATransform3DPerspect (transform, CGPointZero, kDistanceZ); _bottomView.layer.transform = CATransform3DPerspect (transform1, CGPointZero, kDistanceZ); //position.x = frame.origin.x + anchorPoint.x * bounds.size.width; //position.y = frame.origin.y + anchorPoint.y * bounds.size.height; CGFloat position = _topView.layer.frame.size.h Eight + 1 * _bottomView.layer.frame.size.height; _bottomView.layer.position = CGPointMake (_originFrame.size.width/2, position); CGRect rect = self.frame; rect.size.height = _topView.layer.frame.size.height + _bottomView.layer.frame.size.height; self.frame = rect;}`

Exploration process

1, the process of rotating the bottom view does not modify the position, the following effects will appear. :

unnamed.Gif

2, fix the coordinates, we modify the bottomView frame, rather than its layer position.

`- (void) setOffset: (CGFloat) {if (offset < offset offset > ||; 0; _originFrame.size.height/2) {return}; _offset CGFloat = offset; thelta = M_PI/2* (_offset/ (_originFrame.size.height/2)); CATransform3D transform = CATransform3DMakeRotation (-thelta, 1, 0, 0); CATransform3D transform1 = CATransform3DMakeRotation (thelta, 1. 0, 0); _topView.layer.transform = CATransform3DPerspect (transform, CGPointZero, kDistanceZ); _bottomView.layer.transform = CATransform3DPerspect (transform1, CGPointZero, kDistanceZ); CGRect rect = _bottomView.frame; rect.origin.y = _topView.frame.size.height; _bottomView.frame = rect;}`

A magical effect:

unnamed.Gif

If in the process of perspective transformation, modify the UIView frame, will have an impact on after transformation, it is said that the transformation is based on the bottomView transform, which is why more and more small. Before you want to change each time before the reduction of bottomView frame, but in the actual process of trouble.

3, using GLKit transform. According to the related concepts of the 3D perspective projection, it is thought that the matrix transform.

transform formula.Png

Because the GLKit has the related transformation function, in this I will CATransform3D transforms into GLKMatrix4, then uses the function GLKMatrix4MultiplyVector4 to carry on the transformation.

`GLKVector4 transform3DMultiplyVector4 (CATransform3D transform, GLKVector4 vec4) {GLKMatrix4 matrix = GLKMatrix4Make (transform.m11, transform.m12, transform.m13, transform.m14, transform.m21, transform.m22, transform.m23, transform.m24, transform.m31, transform.m32, transform.m33, transform.m34, transform.m41, transform.m42, transform.m43, transform.m44); GLKVector4 transVec4 = GLKMatrix4MultiplyVector4 (matrix, vec4); return transVec4;}`

Details are as follows:

`- (void) setOffset: (CGFloat) {if (offset < offset offset > ||; 0; _originFrame.size.height/2) {return}; _offset CGFloat = offset; thelta = M_PI/2* (_offset/ (_originFrame.size.height/2)); CATransform3D transform = CATransform3DMakeRotation (-thelta, 1, 0, 0); CATransform3D transform1 = CATransform3DMakeRotation (thelta, 1. 0, 0); / / initialize the height matrix transformation after the height of GLKVector4 Top1 = transform3DMultiplyVector4 (transform, GLKVector4Make (_originFrame.size.width/2, 0, 0, 1); top2 =transform3DMultiplyVector4 (transform) GLKVector4, GLKVector4Make (_originFrame.size.width/2, _originFrame.size.height/2, 0, 1)); GLKVector4 bottom1 = transform3DMultiplyVector4 (transform1, GLKVector4Make (_o RiginFrame.size.width/2, _originFrame.size.height/2, 0, 1); bottom2 =transform3DMultiplyVector4 (transform1) GLKVector4, GLKVector4Make (_originFrame.size.width/2, _originFrame.size.height, 0, 1)); _topView.layer.transform = CATransform3DPerspect (transform, CGPointZero, kDistanceZ); _bottomView.layer.transform = CATransform3DPerspect (transform1, CGPointZero, kDistanceZ); NSLog (@ 1>%f%f, top2.y - top1.y. Top1.y (@ NSLog); "2>%f", _topView.layer.frame.size.height); NSLog (@ 3>%f, bottom2.y - bottom1.y); //position.y = frame.origin.y + anchorPoint.y * bounds.size.height; CGFloat = position (top2.y - top1.y) + 1 * (bottom2.y - bottom1.y) _bottomView.layer.position (_originFrame = CGPointMake; .size.width/2, position);}`

The result is still a large error, as shown below:

renderings.Png