iOS Animated Bezier/Sine Curve - ios

I am looking to animate a single-line bezier curve on a loop in iOS. The idea I have in my head resembles the Voice Control screen on the iPhone 4 before Siri. The curve does not need to react to anything ie. Audio, mic etc. It just needs to loop from screen left to screen right, and change the amplitude of the curve.
I have tried a couple tests and this is the closest I have come:
IOS : Animate transformation from a line to a bezier curve
I need to know how to animated the actual curve to appear as if it is moving, not just up and down.
If any one has some light to shed on this, that would be awesome!
Thanks!

Wow, I worked on the exact same thing today. :)
Check this :
So the view where I draw my waves, is initialized as :
_self_view = [[TDTWaveView alloc] initWithFrame:CGRectMake(-320, 174, 640, 200)];
Then in my viewDidLoad, I call [self animateWave]; once.
- (void)animateWave {
[UIView animateWithDuration:.5 delay:0.0 options:UIViewAnimationOptionRepeat|UIViewAnimationOptionCurveLinear animations:^{
_self_view.transform = CGAffineTransformMakeTranslation(+_self_view.frame.size.width/2, 0);
} completion:^(BOOL finished) {
_self_view.transform = CGAffineTransformMakeTranslation(0, 0);
}];
}
This gives the wave a sort of linear motion you might want.
As far as the code for the wave goes, I'll share the drawrect.
self.yc = 30//The height of a crest.
float w = 0;//starting x value.
float y = rect.size.height;
float width = rect.size.width;
int cycles = 7;//number of waves
self.x = width/cycles;
CGContextRef context = UIGraphicsGetCurrentContext();
CGMutablePathRef path = CGPathCreateMutable();
CGContextSetLineWidth(context, .5);
while (w <= width) {
CGPathMoveToPoint(path, NULL, w,y/2);
CGPathAddQuadCurveToPoint(path, NULL, w+self.x/4, y/2 - self.yc, w+self.x/2, y/2);
CGPathAddQuadCurveToPoint(path, NULL, w+3*self.x/4, y/2 + self.yc, w+self.x, y/2);
w+=self.x;
}
CGContextAddPath(context, path);
CGContextDrawPath(context, kCGPathStroke);

Related

Scale circle uivew just big enough to fill screen

I would like to automate the scale factor of many uiviews that have cornerRadius set so that they look like circles. The idea is that these 20 circle uiviews, of varying sizes and location, will scale up to fill the screen no matter their size, location etc when tapped.
My code has a hard coded value that I would like to make intelligent:
[UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.layer.affineTransform = CGAffineTransformMakeScale(15.0f, 15.0f);
} completion:^(BOOL finished) {
}];
Yes - I could just type in 20 for CGAffineTransformMakeScale but then the bigger circles are scaling larger for no reason - they might need to scale 11 instead.
What would be the way to factor the original size to a fixed, much larger size?
Note: The circle uiview, being rounded, should scale big enough that the rounded edges are not visible - it needs to scale beyond the screen to fill in those gaps at the corners.
Conceptually, you want to center the circle in the screen, then scale it so that the screen rect is inscribed in the circle. Here's a handy diagram:
From the center of the screen, we can draw a triangle to the corner. The hypotenuse of that triangle is your desired circle radius. Here's the code to transform your circle:
[UIView animateWithDuration:0.2 animations:^{
CGPoint screenCenter = CGPointMake(CGRectGetMidX([UIScreen mainScreen].bounds), CGRectGetMidY([UIScreen mainScreen].bounds));
CGPoint circleCenter = [self.window convertPoint:self.center fromView:self.superview];
// Calculate distance from circle's current center to the screen center
UIOffset offset = UIOffsetMake(screenCenter.x - circleCenter.x, screenCenter.y - circleCenter.y);
// Calculate distance from screen center to corner
CGFloat screenRadius = sqrtf(powf(CGRectGetWidth([UIScreen mainScreen].bounds) * 0.5, 2.0f)
+ powf(CGRectGetHeight([UIScreen mainScreen].bounds) * 0.5, 2.0f));
CGFloat circleRadius = CGRectGetWidth(self.bounds) * 0.5;
CGFloat scale = screenRadius / circleRadius;
// Combine scale and translation
CGAffineTransform scaleTransform = CGAffineTransformMakeScale(scale, scale);
CGAffineTransform translateTransform = CGAffineTransformMakeTranslation(offset.horizontal, offset.vertical);
self.transform = CGAffineTransformConcat(scaleTransform, translateTransform);
}];

iPhone: Animate circle with UIKit

I am using a CircleView class that basically inherits off UIView and implements drawRect to draw a circle. This all works, hurrah!
What I cannot figure out though is how to make it so when I touch it (touch code is implemented) the circle grows or pops. Normally I'd use the UIKit animation framework to do this but given I am basically overriding the drawRect function to directly draw the circle. So how do I animate this?
- (void)drawRect:(CGRect)rect{
CGContextRef context= UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, _Color.CGColor);
CGContextFillEllipseInRect(context, CGRectMake(0, 0, self.frame.size.width, self.frame.size.height));
}
- (void)handleSingleTap:(UITapGestureRecognizer *)recognizer {
// Animate?
}
The answers depends on what you mean by "grows or pops". When I hear "pop" I assume that the view scales up over a short period of time ans scales back down again to the original size. Something that "grows" on the other hand would scale up but not down again.
For something that scales up and down again over a short period of time I would use a transform to scale it. Custom drawing or not, UIView has build in support for animating a simple transform. If this is what you are looking for then it's not more then a few lines of code.
[UIView animateWithDuration:0.3
delay:0.0
options:UIViewAnimationOptionAutoreverse // reverse back to original value
animations:^{
// scale up 10%
yourCircleView.transform = CGAffineTransformMakeScale(1.1, 1.1);
} completion:^(BOOL finished) {
// restore the non-scaled state
yourCircleView.transform = CGAffineTransformIdentity;
}];
If on the other hand you want the circle to grow a little bit more on every tap then this won't do it for you since the view is going to look pixelated when it scales up. Making custom animations can be tricky so I would still advice you to use a scaling transform for the actual animation and then redraw the view after the animation.
[UIView animateWithDuration:0.3
animations:^{
// scale up 10%
yourCircleView.transform = CGAffineTransformMakeScale(1.1, 1.1);
} completion:^(BOOL finished) {
// restore the non-scaled state
yourCircleView.transform = CGAffineTransformIdentity;
// redraw with new value
yourCircleView.radius = theBiggerRadius;
}];
If you really, really want to do a completely custom animation then I would recommend that you watch Rob Napiers talk on Animating Custom Layer Properties, his example is even the exact thing you are doing (growing a circle).
If you want an animation that expands the ellipse from the centre, try this. In the header, define 3 variables:
BOOL _isAnimating;
float _time;
CGRect _ellipseFrame;
Then implement these 3 methods in the .m file:
- (void)drawRect:(CGRect)rect; {
[super drawRect:rect];
CGContextRef context= UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, _Color.CGColor);
CGContextFillEllipseInRect(context, _ellipseFrame);
}
- (void)expandOutward; {
if(_isAnimating){
_time += 1.0f / 30.0f;
if(_time >= 1.0f){
_ellipseFrame = self.frame;
_isAnimating = NO;
}
else{
_ellipseFrame = CGRectMake(0.0f, 0.0f, self.frame.size.width * _time, self.frame.size.height * _time);
_ellipseFrame.center = CGPointMake(self.frame.size.width / 2.0f, self.frame.size.height / 2.0f);
[self setNeedsDisplay];
[self performSelector:#selector(expandOutward) withObject:nil afterDelay:(1.0f / 30.0f)];
}
}
}
- (void)handleSingleTap:(UITapGestureRecognizer *)recognizer; {
if(_isAnimating == NO){
_time = 0.0f;
_isAnimating = YES;
[self expandOutward];
}
}
This is the most basic way you can animate the circle expanding from the centre. Look into CADisplayLink for a constant sync to the screen if you want more detailed animations. Hope that Helps!

Smooth scaling of vector graphics in UIView

So I have subclassed UIView and added some drawing code. I am scaling the resulting view up and down.
I would like this view to be resolution independent so that it is legible at any size, and I won't need to manage multiple images etc. etc.
As a test I made up a bit of drawing code that looks like this.
It creates concentric ovals that fit within whatever frame size the UIView has.
Actually the outside ring is a little smaller than the frame so it isn't clipped. Fine for this. The actual graphic will be more complex and will contain text which must be readable at small sizes and things of that nature.
- (void)drawRect:(CGRect)rect
{
UIColor* color = [UIColor colorWithRed: 0.833 green: 0.833 blue: 0.833 alpha: 1];
float width = self.bounds.size.width;
float height = self.bounds.size.height;
float scalePercent = 0.8;
for(int i = 0; i < 10; i++){
width = width * scalePercent;
height = height * scalePercent;
float x = (self.bounds.size.width - width) / 2;
float y = (self.bounds.size.height - height) / 2;
UIBezierPath* ovalPath = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(x, y, width, height)];
[color setStroke];
ovalPath.lineWidth = 2;
[ovalPath stroke];
}
}
Now here's the scaling:
- (void) makeBig{
[UIView animateWithDuration:0.5
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:(void (^)(void)) ^{
self.transform = CGAffineTransformMakeScale(2, 2);
}
completion:^(BOOL finished){
}];
}
When you run this the view zooms up, but it is pixelated. It's pixelated because the view has doubled in size but it's resolution has not changed.
So, here's how not to do it.
- (void) makeBig{
[UIView animateWithDuration:0.5
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:(void (^)(void)) ^{
self.transform = CGAffineTransformMakeScale(2, 2);
}
completion:^(BOOL finished){
CGRect targetFrame = self.frame;
self.transform = CGAffineTransformIdentity;
self.frame = targetFrame;
[self setNeedsDisplay];
}];
}
This works, but the fix is visible at the end of the animation when the resolution snaps back to screen resolution.
I could try pre-scaling the view up and pre-drawing at the final size then scaling it down and then running the animation to scale it back up again, but for various reasons that I can think of that sounds totally stupid. I suppose I could be wrong and it's the brightest idea since fire. I kind of doubt it though.
So how is a smooth scale of vector content done best?
View-based animation is really handy, but if I'm not mistaken it uses CABasicAnimation, which only uses a single keyframe. It'll be a little more code, but if you use a CAKeyframeAnimation instead, Core Animation will redraw the contents of the animated layer for each keyframe (you get to specify when those occur), so you can avoid the appearance of pixelation.

Draw many lines with Performance

Hey i need a help with building an app. I build an art app, this app is working with interferencies. Thats the reason i need to draw many lines in this app. More lines are better for the interefernces. I think the problem is the iPad can't handle too many lines, because the speed or the performance is too slow.
I don't know how can i speed up my code for more performance on the iPad. Should i use Open GL or something else...
What can i do?
Here are the Draw.m
#import "Draw.h"
#implementation Draw
- (IBAction) sliderValueChanged:(UISlider *)sender {
label.text = [NSString stringWithFormat:#"%f", slider.value];
//NSLog(#"slider value = %f", sender.value);
[self setNeedsDisplay];
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
//NSLog(#"slider value = %f", self.bounds.size.width);
CGMutablePathRef cgpath = CGPathCreateMutable();
CGPathMoveToPoint(cgpath, NULL, 0, 500);
CGMutablePathRef cgpath2 = CGPathCreateMutable();
CGPathMoveToPoint(cgpath2, NULL, 0, 500);
UIBezierPath *uipath = [[UIBezierPath alloc] init];
[uipath moveToPoint:CGPointMake(0, 0)];
int step = 5;
int iterations = self.bounds.size.width/step;
for (int i = 0; i < iterations+1; i++){
//CGPathAddCurveToPoint(cgpath, NULL, 1+i, 0, 1+i, 0, 1+i ,0);
CGPathAddLineToPoint ( cgpath, NULL, 0, 0 );
CGPathAddLineToPoint ( cgpath, NULL, 0, 768 );
CGPathAddLineToPoint ( cgpath, NULL, step*i-slider.value*2, 768 );
CGPathAddLineToPoint ( cgpath, NULL, step*i, 0 );
CGPathAddLineToPoint ( cgpath, NULL, (step*i)+step, 0 );
[[UIColor blackColor] setStroke];
CGContextAddPath(ctx, cgpath);
[self strokeUIBezierPath:uipath];
CGPathRelease(cgpath);
}
- (void)strokeContext:(CGContextRef)context
{
CGContextStrokePath(context);
}
- (void)strokeUIBezierPath:(UIBezierPath*)path
{
[path stroke];
}
#end
image http://img17.imageshack.us/img17/375/53178410200339197475308.jpg
the problem with bezier paths is, that they can be quite 'calculation heavy'.
You could either use straight lines ( CGContextAddLineToPoint(context, point.x, point.y);)
Or you use graphics acceleration.
You could either dive directly into OpenGL or use a game engine to help you with some of the code.
One of the most popular ones ( and as I think quite easy to use ) is cocos2d.
You should be getting better performance using a pattern to fill the screen. There is an entire section with sample code in the Quartz Programming Guide.
In your case you could create a very small pattern cell (height = 1) with just one black pixel to the very left followed by the same number of white pixels as the distance to the next line.

How to animate UIImageViews like hatch doors opening

I'm trying to create an animation that would look like 2 french doors (or 2 hatch doors) opening towards the user.
I tried using the built in UIViewAnimationOptionTransitionFlipFromRight transition, but the origin of the transition seems to be the center of the UIImageView rather than the left edge. Basically I have 2 UIImageViews that each fill have the screen. I would like the animation to look like the UIImageViews are lifting from the center of the screen to the edges.
[UIView transitionWithView:leftView
duration:1.0
options:UIViewAnimationOptionTransitionFlipFromRight
animations:^ { leftView.alpha = 0; }
completion:^(BOOL finished) {
[leftView removeFromSuperview];
}];
Has anyone done something like this before? Any help would be awesome!
UPDATE:
Working code thanks to Nick Lockwood
leftView.layer.anchorPoint = CGPointMake(0, 0.5); // hinge around the left edge
leftView.frame = CGRectMake(0, 0, 160, 460); //reset view position
rightView.layer.anchorPoint = CGPointMake(1.0, 0.5); //hinge around the right edge
rightView.frame = CGRectMake(160, 0, 160, 460); //reset view position
[UIView animateWithDuration:0.75 animations:^{
CATransform3D leftTransform = CATransform3DIdentity;
leftTransform.m34 = -1.0f/500; //dark magic to set the 3D perspective
leftTransform = CATransform3DRotate(leftTransform, -M_PI_2, 0, 1, 0);
leftView.layer.transform = leftTransform;
CATransform3D rightTransform = CATransform3DIdentity;
rightTransform.m34 = -1.0f/500; //dark magic to set the 3D perspective
rightTransform = CATransform3DRotate(rightTransform, M_PI_2, 0, 1, 0);
rightView.layer.transform = rightTransform;
}];
First add the QuartzCore library to your project and #import <QuartzCore/QuartzCore.h>
Every view has a layer property with sub-properties that are animatable. This is where you'll find all the really cool stuff when it comes to animation capabilities (I suggest reading up on the CALayer class properties you can set - it will blow your mind - dynamic soft drop shadows on any view?)
Anyway, back on topic. To rotate your doors open in 3D, first position them as if they were closed, so with each door filling half the screen.
Now set their view.layer.anchorPoint properties as follows
leftDoorView.layer.anchorPoint = CGPoint(0, 0.5); // hinge around the left edge
rightDoorView.layer.anchorPoint = CGPoint(1.0, 0.5); // hinge around the right edge
Now apply the following animation
[UIView animateWithDuration:0.5 animations:^{
CATransform3D leftTransform = CATransform3DIdentity;
leftTransform.m34 = -1.0f/500; //dark magic to set the 3D perspective
leftTransform = CATransform3DRotate(leftTransform, M_PI_2, 0, 1, 0); //rotate 90 degrees about the Y axis
leftDoorView.layer.transform = leftTransform;
//do the same thing but mirrored for the right door, that probably just means using -M_PI_2 for the angle. If you don't know what PI is, Google "radians"
}];
And that should do it.
DISCLAIMER: I've not actually tested this, so the angles may be backwards, and the perspective may be screwy, etc. but it should be a good start at least.
UPDATE: Curiosity got the better of me. Here is fully working code (this assumes that the left and right doors are laid out in the closed position in the nib file):
- (void)viewDidLoad
{
[super viewDidLoad];
leftDoorView.layer.anchorPoint = CGPointMake(0, 0.5); // hinge around the left edge
leftDoorView.center = CGPointMake(0.0, self.view.bounds.size.height/2.0); //compensate for anchor offset
rightDoorView.layer.anchorPoint = CGPointMake(1.0, 0.5); // hinge around the right edge
rightDoorView.center = CGPointMake(self.view.bounds.size.width,self.view.bounds.size.height/2.0); //compensate for anchor offset
}
- (IBAction)open
{
CATransform3D transform = CATransform3DIdentity;
transform.m34 = -1.0f/500;
leftDoorView.layer.transform = transform;
rightDoorView.layer.transform = transform;
[UIView animateWithDuration:0.5 animations:^{
leftDoorView.layer.transform = CATransform3DRotate(transform, M_PI_2, 0, 1, 0);
rightDoorView.layer.transform = CATransform3DRotate(transform, -M_PI_2, 0, 1, 0);
}];
}
- (IBAction)close
{
[UIView animateWithDuration:0.5 animations:^{
CATransform3D transform = CATransform3DIdentity;
transform.m34 = -1.0f/500;
leftDoorView.layer.transform = transform;
rightDoorView.layer.transform = transform;
}];
}

Resources