I would like to animate a straight line curving into a bezier curve (from "_" to "n"), is there a library somewhere that can help me to do it ?
I know how to draw a Bezier curve with UIBezierPath, I could redraw rapidly and progressively do the transformation, but if something already does that it would be cool :-)
I might do something with a CADisplayLink
. For example, you could do this in your view controller using a CAShapeLayer
, e.g.:
#import "ViewController.h"
#import <QuartzCore/QuartzCore.h>
@interface ViewController ()
@property (nonatomic) CFTimeInterval firstTimestamp;
@property (nonatomic, strong) CAShapeLayer *shapeLayer;
@property (nonatomic, strong) CADisplayLink *displayLink;
@property (nonatomic) NSUInteger loopCount;
@end
static CGFloat const kSeconds = 5.0;
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self addShapeLayer];
[self startDisplayLink];
}
- (void)addShapeLayer
{
self.shapeLayer = [CAShapeLayer layer];
self.shapeLayer.path = [[self pathAtInterval:0.0] CGPath];
self.shapeLayer.fillColor = [[UIColor clearColor] CGColor];
self.shapeLayer.lineWidth = 3.0;
self.shapeLayer.strokeColor = [[UIColor redColor] CGColor];
[self.view.layer addSublayer:self.shapeLayer];
}
- (void)startDisplayLink
{
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)stopDisplayLink
{
[self.displayLink invalidate];
self.displayLink = nil;
}
- (void)handleDisplayLink:(CADisplayLink *)displayLink
{
if (!self.firstTimestamp)
self.firstTimestamp = displayLink.timestamp;
self.loopCount++;
NSTimeInterval elapsed = (displayLink.timestamp - self.firstTimestamp);
self.shapeLayer.path = [[self pathAtInterval:elapsed] CGPath];
if (elapsed >= kSeconds)
{
[self stopDisplayLink];
self.shapeLayer.path = [[self pathAtInterval:0] CGPath];
self.statusLabel.text = [NSString stringWithFormat:@"loopCount = %.1f frames/sec", self.loopCount / kSeconds];
}
}
- (UIBezierPath *)pathAtInterval:(NSTimeInterval) interval
{
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, self.view.bounds.size.height / 2.0)];
CGFloat fractionOfSecond = interval - floor(interval);
CGFloat yOffset = self.view.bounds.size.height * sin(fractionOfSecond * M_PI * 2.0);
[path addCurveToPoint:CGPointMake(self.view.bounds.size.width, self.view.bounds.size.height / 2.0)
controlPoint1:CGPointMake(self.view.bounds.size.width / 2.0, self.view.bounds.size.height / 2.0 - yOffset)
controlPoint2:CGPointMake(self.view.bounds.size.width / 2.0, self.view.bounds.size.height / 2.0 + yOffset)];
return path;
}
@end
Alternatively, if you wanted to do it by subclassing UIView
, you could do it like so:
#import "View.h"
#import <QuartzCore/QuartzCore.h>
@interface View ()
@property (nonatomic, strong) CADisplayLink *displayLink;
@property (nonatomic) CFTimeInterval firstTimestamp;
@property (nonatomic) CFTimeInterval displayLinkTimestamp;
@property (nonatomic) NSUInteger loopCount;
@end
static CGFloat const kSeconds = 5.25;
@implementation View
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self startDisplayLink];
}
return self;
}
- (void)startDisplayLink
{
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)stopDisplayLink
{
[self.displayLink invalidate];
self.displayLink = nil;
}
- (void)handleDisplayLink:(CADisplayLink *)displayLink
{
if (!self.firstTimestamp)
self.firstTimestamp = displayLink.timestamp;
self.displayLinkTimestamp = displayLink.timestamp;
self.loopCount++;
[self setNeedsDisplayInRect:self.bounds];
NSTimeInterval elapsed = (displayLink.timestamp - self.firstTimestamp);
if (elapsed >= kSeconds)
{
[self stopDisplayLink];
self.displayLinkTimestamp = self.firstTimestamp + kSeconds;
[self setNeedsDisplayInRect:self.bounds];
self.statusLabel.text = [NSString stringWithFormat:@"loopCount = %.1f frames/sec", self.loopCount / kSeconds];
}
}
- (UIBezierPath *)pathAtInterval:(NSTimeInterval) interval
{
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, self.bounds.size.height / 2.0)];
CGFloat fractionOfSecond = interval - floor(interval);
CGFloat yOffset = self.bounds.size.height * sin(fractionOfSecond * M_PI * 2.0);
[path addCurveToPoint:CGPointMake(self.bounds.size.width, self.bounds.size.height / 2.0)
controlPoint1:CGPointMake(self.bounds.size.width / 2.0, self.bounds.size.height / 2.0 - yOffset)
controlPoint2:CGPointMake(self.bounds.size.width / 2.0, self.bounds.size.height / 2.0 + yOffset)];
return path;
}
- (void)drawRect:(CGRect)rect
{
NSTimeInterval elapsed = (self.displayLinkTimestamp - self.firstTimestamp);
UIBezierPath *path = [self pathAtInterval:elapsed];
[[UIColor redColor] setStroke];
path.lineWidth = 3.0;
[path stroke];
}
@end
I test both the subclassed UIView
as well as the view controller, and they both resulted in roughly 60 frames per second.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With