Permalink
Browse files

Added CAMediaTimingFunction curve editor.

  • Loading branch information...
1 parent 1c42aca commit 7f4d5fc2d8cb0a2a504502564a06acd3458a7355 Brian Coyner committed Oct 25, 2011
@@ -11,6 +11,7 @@
5D284BA1144BB57E0000650E /* BTSSineWaveLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D284BA0144BB57E0000650E /* BTSSineWaveLayer.m */; };
5D284BA3144BB7A20000650E /* BTSSineWaveView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D284BA2144BB7A20000650E /* BTSSineWaveView.m */; };
5D284BB3144BD0BA0000650E /* BTSCoreGraphics.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D284BB2144BD0BA0000650E /* BTSCoreGraphics.m */; };
+ 5D309B451453969A00AD7EAB /* BTSMediaTimingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D309B441453969A00AD7EAB /* BTSMediaTimingViewController.m */; };
5D31F6ED143BEA0E008ECE3D /* BTSReflectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D31F6EC143BEA0E008ECE3D /* BTSReflectionViewController.m */; };
5D31F703143D1FD7008ECE3D /* BTSWiggleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D31F702143D1FD7008ECE3D /* BTSWiggleViewController.m */; };
5D422D671440D61600F85498 /* CALayer+CALayer_WiggleAnimationAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D422D661440D61600F85498 /* CALayer+CALayer_WiggleAnimationAdditions.m */; };
@@ -40,6 +41,8 @@
5D284BA2144BB7A20000650E /* BTSSineWaveView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTSSineWaveView.m; sourceTree = "<group>"; };
5D284BB1144BD0BA0000650E /* BTSCoreGraphics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTSCoreGraphics.h; sourceTree = "<group>"; };
5D284BB2144BD0BA0000650E /* BTSCoreGraphics.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTSCoreGraphics.m; sourceTree = "<group>"; };
+ 5D309B431453969A00AD7EAB /* BTSMediaTimingViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTSMediaTimingViewController.h; sourceTree = "<group>"; };
+ 5D309B441453969A00AD7EAB /* BTSMediaTimingViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTSMediaTimingViewController.m; sourceTree = "<group>"; };
5D31F6EB143BEA0E008ECE3D /* BTSReflectionViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTSReflectionViewController.h; sourceTree = "<group>"; };
5D31F6EC143BEA0E008ECE3D /* BTSReflectionViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BTSReflectionViewController.m; sourceTree = "<group>"; };
5D31F701143D1FD7008ECE3D /* BTSWiggleViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BTSWiggleViewController.h; sourceTree = "<group>"; };
@@ -152,6 +155,8 @@
5D284BA0144BB57E0000650E /* BTSSineWaveLayer.m */,
5D284BB1144BD0BA0000650E /* BTSCoreGraphics.h */,
5D284BB2144BD0BA0000650E /* BTSCoreGraphics.m */,
+ 5D309B431453969A00AD7EAB /* BTSMediaTimingViewController.h */,
+ 5D309B441453969A00AD7EAB /* BTSMediaTimingViewController.m */,
);
path = CoreAnimationFunHouse;
sourceTree = "<group>";
@@ -249,6 +254,7 @@
5D284BA1144BB57E0000650E /* BTSSineWaveLayer.m in Sources */,
5D284BA3144BB7A20000650E /* BTSSineWaveView.m in Sources */,
5D284BB3144BD0BA0000650E /* BTSCoreGraphics.m in Sources */,
+ 5D309B451453969A00AD7EAB /* BTSMediaTimingViewController.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -8,8 +8,23 @@
#import <UIKit/UIKit.h>
+@class CAMediaTimingFunction;
+
@interface BTSCubicBezierPathView : UIView
+// set to YES to anchor the beginning point to the lower left and the ending point to the upper right.
+// set to NO to allow the user to move the begin and end points.
+@property (nonatomic, assign) BOOL lockedForMediaTimingFunction;
+
+// Call to get the current bezier path.
- (CGPathRef)bezierPath;
+// Call to get the current media timing function based on the current bezier curve.
+// NOTE: this should only be called if the view is "locked for media timing function".
+- (CAMediaTimingFunction *)currentMediaTimingFunction;
+
+// Move the begin control point and end control point to the default positions based on the
+// timing function name.
+- (void)transitionToMediaTimingFunctionWithName:(NSString *)functionName;
+
@end
@@ -42,6 +42,9 @@ @interface BTSCubicBezierPathView() {
CFMutableDictionaryRef _touchesToLayers;
NSArray *_hitTestLayers;
+
+ // Used only when transitioning the control points.
+ NSTimer *_animationTimer;
}
CGPathRef BTSPathCreateForCurrentControlPointPositions(CALayer *beginPointLayer, CALayer *endPointLayer, CALayer *beginPointControlPointLayer, CALayer *endPointControlPointLayer);
@@ -52,6 +55,8 @@ - (void)initLayers;
@implementation BTSCubicBezierPathView
+@synthesize lockedForMediaTimingFunction = _lockedForMediaTimingFunction;
+
static NSString *kBTSCubicBezierPathLocationOffset = @"BTSCubicBezierPathLocationOffset";
- (id)initWithCoder:(NSCoder *)aDecoder
@@ -79,6 +84,96 @@ - (void)dealloc
}
}
+#pragma mark - Media Timing Function Support
+
+- (void)setLockedForMediaTimingFunction:(BOOL)lockedForMediaTimingFunction
+{
+ _lockedForMediaTimingFunction = lockedForMediaTimingFunction;
+
+ if (_lockedForMediaTimingFunction) {
+ [[self layer] setGeometryFlipped:YES];
+ _hitTestLayers = [NSArray arrayWithObjects:_beginPointControlPointLayer, _endPointControlPointLayer, nil];
+
+ CGRect bounds = [self bounds];
+ CGFloat segment = MIN(CGRectGetWidth(bounds), CGRectGetHeight(bounds));
+
+ [_beginPointLayer setPosition:CGPointMake(0, 0)];
+ [_endPointLayer setPosition:CGPointMake(segment, segment)];
+
+ [_beginPointControlPointLayer setPosition:[_beginPointLayer position]];
+ [_endPointControlPointLayer setPosition:[_endPointLayer position]];
+
+ CGPathRef path = BTSPathCreateForCurrentControlPointPositions(_beginPointLayer, _endPointLayer, _beginPointControlPointLayer, _endPointControlPointLayer);
+ [_shapeLayer setPath:path];
+ CFRelease(path);
+ } else {
+ _hitTestLayers = [NSArray arrayWithObjects:_beginPointControlPointLayer, _endPointControlPointLayer, _beginPointLayer, _endPointLayer, nil];
+ [[self layer] setGeometryFlipped:NO];
+ }
+}
+
+- (CAMediaTimingFunction *)currentMediaTimingFunction
+{
+ CGPoint beginPoint = [_beginPointLayer position];
+ CGPoint endPoint = [_endPointLayer position];
+ CGFloat xDistance = ABS(endPoint.x - beginPoint.x);
+ CGFloat yDistance = ABS(endPoint.y - beginPoint.y);
+
+ CGFloat cpx1 = [_beginPointControlPointLayer position].x / xDistance;
+ CGFloat cpy1 = [_beginPointControlPointLayer position].y / yDistance;
+ CGFloat cpx2 = [_endPointControlPointLayer position].x / xDistance;
+ CGFloat cpy2 = [_endPointControlPointLayer position].y / yDistance;
+
+ return [[CAMediaTimingFunction alloc] initWithControlPoints:cpx1 :cpy1: cpx2 :cpy2];
+}
+
+#pragma mark - Transition Control Points to Default Positions (Animation support)
+
+- (void)timerFired:(NSTimer *)timer
+{
+ CGPathRef path = BTSPathCreateForCurrentControlPointPositions([_beginPointLayer presentationLayer], [_endPointLayer presentationLayer], [_beginPointControlPointLayer presentationLayer], [_endPointControlPointLayer presentationLayer]);
+ [_shapeLayer setPath:path];
+ CFRelease(path);
+}
+
+- (void)animationDidStart:(CAAnimation *)anim
+{
+ _animationTimer = [NSTimer scheduledTimerWithTimeInterval:1/60 target:self selector:@selector(timerFired:) userInfo:nil repeats:YES];
+}
+
+- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
+{
+ [_animationTimer invalidate];
+ _animationTimer = nil;
+}
+
+- (void)transitionToMediaTimingFunctionWithName:(NSString *)functionName
+{
+ CGPoint beginPoint = [_beginPointLayer position];
+ CGPoint endPoint = [_endPointLayer position];
+ CGFloat xDistance = ABS(endPoint.x - beginPoint.x);
+ CGFloat yDistance = ABS(endPoint.y - beginPoint.y);
+
+ CAMediaTimingFunction *function = [CAMediaTimingFunction functionWithName:functionName];
+
+ float values[2];
+ [function getControlPointAtIndex:1 values:values];
+ [_beginPointControlPointLayer setPosition:CGPointMake(values[0] * xDistance, values[1] * yDistance)];
+
+ [function getControlPointAtIndex:2 values:values];
+ [_endPointControlPointLayer setPosition:CGPointMake(values[0] * xDistance, values[1] * yDistance)];
+
+ // A dummy animation that allows us to attach a run loop timer to animate the bezier path as the control points animate
+ // to their new locations.
+ CABasicAnimation *dummy = [CABasicAnimation animation];
+ [dummy setFromValue:[NSNumber numberWithInt:1]];
+ [dummy setToValue:[NSNumber numberWithInt:2]];
+ [dummy setDelegate:self];
+ [[self layer] addAnimation:dummy forKey:nil];
+}
+
+#pragma mark - Bezier Path
+
// Returns the current underlying shape layer's path.
- (CGPathRef)bezierPath
{
@@ -116,12 +211,17 @@ - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
// - we do this so that the layer is not implicitly animated when changing the position.
[CATransaction setDisableActions:YES];
+ CGRect bounds = [self bounds];
+ CGFloat width = bounds.size.width;
+ CGFloat height = bounds.size.height;
+
for (UITouch *currentTouch in touches) {
CALayer *layerToMove = (__bridge CALayer *)CFDictionaryGetValue(_touchesToLayers, (__bridge CFTypeRef)currentTouch);
CGPoint locationInView = [currentTouch locationInView:self];
CGPoint offsetFromCenter = [(NSValue *)[layerToMove valueForKey:kBTSCubicBezierPathLocationOffset] CGPointValue];
- CGPoint newPosition = CGPointMake(locationInView.x + offsetFromCenter.x, locationInView.y + offsetFromCenter.y);
+ CGPoint newPosition = CGPointMake(MIN(width, MAX(0, locationInView.x + offsetFromCenter.x)), MIN(height, MAX(0, locationInView.y + offsetFromCenter.y)));
+
[layerToMove setPosition:newPosition];
}
@@ -267,3 +367,5 @@ - (void)drawInContext:(CGContextRef)context
}
@end
+
+
@@ -0,0 +1,13 @@
+//
+// BTSMediaTimingViewController.h
+// CoreAnimationFunHouse
+//
+// Created by Brian Coyner on 10/22/11.
+// Copyright (c) 2011 Black Software, Inc. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface BTSMediaTimingViewController : UIViewController
+
+@end
@@ -0,0 +1,113 @@
+//
+// BTSMediaTimingViewController.m
+// CoreAnimationFunHouse
+//
+// Created by Brian Coyner on 10/22/11.
+// Copyright (c) 2011 Black Software, Inc. All rights reserved.
+//
+
+#import "BTSMediaTimingViewController.h"
+#import "BTSCubicBezierPathView.h"
+
+#import <QuartzCore/QuartzCore.h>
+
+@interface BTSMediaTimingViewController() {
+
+ __weak IBOutlet BTSCubicBezierPathView *_bezierPathView;
+ __weak IBOutlet UIView *_animationContainer;
+
+ CALayer *_animationLayer;
+
+ CALayer *_testLayer;
+}
+
+@end
+
+@implementation BTSMediaTimingViewController
+
+#pragma mark - View lifecycle
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ [_bezierPathView setLockedForMediaTimingFunction:YES];
+
+ CGFloat width = [_animationContainer bounds].size.height * .75;
+
+ _animationLayer = [CALayer layer];
+ [_animationLayer setBackgroundColor:[[UIColor redColor] colorWithAlphaComponent:0.75].CGColor];
+ [_animationLayer setBounds:CGRectMake(0, 0, width, width)];
+ [_animationLayer setPosition:CGPointMake([_bezierPathView frame].origin.x, CGRectGetMidY([_animationContainer bounds]))];
+
+ _testLayer = [CALayer layer];
+ [_testLayer setMasksToBounds:YES];
+ [_testLayer setBackgroundColor:[[UIColor blueColor] colorWithAlphaComponent:0.5].CGColor];
+ [_testLayer setBounds:CGRectMake(0, 0, width, width)];
+ [_testLayer setPosition:CGPointMake([_bezierPathView frame].origin.x, CGRectGetMidY([_animationContainer bounds]))];
+ [_testLayer setCornerRadius:[_testLayer bounds].size.width / 2];
+
+ CALayer *layer = [_animationContainer layer];
+ [layer addSublayer:_animationLayer];
+ [layer addSublayer:_testLayer];
+}
+
+- (void)viewDidUnload
+{
+ _bezierPathView = nil;
+ _animationContainer = nil;
+
+ [super viewDidUnload];
+}
+
+- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
+{
+ return interfaceOrientation == UIInterfaceOrientationPortrait;
+}
+
+- (IBAction)animate:(id)sender {
+
+ CGPoint newPosition = CGPointMake(290, [_animationLayer position].y);
+ [_animationLayer setPosition:newPosition];
+ [_testLayer setPosition:newPosition];
+
+ CAMediaTimingFunction *timingFunction = [_bezierPathView currentMediaTimingFunction];
+
+ CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
+ [animation setTimingFunction:timingFunction];
+ [animation setDuration:2.0];
+ [animation setFromValue:[NSValue valueWithCGPoint:CGPointMake([_bezierPathView frame].origin.x, CGRectGetMidY([_animationContainer bounds]))]];
+ [animation setToValue:[NSValue valueWithCGPoint:newPosition]];
+
+ [_animationLayer addAnimation:animation forKey:@"position"];
+
+
+ [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
+ [_testLayer addAnimation:animation forKey:@"position"];
+}
+
+- (IBAction)makeLinear:(id)sender {
+ [_bezierPathView transitionToMediaTimingFunctionWithName:kCAMediaTimingFunctionLinear];
+}
+
+- (IBAction)makeEaseIn:(id)sender {
+ [_bezierPathView transitionToMediaTimingFunctionWithName:kCAMediaTimingFunctionEaseIn];
+}
+
+
+- (IBAction)makeEaseOut:(id)sender {
+ [_bezierPathView transitionToMediaTimingFunctionWithName:kCAMediaTimingFunctionEaseOut];
+}
+
+- (IBAction)makeEaseInEaseOut:(id)sender {
+ [_bezierPathView transitionToMediaTimingFunctionWithName:kCAMediaTimingFunctionEaseInEaseOut];
+}
+
+
+
+
+
+
+
+
+@end
Oops, something went wrong.

0 comments on commit 7f4d5fc

Please sign in to comment.