How to animate layer distance from viewer - user - camera change with perspective? - ios

I'm trying to make an animation very similar to the one at the start of Air bnb iOS app.
Here's a video of the animation : video
The idea is to simulate a layer flying from being very close to the user to end sticking on a far away surface.
I've read some articles talking about manipulating the layer.transform.m34 and the one that helped me more is this one.
By applying perspective and a translation on the z-axis, I managed to get the layer look bigger.
Here's the code I used :
CALayer *aLayer = [CALayer layer];
aLayer.frame = ...
aLayer.backgroundColor = ...
CATransform3D perspectiveTransform = CATransform3DIdentity;
perspectiveTransform.m34 = 1.0f/-250.0f;
perspectiveTransform.m44 = 0.0f;
perspectiveTransform = CATransform3DTranslate(perspectiveTransform, 0.0f, 0.0f. -100.0f);
aLayer.transform = perspectiveTransform;
The problem is I can't get it to animate back to CATransform3DIdentity .
I'm not used to CoreAnimation so I may be trying a bad approach.
It would help a lot if someone could point me to what I'm doing wrong or to a better solution.
Thanks in advance!

You need to first create your layer and add it to the layer tree. Once the layer is part of the layer tree then implicit animations should work.
I think you may need to do this:
Create layer
Add layer to parent layer
run remaining animation code with performSelector:withObject:afterDelay: so system gets a chance to add the layer to the layer before your code to do implicit animations is run.

Related

How to render same UIView content (a camera preview and layers) into two UIViews side by side for VR Headset in Swift

I found the question:
How to show side by side two previews of camera in iOS objective-C for vr app?
But there is no answer.
And, I am looking for the solution in Swift 3.
I also need render same extra layers in both preview.
Thus, my question is how to mirror UIView side by side to play camera preview or other incoming video stream? I don't need stereoscopic vision in this stage. I just need show same content side by side. Something like:
https://itunes.apple.com/ca/app/3d-fpv-stereoscopic-3d-vr/id1008155528?mt=8
I found the answer! The answer is CAReplicatorLayer
Multiple-Camera-Feeds (It is not swift, but can be re-factorised in Swift very very easily)
use a CAReplicatorLayer to duplicate the layer automatically. As the docs say, it will automatically create "...a specified number of copies of its sublayers (the source layer), each copy potentially having geometric, temporal and color transformations applied to it."
This is super useful if there isn't a lot of interaction with the live previews besides simple geometric or color transformations (Think Photo Booth). I have most often seen the CAReplicatorLayer used as a way to create the 'reflection' effect.
Here is some sample code to replicate a CACaptureVideoPreviewLayer:
Init AVCaptureVideoPreviewLayer
AVCaptureVideoPreviewLayer *previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
[previewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
[previewLayer setFrame:CGRectMake(0.0, 0.0, self.view.bounds.size.width, self.view.bounds.size.height / 4)];
Init CAReplicatorLayer and set properties
Note: This will replicate the live preview layer *four** times.*
NSUInteger replicatorInstances = 4;
CAReplicatorLayer *replicatorLayer = [CAReplicatorLayer layer];
replicatorLayer.frame = CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height / replicatorInstances);
replicatorLayer.instanceCount = instances;
replicatorLayer.instanceTransform = CATransform3DMakeTranslation(0.0, self.view.bounds.size.height / replicatorInstances, 0.0);
Add Layers
Note: From my experience you need to add the layer you want to replicate to the CAReplicatorLayer as a sublayer.
[replicatorLayer addSublayer:previewLayer];
[self.view.layer addSublayer:replicatorLayer];
Downsides
A downside to using CAReplicatorLayer is that it handles all placement of the layer replications. So it will apply any set transformations to each instance and and it will all be contained within itself. E.g. There would be no way to have a replication of a AVCaptureVideoPreviewLayer on two separate cells.

Creating animated image masking

I have a circular UIView. I want to be able to apply a mask that sits on top of it, and rotates above it so that the circular view appears gradually.
Is this even possible in iOS? I know it is in flast. But not sure where would I even start with a task like this in iOS??
You can create a shape layer like in this answer that will look as if it is appearing gradually (like a clock making a full circle). Then you can put the circular content in another layer (or use your view's layer) and make the shape layer the mask:
CAShapeLayer *maskLayer = // see linked answer
yourCircularView.layer.mask = maskLayer;
CABasicAnimation *drawMaskAnimation = // see linked answer
[maskLayer addAnimation:drawMaskAnimation forKey:#"appear gradually"];];
All UIView is backed by CALayer which allows some of the properties to be animated as well as mask to be applied. You will find the tutorial in this page very helpful. www.raywenderlich.com

Distorting UIImage into Parallelogram

I have a UIImage that results from a function. It's something like the following.
image1.image = [self reflectedImage:img];
where image1 is a UIImage control. Before plugging it into image1, I want to further distort it to the right or left a little into a parallelogram as shown below. So I just want to relocate point c and d.
I've read several dozen articles here and there. I'm not sure if I can do it with CGAffineTransformMake. This web page suggests that I could. Unfortunately, there is no sample project to see how it works. Unfortunately, I haven't found a single web site that shows me how to distort a simple rectangle image into a parallelogram. So what is the easiest way of doing it? Do I need to create a layer so that I can use CATransform3D?
Thank you for your help.
I found out an alternative way which is way simpler than the other in this topic. Just apply this transformation to your view:
imgView.transform = CGAffineTransformMake(1.0, 0.0, proportion, 1.0, 0.0, 0.0);
where proportion is between -1 and 1
This is called a shear transformation, look at the link below for more
http://iphonedevelopment.blogspot.it/2008/10/cgaffinetransform-11-little-more.html
What you have to do is give your UIImageView some perspective. By default a layers transform do not have perspective, so you must also setup this: transform.m34 = 1.0 / -2000;
Also change the anchorpoint of your view so that it rotates along the top edge of the view. For you it becomes (0,0.5).
The perspective gives it that parallelogram look as if the view has depth (its also called 2.5D as its not pure 3D animation, its pseudo 3D). Set the anchor point so that it rotates along that edge and finally give it an angle. i.e. rotate by how much?
// Rotate by 30 degrees
CGAffineTransform rotationTransform = CGAffineTransformIdentity;
rotationTransform = CGAffineTransformRotate(rotationTransform, DegreesToRadians(30));
swingView.transform = rotationTransform;
all of this will give you what you want...

UIView animations on a path not linear [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How can I animate the movement of a view or image along a curved path?
I have an iOS application that I want to animate a falling leaf (or several). I have my leaf image in an ImageView and I've figured out a simple animation from the documentation:
[UIView animateWithDuration:4.0f
delay:0
options:UIViewAnimationTransitionFlipFromLeft
animations:^(void)
{
leaf1ImageView.frame = CGRectMake(320, 480, leaf1ImageView.frame.size.width,leaf1ImageView.frame.size.height);
}
completion:NULL];
This will make the leaf go from its starting position to the bottom right corner in a straight line. How would I animate this to follow a path or curve like a parabola or sinusoid and maybe even rotate the image or view? Would this be done in the animations block? Thanks in advance!
You can do this with a CAKeyframeAnimation and a CGPath. The code looks something like this:
CAKeyframeAnimation *leafAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
leafAnimation.duration = 10.0;
leafAnimation.path = /* your CGPathRef */;
[leaf1ImageView.layer addAnimation:leafAnimation forKey:#"leafAnimation"];
The thing you are looking for is animating along a Bezier CGPath. For more details see this question:
How can I animate the movement of a view or image along a curved path?
Creating bezier paths in code is rather hard though, for easier ways to create a bezier curve to animate along look through the answers to the question here:
Drawing bezier curves with my finger in iOS?

Why is renderInContext so much slower than drawing to the screen?

I have a pretty large CAShapeLayer that I'm rendering. The layer is completely static, but it's contained in a UIScrollView so it can move around and be zoomed -- basically, it must be redrawn every now and then. In an attempt to improve the framerate of this scrolling, I set shouldRasterize = YES on the layer, which worked perfectly. Because I never change any property of the layer it never has a rasterization miss and I get a solid 60 fps. High fives all around, right?
Until the layer gets a little bigger. Eventually -- and it doesn't take long -- the rasterized image gets too large for the GPU to handle. According to my console, <Notice>: CoreAnimation: surface 2560 x 4288 is too large, and it just doesn't draw anything on the screen. I don't really blame it -- 2560 x 4288 is pretty big -- but I spent a while scratching my head before I noticed this in the device console.
Now, my question is: how can I work around this limitation? How can I rasterize a really large layer?
The obvious solution seems to be to break the layer up into multiple sublayers, say one for each quadrant, and rasterize each one independently. Is there an "easy" way to do this? Can I create a new layer that renders a rectangular area from another layer? Or is there some other solution I should explore?
Edit
Creating a tiled composite seems to have really bad performance because the layers are re-rasterized every time they enter the screen, creating for a very jerky scrolling experience. Is there some way to cache those rasterizations? Or is this the wrong approach altogether?
Edit
Alright, here's my current solution: render the layer once to a CGImageRef. Create multiple tile layers using sub-rectangles from that image, and actually put those on the screen.
- (CALayer *)getTiledLayerFromLayer:(CALayer *)sourceLayer withHorizontalTiles:(int)horizontalTiles verticalTiles:(int)verticalTiles
{
CALayer *containerLayer = [CALayer layer];
CGFloat tileWidth = sourceLayer.bounds.size.width / horizontalTiles;
CGFloat tileHeight = sourceLayer.bounds.size.height / verticalTiles;
// make sure these are integral, otherwise you'll have image alignment issues!
NSLog(#"tileWidth:%f height:%f", tileWidth, tileHeight);
UIGraphicsBeginImageContextWithOptions(sourceLayer.bounds.size, NO, 0);
CGContextRef tileContext = UIGraphicsGetCurrentContext();
[sourceLayer renderInContext:tileContext];
CGImageRef image = CGBitmapContextCreateImage(tileContext);
UIGraphicsEndImageContext();
for(int horizontalIndex = 0; horizontalIndex < horizontalTiles; horizontalIndex++) {
for(int verticalIndex = 0; verticalIndex < verticalTiles; verticalIndex++) {
CGRect frame = CGRectMake(horizontalIndex * tileWidth, verticalIndex * tileHeight, tileWidth, tileHeight);
CGRect visibleRect = CGRectMake(horizontalIndex / (CGFloat)horizontalTiles, verticalIndex / (CGFloat)verticalTiles, 1.0f / horizontalTiles, 1.0f / verticalTiles);
CALayer *tile = [CALayer layer];
tile.frame = frame;
tile.contents = (__bridge id)image;
tile.contentsRect = visibleRect;
[containerLayer addSublayer:tile];
}
}
CGImageRelease(image);
return containerLayer;
}
This works great...sort of. One the one hand, I get 60fps panning and zooming of a 1980 x 3330 layer on a retina iPad. On the other hand, it takes 20 seconds to start up! So while this solution solves my original problem, it gives me a new one: how can I generate the tiles faster?
Literally all of the time is spent in the [sourceLayer renderInContext:tileContext]; call. This seems weird to me, because if I just add that layer directly I can render it about 40 times per second, according to the Core Animation Instrument. Is it possible that creating my own image context causes it to not use the GPU or something?
Breaking the layer into tiles is the only solution. You can however implement it in many different ways. I suggest doing it manually (creating layers & sublayers on your own), but many recommend using CATiledLayer http://www.mlsite.net/blog/?p=1857, which is the way maps are usually implemented - zooming and rotating is quite easy with this one. The tiles of CATiledLayer are loaded (drawn) on demand, just after they are put on the screen. This implies a short delay (blink) before the tile is fully drawn and AFAIK it is quite hard to get rid of this behaviour.

Resources