CALayer perspective reset - ios

I've got the following trouble.
In my app i'm implementing flip-clock. I've got 3 CATransformLayers and can swipe them up-down and down-up. I always have two layers on top with indexes 1 at top and 0 at bottom and one layer down with index 2. So, when i flip down-up, i quickly rotate layer 0 down and transform layer 2 up, so layer 0 could already be visible.
i'm using the following code to do this :
auto CATransform3D t3d = CATransform3DIdentity;
t3d.m34 = 1.0/-500.0;
layer2.sublayerTransform = CATransform3DRotate(t3d, -angle , 1.0, 0.0, 0.0) ;
to flip layer at index 2 ( bottom layer ) up.
After this, i immedeately do the following with the layer at index 0 :
auto CATransform3D t3d = CATransform3DIdentity;
t3d.m34 = 0.0; // for make sure m34 is zero - excess code
layer0.sublayerTransform = CATransform3DRotate(t3d, M_PI , 1.0, 0.0, 0.0) ;
After all animation completed - of up-down direction - i rearrange transform sublayers to make this action more and more times :
[container.layer insertSublayer:layer0 above:(CALayer*)[container.layer.sublayers objectAtIndex:2]] ;
Container is flip-clock's holder base view. The problem is, that when i flip one time - zero layer falls down on the back view normally. After second flip - it's all right too. But after third flip - the layer, that was flipped the very first time ( former layer 2) falls in the back scene with perspective, not looking at the fact, that i reset transformation to m34 = 0.0. What can be the reason for this?
As always, any help would be appreciated.

well, the problem was in transformation speed - i increased it to 2000 ( it was 0.5 ) and the back layer isn't visible anymore!

Related

CGAffineTransfrom: Translating after a rotation

I am trying to animate a rectangle after I have rotated it with CGAffineTransform. The issue I am having is that the rectangle ends up where it is suppose to be, but the "starting" position for the second transform is not what I expect. This is only an issue because I am animating it. Here is my code below:
UIView.animate(withDuration: 5) {
let shift = 200 * CGFloat(2.0.squareRoot() / 2)
view.transform = view.transform.translatedBy(x: shift, y: 0)
}
view is already defined elsewhere and I have already rotated the view with this:
let rotation = CGAffineTransform(rotationAngle: (45/180)*CGFloat(M_PI))
view.transform = rotation
Before the animation, this is what it looks like: 1
For some reason, the transformation starts above (a little offscreen) and then moves down into position. 2
I would like this to happen instead, where it starts from the original picture, and then shifts in the direction of how its rotated. 3
Note I did try to apply the same shift value to both x and y for the translation but that did not fix my issue.

Ios Swift Animate a view in non linear path

I am trying to animate a UIView through non linear path(i'm not trying to draw the path itself) like this :
The initial position of the view is determinated using a trailing and bottom constraint (viewBottomConstraint.constant == 100 & viewTrailingConstraint.constant == 300)
I am using UIView.animatedWithDuration like this :
viewTrailingConstraint.constant = 20
viewBottomConstraint.constant = 450
UIView.animateWithDuration(1.5,animation:{
self.view.layoutIfNeeded()
},completition:nil)
But it animate the view in a linear path.
You can use keyFrame animation with path
let keyFrameAnimation = CAKeyframeAnimation(keyPath:"position")
let mutablePath = CGPathCreateMutable()
CGPathMoveToPoint(mutablePath, nil,50,200)
CGPathAddQuadCurveToPoint(mutablePath, nil,150,100, 250, 200)
keyFrameAnimation.path = mutablePath
keyFrameAnimation.duration = 2.0
keyFrameAnimation.fillMode = kCAFillModeForwards
keyFrameAnimation.removedOnCompletion = false
self.label.layer.addAnimation(keyFrameAnimation, forKey: "animation")
Gif
About this function
void CGContextAddQuadCurveToPoint (
CGContextRef _Nullable c,
CGFloat cpx,
CGFloat cpy,
CGFloat x,
CGFloat y
);
(cpx,cpy) is control point,and (x,y) is end point
Leo's answer of using Core Animation and CAKeyframeAnimation is good, but it operates on the view's "presentation layer" and only creates the appearance of moving the view to a new location. You'll need to add extra code to actually move the view to it's final location after the animation completes. Plus Core Animation is complex and confusing.
I'd recommend using the UIView method
animateKeyframesWithDuration:delay:options:animations:completion:. You'd probably want to use the option value UIViewKeyframeAnimationOptionCalculationModeCubic, which causes the object to move along a curved path that passes through all of your points.
You call that on your view, and then in the animation block, you make multiple calls to addKeyframeWithRelativeStartTime:relativeDuration:animations: that move your view to points along your curve.
I have a sample project on github that shows this and other techniques. It's called KeyframeViewAnimations (link)
Edit:
(Note that UIView animations like animateKeyframes(withDuration:delay:options:animations:completion:) don't actually animate your views along the path you specify. They use a presentation layer just like CALayer animations do, and while the presentation layer makes the view look like it's moving along the specified path, it actually snaps from the beginning position to the end position at the beginning of the animation. UIView animations do move the view to its destination position, where CALayer animations move the presentation layer while not moving the layer/view at all.)
Another subtle difference between Leo's path-based UIView animation and my answer using UIView animateKeyframes(withDuration:delay:options:animations:completion:)is that CGPath curves are cubic or quadratic Bezier curves, and my answer animates using a different kind of curve called a Katmull-Rom spline. Bezier paths start and end at their beginning and ending points, and are attracted to, but don't pass through their middle control points. Catmull-Rom splines generate a curve that passes through every one of their control points.

how to change the scale of view

I am trying to make sidebar menu like in app Euro Sport! When the menu slides from left , the sourceviewcontroller slide to left and becomes smaller.
var percentWidthOfContainer = containerView.frame.width * 0.2 // this is 20 percent of width
var widthOfMenu = containerView.frame.width - percentWidthOfContainer
bottomView.transform = self.offStage(widthOfMenu)
bottomView.frame.origin.y = 60
bottomView.frame.size = CGSizeMake(widthOfMenu, 400)
bottomView.updateConstraints()
menucontroller.view.frame.size = CGSizeMake(widthOfMenu, containerView.frame.height)
menucontroller.updateViewConstraints()
Here, the bottom view is sourceviewcontroller.view. So, the question is how to scale bottom view. In my case , i can change the size but everything inside view is still in the same size.
You can use CGAffineTransformScale to scale instance of UIView
For Instance
Suppose you have an instance of UIView as
UIView *view;
// lets say you have instantiated and customized your view
..
..
// Keep the original transform of the view in a variable as
CGAffineTransform viewsOriginalTransform = view.transform;
// to scale down the view use CGAffineTransformScale
view.transform = CGAffineTransformScale(viewsOriginalTransform, 0.5, 0.5);
// again to scale up the view
view.transform = CGAffineTransformScale(viewsOriginalTransform, 1.0, 1.0);
As per Apple doc's
The CGAffineTransform data structure represents a matrix used for
affine transformations. A transformation specifies how points in one
coordinate system map to points in another coordinate system. An
affine transformation is a special type of mapping that preserves
parallel lines in a path but does not necessarily preserve lengths or
angles. Scaling, rotation, and translation are the most commonly used
manipulations supported by affine transforms, but skewing is also
possible.
So your solution to minimize the size of bottomView :-
bottomView.transform = CGAffineTransformMakeScale(0.2, 0.2) // you can change it as per your requirement
If you want to resize it or maximize it to its original size:
bottomView.transform = CGAffineTransformMakeScale(1.0, 1.0)
Just in case you want to expand the bottom view more than its size:-
bottomView.transform = CGAffineTransformMakeScale(1.3, 1.3) // you can change it as per your requirement

CATransform3D breaks layer order?

Basically I want to build the scrollView like the one in iOS7 safari tab switcher. I use CATransform3D to get the feeling of oblique. However when I employ transform the layers just don't display in proper order.(They are in correct order before transform, seems like a total reversal in order.Back layer jumps to the front). How can I fix this thorny problem?
By the way in my case superView.bringSubViewToFront doesn't work.
My code:
//Create imageView
...
scroller.addSubview(imageView)
let layer = imageView.layer
//Set shadow
layer.shadowOpacity = 0.2
layer.shadowRadius = 2
layer.shadowOffset = CGSizeMake(6, -10)
//Transform, if without following code the layer order is correct
var transform = CATransform3DIdentity
transform.m34 = 0.0009
let radiants = 0.11*M_PI
transform = CATransform3DRotate(transform, CGFloat(radiants), 1.0, 0.0, 0.0)
scroller.bringSubviewToFront(imageView)//It doesn't work.
I just found that if you set the z axis to 1.0 in CATransform3DTranslate the layer become in front of other layers, so just make it 0.

Positioning a view after transform rotation

I'm creating a custom popover background, so I subclassed the UIPopoverBackground abstract class. While creating the layout function I came across a problem with placing and rotating the arrow for the background.
The first picture shows the arrow at the desired position. In the following code I calculated the origin I wanted but the rotation seemed to have translated the new position of the image off to the side about 11 points. As you can see, I created a hack solution where I shifted the arrow over 11 points. But that still doesn't cover up the fact that I have a gapping hole in my math skills. If someone would be so kind as to explain to me what's going on here I'd be eternally grateful. What also would be nice is a solution that would not involve magic numbers, so that I could apply this solution to the cases with the up, down and right arrow
#define ARROW_BASE_WIDTH 42.0
#define ARROW_HEIGHT 22.0
case UIPopoverArrowDirectionRight:
{
width -= ARROW_HEIGHT;
float arrowCenterY = self.frame.size.height/2 - ARROW_HEIGHT/2 + self.arrowOffset;
_arrowView.frame = CGRectMake(width,
arrowCenterY,
ARROW_BASE_WIDTH,
ARROW_HEIGHT);
rotation = CGAffineTransformMakeRotation(M_PI_2);
//rotation = CGAffineTransformTranslate(rotation, 0, 11);
_borderImageView.frame = CGRectMake(left, top, width, height);
[_arrowView setTransform:rotation];
}
break;
Well, if the rotation is applied about the center of the arrow view (as it is), that leaves a gap of (ARROW_BASE_WIDTH - ARROW_HEIGHT) / 2 to the post-rotation left of the arrow, which is what you have to compensate for, it seems. By offsetting the center of the arrow view by this much, it should come back into alignment.

Resources