Bees animate from left to right along bezier path
Tap to toggle visible flight paths
iPad landscape screenshots
Key Tech Features
Core Graphics Layers
Scalable Bezier Paths
Core Animation
CAShapeLayer * visibleFlightPath1; //ivars
CAShapeLayer * visibleFlightPath2;
-(void)setup
{
CALayer * b1 = getBumblebee();
CALayer * b2 = getBumblebee();
CGPathRef fp1 = getFlightPath(0, 1.0f, 1.0f);
CGPathRef fp2 = getFlightPath(80, 3.0f, 0.8f);
CAKeyframeAnimation * a1 = getAnimationWithPath(fp1, 3.3, 0);
[b1 addAnimation:a1 forKey:nil];
[self.view.layer addSublayer:b1];
CAKeyframeAnimation * a2 = getAnimationWithPath(fp2, 3.9, 0);
[b2 addAnimation:a2 forKey:nil];
[self.view.layer addSublayer:b2];
visibleFlightPath1 = getVisibleFlightPath(fp1);
visibleFlightPath2 = getVisibleFlightPath(fp2);
[self.view.layer addSublayer:visibleFlightPath1];
[self.view.layer addSublayer:visibleFlightPath2];
}
CAGradientLayer * getBumblebee()
{
CAGradientLayer * b = [CAGradientLayer layer];
b.frame = CGRectMake(0, 0, 25, 25);
b.borderColor = [UIColor darkGrayColor].CGColor;
b.borderWidth = 0.5f;
b.cornerRadius = 10.0f;
b.masksToBounds = YES;
CGColorRef black = [UIColor blackColor].CGColor;
CGColorRef yellow = [UIColor yellowColor].CGColor;
b.colors = @[ (__bridge id)black,
(__bridge id)yellow,
(__bridge id)black,
(__bridge id)yellow ];
//vertical gradient
b.startPoint = CGPointMake(0.0f, 0.5f);
b.endPoint = CGPointMake(1.0f, 0.5f);
b.locations = @[ [NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:0.33],
[NSNumber numberWithFloat:0.5],
[NSNumber numberWithFloat:0.9],
[NSNumber numberWithFloat:1.0] ];
return b;
}
CGPathRef getFlightPath(const int yOffset, const CGFloat factor1, const CGFloat factor2)
{
const CGPoint startPoint = CGPointMake(20, (200 + (yOffset * factor1)));
const CGFloat normalFlightPoints[] = { 220, 200,
420, 200,
620, 200,
820, 200 };
UIBezierPath * fp = [UIBezierPath bezierPath];
[fp moveToPoint:startPoint];
//add normal flight points
const int count = sizeof(normalFlightPoints)/sizeof(CGFloat);
const int cpOffset = 100;
const CGFloat cpYOffsetFactor = (cpOffset * factor1);
for (int i = 0, s = 0; i < count; i++, s++)
{
const CGFloat x = normalFlightPoints[i];
const CGFloat y = normalFlightPoints[i + 1] + (yOffset * factor1);
const CGPoint p = CGPointMake(x, y);
const CGFloat cpYHighLowOffset = ((s % 2) ? cpYOffsetFactor : -cpYOffsetFactor);
const CGPoint cp = CGPointMake((p.x - cpOffset), (p.y + cpYHighLowOffset));
[fp addQuadCurveToPoint:p controlPoint:cp]; //connect flight points
i += 1;
}
//add pseudo random flight points
const CGFloat w = (yOffset * factor1) * factor2;
[fp addQuadCurveToPoint:CGPointMake(700, 175 + w) controlPoint:CGPointMake(850, 150 + w)];
[fp addQuadCurveToPoint:CGPointMake(900, 200 + w) controlPoint:CGPointMake(550, 200 + w)];
const CGFloat randomYOffset = (arc4random_uniform(2) == 0 ? -cpOffset : cpOffset);
[fp addCurveToPoint:CGPointMake(1200, fp.currentPoint.y + randomYOffset)
controlPoint1:CGPointMake(1000, fp.currentPoint.y)
controlPoint2:CGPointMake(1100, fp.currentPoint.y)];
return fp.CGPath;
}
CAKeyframeAnimation * getAnimationWithPath(CGPathRef aPath, CFTimeInterval aDuration, CFTimeInterval aTimeOffset)
{
CAKeyframeAnimation * a = [CAKeyframeAnimation animation];
a.keyPath = @"position";
a.path = aPath;
a.duration = aDuration;
a.timeOffset = aTimeOffset;
a.repeatCount = HUGE_VALF;
a.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
return a;
}
CAShapeLayer * getVisibleFlightPath(CGPathRef aPath)
{
CAShapeLayer * fp = [CAShapeLayer layer];
fp.path = aPath;
fp.fillColor = [UIColor clearColor].CGColor;
fp.strokeColor = [UIColor darkGrayColor].CGColor;
fp.lineWidth = 1.0f;
fp.zPosition = -1.0f;
return fp;
}
//tap to toggle visible flight paths
-(void)tap:(UIGestureRecognizer*)sender
{
visibleFlightPath1.hidden = !visibleFlightPath1.hidden;
visibleFlightPath2.hidden = !visibleFlightPath2.hidden;
}
-(void)viewDidLoad
{
[super viewDidLoad];
UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
[self.view addGestureRecognizer:tap];
[self setup];
}