How to create custom easing function with Core Animation? - iphone

I am animating a CALayer along a CGPath (QuadCurve) quite nicely in iOS. But I'd like to use a more interesting easing function than the few provided by Apple (EaseIn/EaseOut etc). For instance, a bounce or elastic function.
These things are possible to do with MediaTimingFunction (bezier):
But I'd like to create timing functions that are more complex. Problem is that media timing seems to require a cubic bezier which is not powerful enough to create these effects:
(source: sparrow-framework.org)
The code to create the above is simple enough in other frameworks, which makes this very frustrating. Note that the curves are mapping input time to output time (T-t curve) and not time-position curves. For instance, easeOutBounce(T) = t returns a new t. Then that t is used to plot the movement (or whatever property we should animate).
So, I'd like to create a complex custom CAMediaTimingFunction but I have no clue how to do that, or if it's even possible? Are there any alternatives?
EDIT:
Here is a concrete example in to steps. Very educational :)
I want to animate an object along a line from point a to b, but I want it to "bounce" its movement along the line using the easeOutBounce curve above. This means it will follow the exact line from a to b, but will accelerate and decelerate in a more complex way than what is possible using the current bezier-based CAMediaTimingFunction.
Lets make that line any arbitrary curve movement specified with CGPath. It should still move along that curve, but it should accelerate and decelerate the same way as in the line example.
In theory I think it should work like this:
Lets describe the movement curve as a keyframe animation move(t) = p, where t is time [0..1], p is position calculated at time t. So move(0) returns the position at the start of curve, move(0.5) the exact middle and move(1) at end. Using a an timing function time(T) = t to provide the t values for move should give me what I want. For a bouncing effect, the timing function should return the same t values for time(0.8) and time(0.8) (just an example). Just replace the timing function to get a different effect.
(Yes, it's possible to do line-bouncing by creating and joining four line segments which goes back and forth, but that shouldn't be necessary. After all, it's just a simple linear function which maps time values to positions.)
I hope I'm making sense here.

I found this:
Cocoa with Love - Parametric acceleration curves in Core Animation
But I think it can be made a little simpler and more readable by using blocks. So we can define a category on CAKeyframeAnimation that looks something like this:
CAKeyframeAnimation+Parametric.h:
// this should be a function that takes a time value between
// 0.0 and 1.0 (where 0.0 is the beginning of the animation
// and 1.0 is the end) and returns a scale factor where 0.0
// would produce the starting value and 1.0 would produce the
// ending value
typedef double (^KeyframeParametricBlock)(double);
#interface CAKeyframeAnimation (Parametric)
+ (id)animationWithKeyPath:(NSString *)path
function:(KeyframeParametricBlock)block
fromValue:(double)fromValue
toValue:(double)toValue;
CAKeyframeAnimation+Parametric.m:
#implementation CAKeyframeAnimation (Parametric)
+ (id)animationWithKeyPath:(NSString *)path
function:(KeyframeParametricBlock)block
fromValue:(double)fromValue
toValue:(double)toValue {
// get a keyframe animation to set up
CAKeyframeAnimation *animation =
[CAKeyframeAnimation animationWithKeyPath:path];
// break the time into steps
// (the more steps, the smoother the animation)
NSUInteger steps = 100;
NSMutableArray *values = [NSMutableArray arrayWithCapacity:steps];
double time = 0.0;
double timeStep = 1.0 / (double)(steps - 1);
for(NSUInteger i = 0; i < steps; i++) {
double value = fromValue + (block(time) * (toValue - fromValue));
[values addObject:[NSNumber numberWithDouble:value]];
time += timeStep;
}
// we want linear animation between keyframes, with equal time steps
animation.calculationMode = kCAAnimationLinear;
// set keyframes and we're done
[animation setValues:values];
return(animation);
}
#end
Now usage will look something like this:
// define a parametric function
KeyframeParametricBlock function = ^double(double time) {
return(1.0 - pow((1.0 - time), 2.0));
};
if (layer) {
[CATransaction begin];
[CATransaction
setValue:[NSNumber numberWithFloat:2.5]
forKey:kCATransactionAnimationDuration];
// make an animation
CAAnimation *drop = [CAKeyframeAnimation
animationWithKeyPath:#"position.y"
function:function fromValue:30.0 toValue:450.0];
// use it
[layer addAnimation:drop forKey:#"position"];
[CATransaction commit];
}
I know it might not be quite as simple as what you wanted, but it's a start.

From iOS 10 it became possible to create custom timing function easier using two new timing objects.
1) UICubicTimingParameters allows to define cubic Bézier curve as an easing function.
let cubicTimingParameters = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.25, y: 0.1), controlPoint2: CGPoint(x: 0.25, y: 1))
let animator = UIViewPropertyAnimator(duration: 0.3, timingParameters: cubicTimingParameters)
or simply using control points on animator initialization
let controlPoint1 = CGPoint(x: 0.25, y: 0.1)
let controlPoint2 = CGPoint(x: 0.25, y: 1)
let animator = UIViewPropertyAnimator(duration: 0.3, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
This awesome service is going to help to choose control points for your curves.
2) UISpringTimingParameters lets developers manipulate damping ratio, mass, stiffness, and initial velocity to create desired spring behavior.
let velocity = CGVector(dx: 1, dy: 0)
let springParameters = UISpringTimingParameters(mass: 1.8, stiffness: 330, damping: 33, initialVelocity: velocity)
let springAnimator = UIViewPropertyAnimator(duration: 0.0, timingParameters: springParameters)
Duration parameter is still presented in Animator, but will be ignored for spring timing.
If these two options are not enough you also can implement your own timing curve by confirming to the UITimingCurveProvider protocol.
More details, how to create animations with different timing parameters, you can find in the documentation.
Also, please, see Advances in UIKit Animations and Transitions presentation from WWDC 2016.

A way to create a custom timing function is by using the functionWithControlPoints:::: factory method in CAMediaTimingFunction (there is a corresponding initWithControlPoints:::: init method as well). What this does is create a Bézier curve for your timing function. It is not an arbitrary curve, but Bézier curves are very powerful and flexible. It takes a little practice to get the hang of the control points. A tip: most drawing programs can create Bézier curves. Playing with those will give you a visual feedback on the curve you are representing with the control points.
The this link points to apple's documentation. There is a short but useful section on how the pre-build functions are constructed from curves.
Edit:
The following code shows a simple bounce animation. For doing so, I created a composed timing function (values and timing NSArray properties) and gave each segment of the animation a different time length (keytimes property). In this way you can compose Bézier curves to compose more sophisticated timing for animations. This is a good article on this type of animations with a nice sample code.
- (void)viewDidLoad {
UIView *v = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 50.0, 50.0)];
v.backgroundColor = [UIColor redColor];
CGFloat y = self.view.bounds.size.height;
v.center = CGPointMake(self.view.bounds.size.width/2.0, 50.0/2.0);
[self.view addSubview:v];
//[CATransaction begin];
CAKeyframeAnimation * animation;
animation = [CAKeyframeAnimation animationWithKeyPath:#"position.y"];
animation.duration = 3.0;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
NSMutableArray *values = [NSMutableArray array];
NSMutableArray *timings = [NSMutableArray array];
NSMutableArray *keytimes = [NSMutableArray array];
//Start
[values addObject:[NSNumber numberWithFloat:25.0]];
[timings addObject:GetTiming(kCAMediaTimingFunctionEaseIn)];
[keytimes addObject:[NSNumber numberWithFloat:0.0]];
//Drop down
[values addObject:[NSNumber numberWithFloat:y]];
[timings addObject:GetTiming(kCAMediaTimingFunctionEaseOut)];
[keytimes addObject:[NSNumber numberWithFloat:0.6]];
// bounce up
[values addObject:[NSNumber numberWithFloat:0.7 * y]];
[timings addObject:GetTiming(kCAMediaTimingFunctionEaseIn)];
[keytimes addObject:[NSNumber numberWithFloat:0.8]];
// fihish down
[values addObject:[NSNumber numberWithFloat:y]];
[keytimes addObject:[NSNumber numberWithFloat:1.0]];
//[timings addObject:GetTiming(kCAMediaTimingFunctionEaseIn)];
animation.values = values;
animation.timingFunctions = timings;
animation.keyTimes = keytimes;
[v.layer addAnimation:animation forKey:nil];
//[CATransaction commit];
}

Not sure if you're still looking, but PRTween looks fairly impressive in terms of its ability to go beyond what Core Animation gives you out of the box, most notably, custom timing functions. It also comes packaged with many—if not all—of the popular easing curves that various web frameworks provide.

A swift version implementation is TFAnimation.The demo is a sin curve animation.Use TFBasicAnimation just like CABasicAnimation except assign timeFunction with a block other than timingFunction.
The key point is subclass CAKeyframeAnimation and calculate frames position by timeFunction in 1 / 60fps s interval .After all add all the calculated value to values of CAKeyframeAnimation and the times by interval to keyTimes too.

I created a blocks based approach, that generates an animation group, with multiple animations.
Each animation, per property, can use 1 of 33 different parametric curves, a Decay timing function with initial velocity, or a custom spring configured to your needs.
Once the group is generated, it's cached on the View, and can be triggered using an AnimationKey, with or without the animation. Once triggered the animation is synchronized accordingly the presentation layer's values, and applied accordingly.
The framework can be found here FlightAnimator
Here is an example below:
struct AnimationKeys {
static let StageOneAnimationKey = "StageOneAnimationKey"
static let StageTwoAnimationKey = "StageTwoAnimationKey"
}
...
view.registerAnimation(forKey: AnimationKeys.StageOneAnimationKey, maker: { (maker) in
maker.animateBounds(toValue: newBounds,
duration: 0.5,
easingFunction: .EaseOutCubic)
maker.animatePosition(toValue: newPosition,
duration: 0.5,
easingFunction: .EaseOutCubic)
maker.triggerTimedAnimation(forKey: AnimationKeys.StageTwoAnimationKey,
onView: self.secondaryView,
atProgress: 0.5,
maker: { (makerStageTwo) in
makerStageTwo.animateBounds(withDuration: 0.5,
easingFunction: .EaseOutCubic,
toValue: newSecondaryBounds)
makerStageTwo.animatePosition(withDuration: 0.5,
easingFunction: .EaseOutCubic,
toValue: newSecondaryCenter)
})
})
To trigger the animation
view.applyAnimation(forKey: AnimationKeys.StageOneAnimationKey)

Related

Stop CABasicAnimation at specific point

I'm using a rotation animation created with CABasicAnimation. It rotates a UIView over 2 seconds. But I need to be able to stop it when the UIView is touched. If I remove the animation the view is in the same position as before the animation started.
Here's my animation code:
float duration = 2.0;
float rotationAngle = rotationDirection * ang * speed * duration;
//rotationAngle = 3*(2*M_PI);//(double)rotationAngle % (double)(2*M_PI) ;
CABasicAnimation* rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotationAnimation.toValue = [NSNumber numberWithFloat: rotationAngle ];
rotationAnimation.duration = duration;
rotationAnimation.cumulative = YES;
rotationAnimation.removedOnCompletion = NO;
rotationAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
rotationAnimation.fillMode = kCAFillModeForwards;
rotationAnimation.delegate = self;
[self.view.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
How can I stop the UIView's rotation right where it is, when it's touched? I know how to manage the touch part, but I can't figure out how to stop the view at the animation's current angle.
Solution:
I solved the problem by getting the angle of the presentation layer, removing the animation and setting the view's transform. Here's the code:
[self.view.layer removeAllAnimations];
CALayer* presentLayer = self.view.layer.presentationLayer;
float currentAngle = [(NSNumber *)[presentLayer valueForKeyPath:#"transform.rotation.z"] floatValue];
self.view.transform = CGAffineTransformMakeRotation(currentAngle);
Good question! For this, it's helpful to know the Core Animation architecture.
If you check out the diagram in the Core Animation Programming Guide that describes the Core Animation Rendering Architecture, you can see that there's three trees.
You have the model tree. That's where you set the values of what you want to happen. Then there's the presentation tree. That's what is pretty much happening as far as the runtime is concerned. Then, finally is the render tree. That's what the user sees.
In your case, you want to query the values of the presentation tree.
It's easy to do. For the view that you have attached the animation, get the layer and for that layer, query the presentationLayer's values. For example:
CATransform3D myTransform = [(CALayer*)[self.view.layer presentationLayer] transform];
There's no way to "pause" an animation mid flow. All you can do is query the values, remove it, and then re-create it again from where you left off.
It's a bit of a pain!
Have a look at some of my other posts where I go into this in a bit more detail, e.g.
Restoring animation where it left off when app resumes from background
Don't forget also that when you add an animation to a view's layer, you aren't actually changing the underlying view's properties. So what happens? We'll you get weird effects where the animation stops and you see the view in it's original position.
That's where you need to use the CAAnimation delegates. Have a look at my answer to this post where I cover this:
CABasicAnimation rotate returns to original position
You need to set the rotation to the rotation of the presentationLayer and then remove the animation from the layer. You can read about the presentation layer in my blog post about Hit testing animating layers.
The code to set the final rotation would be something like:
self.view.layer.transform = [(CALayer*)[self.view.layer presentationLayer] transform];
[self.view.layer removeAnimationForKey:#"rotationAnimation"];

IOS: move an UIImageView

In my app I want to move a little UIImageView with inside a .png; this is a little insect and I want to simulate his flight. At example I want that this png do when it move an inverted eight as the infinite simbol ∞
You may use CoreAnimation. You can subclass a view, create a subview for the insect, and then assign an animation to it, following a defined path.
Your UIImageView could be animated. If it's a fly, you can do a few frames for wing moves:
NSArray *images = [NSArray arrayWithObjects:..., nil];
insect.animationImages = images;
insect.animationDuration = ??;
insect.animationRepeatCount = 0;
[insect startAnimating];
Then set an init frame for the insect:
insect.frame = CGRectMake(-120, 310, [[images objectAtIndex:0] size].width, [[images objectAtIndex:0] size].height);
And then define the path:
CGMutablePathRef aPath;
CGFloat arcTop = insect.center.y - 50;
aPath = CGPathCreateMutable();
CGPathMoveToPoint(aPath, NULL, insect.center.x, insect.center.y);
CGPathAddCurveToPoint(aPath, NULL, insect.center.x, arcTop, 240, -100, 490, 360);
CAKeyframeAnimation* arcAnimation = [CAKeyframeAnimation animationWithKeyPath: #"position"];
arcAnimation.repeatCount = HUGE_VALF;
[arcAnimation setDuration: 4.5];
[arcAnimation setAutoreverses: NO];
arcAnimation.removedOnCompletion = NO;
arcAnimation.fillMode = kCAFillModeBoth;
[arcAnimation setPath: aPath];
CFRelease(aPath);
[insect.layer addAnimation: arcAnimation forKey: #"position"];
I leave how to do the infinite loop path up to you :)
Hope it helps!
Normally, if you were to be moving things around, I'd suggest using [UIView animate...]. However, you want something to move on a complex, curvy path. So instead, I'd suggest coming up with an equation that gives the (x,y) for the insect as a function of time, and then start an NSTimer with a fairly small time interval, and every time you get an update, move the insect (perhaps using [UIView animate...]).
Another way to go is to use a 2-d animation framework such as cocos2d - then, you can get an 'update' call linked to the frame refresh rate, inside of which you update the position of your insect using the same equation as from above.

iphone - create animation like user location Blue marble drop

Do you know how to create an animation like the Blue Marble drop User-Location in MKMapView?
Although I am not sure on the specifics of how Apple accomplished this effect, this feels to me like a great opportunity to use CoreAnimation and custom animatable properties. This post provides some nice background on the subject. I assume by the "Blue Marble drop" animation you're referring to the following sequence:
Large light blue circle zooms into frame
Large light blue circle oscillates between two relatively large radii as location is
calculated
Large light blue circle zooms into small darker blue circle on the user's location
Although this may be simplifying the process slightly, I think it's a good place to start and more complex/detailed functionality can be added with relative ease (i.e. the small dark circle pulsing as larger circle converges on it.)
The first thing we need is a custom CALayer subclass with a custom property for our outer large light blue circles radius:
#import <QuartzCore/QuartzCore.h>
#interface CustomLayer : CALayer
#property (nonatomic, assign) CGFloat circleRadius;
#end
and the implementation:
#import "CustomLayer.h"
#implementation CustomLayer
#dynamic circleRadius; // Linked post tells us to let CA implement our accessors for us.
// Whether this is necessary or not is unclear to me and one
// commenter on the linked post claims success only when using
// #synthesize for the animatable property.
+ (BOOL)needsDisplayForKey:(NSString*)key {
// Let our layer know it has to redraw when circleRadius is changed
if ([key isEqualToString:#"circleRadius"]) {
return YES;
} else {
return [super needsDisplayForKey:key];
}
}
- (void)drawInContext:(CGContextRef)ctx {
// This call is probably unnecessary as super's implementation does nothing
[super drawInContext:ctx];
CGRect rect = CGContextGetClipBoundingBox(ctx);
// Fill the circle with a light blue
CGContextSetRGBFillColor(ctx, 0, 0, 255, 0.1);
// Stoke a dark blue border
CGContextSetRGBStrokeColor(ctx, 0, 0, 255, 0.5);
// Construct a CGMutablePath to draw the light blue circle
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddArc(path, NULL, rect.size.width / 2,
rect.size.height / 2,
self.circleRadius, 0, 2 * M_PI, NO);
// Fill the circle
CGContextAddPath(ctx, path);
CGContextFillPath(ctx);
// Stroke the circle's border
CGContextAddPath(ctx, path);
CGContextStrokePath(ctx);
// Release the path
CGPathRelease(path);
// Set a dark blue color for the small inner circle
CGContextSetRGBFillColor(ctx, 0, 0, 255, 1.0f);
// Draw the center dot
CGContextBeginPath (ctx);
CGContextAddArc(ctx, rect.size.width / 2,
rect.size.height / 2,
5, 0, 2 * M_PI, NO);
CGContextFillPath(ctx);
CGContextStrokePath(ctx);
}
#end
With this infrastructure in place, we can now animate the radius of the outer circle with ease b/c CoreAnimation will take care of the value interpolations as well as redraw calls. All we have to do his add an animation to the layer. As a simple proof of concept, I chose a simple CAKeyframeAnimation to go through the 3 stage animation:
// In some controller class...
- (void)addLayerAndAnimate {
CustomLayer *customLayer = [[CustomLayer alloc] init];
// Make layer big enough for the initial radius
// EDIT: You may want to shrink the layer when it reacehes it's final size
[customLayer setFrame:CGRectMake(0, 0, 205, 205)];
[self.view.layer addSublayer:customLayer];
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"circleRadius"];
// Zoom in, oscillate a couple times, zoom in further
animation.values = [NSArray arrayWithObjects:[NSNumber numberWithFloat:100],
[NSNumber numberWithFloat:45],
[NSNumber numberWithFloat:50],
[NSNumber numberWithFloat:45],
[NSNumber numberWithFloat:50],
[NSNumber numberWithFloat:45],
[NSNumber numberWithFloat:20],
nil];
// We want the radii to be 20 in the end
customLayer.circleRadius = 20;
// Rather arbitrary values. I thought the cubic pacing w/ a 2.5 second pacing
// looked decent enough but you'd probably want to play with them to get a more
// accurate imitation of the Maps app. You could also define a keyTimes array for
// a more discrete control of the times per step.
animation.duration = 2.5;
animation.calculationMode = kCAAnimationCubicPaced;
[customLayer addAnimation:animation forKey:nil];
}
The above is a rather "hacky" proof of concept as I am not sure of the specific way in which you intend to use this effect. For example, if you wanted to oscillate the circle until data was ready, the above wouldn't make a lot of sense because it will always oscillate twice.
Some closing notes:
Again, I am not sure of your intent for this effect. If, for
example, you're adding it to an MKMapView, the above may require
some tweaking to integrate with MapKit.
The linked post suggests the above method requires the version of CoreAnimation in iOS 3.0+ and OS X 10.6+
Speaking of the linked post (as I did often), much credit and thanks to Ole Begemann who wrote it and did a wonderful job explaining custom properties in CoreAnimation.
EDIT: Also, for performance reasons, you're probably going to want to make sure the layer is only as big as it needs to be. That is, after your done animating from the larger size, you may want to scale the size down so you're only using/drawing as much room as necessary. A nice way to do this would be just to find a way animate the bounds (as opposed to circleRadius) and perform this animation based the size interpolation but I've had some trouble implementing that (perhaps someone could add some insight on that subject).
Hope this helps,
Sam
Add this to your map object:
myMap.showsUserLocation = TRUE;

Non-Smooth animation with Core Animation

This is a weird request, but I'm using Core Animation (CALayers), and I want my animation to be choppy and non-smooth. I want an image I set up to rotate like a second hand on a clock. Here's my code:
UIImage *arrowImage = [UIImage imageNamed:#"arrow.jpg"];
CALayer *arrow = [CALayer layer];
arrow.contents = (id)arrowImage.CGImage;
arrow.bounds = CGRectMake(0, 0, 169.25, 45.25);
arrow.position = CGPointMake(self.view.bounds.size.width / 2, arrowImage.size.height / 2);
arrow.anchorPoint = CGPointMake(0.0, 0.5);
[self.view.layer addSublayer:arrow];
CABasicAnimation *anim1 = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
anim1.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
anim1.fromValue = [NSNumber numberWithFloat:0];
anim1.toValue = [NSNumber numberWithFloat:((360*M_PI)/180)];
anim1.duration = 4.0;
[arrow addAnimation:anim1 forKey:#"transform"];
It produces a gliding motion, which I don't want. How do I get around this?
Any help is appreciated.
If you want it to be really choppy, don't use Core Animation at all. On the other hand, if you want something somewhere in between those two extremes, don't use linear media timing. Instead, you might want to try kCAMediaTimingFunctionEaseIn so that the animation accelerates slightly as the hand moves.
The simple way to do this would be to simply apply a transform to your view. The second hand would snap from one position to the next. Just change the rotation by 360/60 = 6 degrees for each second.
If you want the second-hand to do an animation for each tick, you could use a very fast UIView block-based animation. (say with a 1/15 second duration or so.)
Take a look at the UIView class methods who's names start with animateWithDuration.
Something like this:
- (void) moveSecondHand;
{
seconds++;
angle = M_PI*2*seconds/60 - M_PI/2;
CGAffineTransform transform = CGAffineTransformMakeRotation(angle);
[UIView animateWithDuration: 1.0/15
animations: *{
secondHand.transform = transform
}];
}
That's about all it would take. You're trigger that code with a timer once a second. By default animations use ease-in, ease-out timing, which models physical movement pretty well. Try different durations, but 1/15 is probably a good starting point (you want it fast, but not too fast to see.)
If you want a wobble to your animation you will need to get much fancier, and create an animation group that first moves it by the full amount, and then does a repeating animation that overshoots the stopping point by a small amount and then goes back.

Curving/warping views with CoreAnimation or OpenGL for carousel effect

Right now I'm populating a UIScrollView with a series of views. The views need to be warped to make the UIScrollView appear like a carousel. In other words when the user scrolls it needs to be like a circle. I've never done anything quite like this before, but I'm assuming CoreAnimation is out of the question and OpenGL needs to be used. If this is possible with CoreAnimation or Quartz then I really just need a sample on how to warp the views and I can figure the rest out myself but I'm not familiar with OpenGL.
If you want to warp the views, you'll either need OpenGL or you could use Core Animation's CAShapLayer which allows you to specify a bezier path which can have this curve in it. But keep in mind that this curving you're seeing is likely just an optical illusion (though in your image above it looks like an actual curve). If you get enough rectangles with the correct y axis rotation in a row, I think you can come up with the effect you're looking for with straight Core Animation. I'm pretty sure that's how things are implemented in the Core Animation demos Apple provided a couple years ago. Here's a screenshot from the video from that presentation:
I messed around with the transform of a view's layer a little bit and came up with this:
- (IBAction)sliderDidChange:(id)sender
{
CGFloat value = [(UISlider*)sender value];
CGFloat xOff = value - 0.5;
CATransform3D trans = CATransform3DIdentity;
trans.m34 = 1.0f / -1000.0f;
trans = CATransform3DRotate(trans, degreesToRadians(xOff * -25.0f), 0.0f, 1.0f, 0.0f);
trans = CATransform3DTranslate(trans, 0.0f, 0.0f, 900.0f * fabs(xOff));
[[frameView layer] setTransform:trans];
CGPoint center= [frameView center];
[frameView setCenter:CGPointMake(1024.0 * value, center.y)];
}
I threw together a demo project that shows how the rotation works in response to a slider. It doesn't use a scroll view so you would have to adapt it, but I think you can track the current scroll offset and apply the transform accordingly. Not sure if it will help but there it is.
In my experience, it is a bit tricky to get the values right. The way to give a view perspective is by manipulating it's layer transform. I have used the following method to achieve the transfor for a similar effect:
-(CATransform3D)makeTransformForAngle:(CGFloat)angle from:(CATransform3D)start{
CATransform3D transform = start;
// the following two lines are the key to achieve the perspective effect
CATransform3D persp = CATransform3DIdentity;
persp.m34 = 1.0 / -1000;
transform = CATransform3DConcat(transform, persp);
transform = CATransform3DRotate(transform,angle, 0.0, 1.0, 0.0);
return transform;
}
This was done to create a "flip page" animation, so you may need to adapt. To use it, do the following:
flip_page.layer.transform = [self makeTransformForAngle:angle from:CATransform3DIdentity];
where flip_page is a UIView. Cheers!