Rotating UIButtons on a view - iphone

I have 5 buttons on a UIView that I would like to plot evenly along a circular path, and have them rotate continuously along that path. To be clear, I don't want to rotate the buttons themselves, I just want them to move along the circular path.
What is the best way to manage this?

Create a UIBezierPath or CGPath for each button, adjusting the starting angle and ending angle to account for the initial starting points of each. Then for each button/path create a CAKeyframeAnimation, give it the path, set it to autorepeat and add it to the appropriate button's layer.
The UIBezierPath method +bezierPathWithArcCenter:radius:startAngle:endAngle:clockwise: will help you create the circular paths. You'll need to calculate the correct angles, but that's just a small bit of math.

There's no reason to animate each button individually along a path. You said that you have 5 buttons on a UIView. Just rotate this view you've added them to and all other buttons will rotate providing the same effect as what you're asking for with a path.
The trick is you will need an explicit animation rather than the UIView animation shortcuts:
CABasicAnimation* rotationAnimation =
[CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
[rotationAnimation setToValue:[NSNumber numberWithFloat:DegreesToRadians(360)]];
// Make a full rotation in five seconds
[rotationAnimation setDuration:5.0];
// Repeat forever.
[rotationAnimation setRepeatCount:HUGE_VALF];
// Make the animation timing linear
[rotationAnimation setTimingFunction:
[CAMediaTimingFunction
functionWithName:kCAMediaTimingFunctionLinear]];
[[rotationView layer] addAnimation:rotationAnimation forKey:nil];
The variable rotationView is the view that contains all of the buttons. At this point all you really need to do is calculate where your buttons should be positioned initially. Everything else is handled for you.

Related

struggle to understand toValue, byValue of CABasicAnimation, ios

I am learning a different way to create a custom indicator. Below is a partial code from tutorial using CABasicAnimation to achieve a task.
-(void)spin
{
CABasicAnimation *spinAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
spinAnimation.toValue = [NSNumber numberWithFloat:2*M_PI];
spinAnimation.duration = self.animationDuration;
spinAnimation.delegate = self;
[self.layer addAnimation:spinAnimation forKey:#"spinAnimation"];
}
What is the toValue at line number 2 and what it is used for. When I tried to use
spinAnimation.byValue = [NSNumber numberWithFloat:2*M_PI];
I dont get the idea of these interpolation values.Was searching over the internet but cant still get the whole picture of it..
Please help if you have any ideas about it. All comments are appreciated.
CABasicAnimations can be a little tough to wrap your head around, but the properties associated with the animation really aren't that tough, once you can visualize what they're trying to accomplish. For instance, if I have a red square that represents a layer, and I want to rotate it 360˚ (as you're doing there), then I have to initialize an animation object, tell it what I want to animate, and where I want the animation to go.
The animation you've provided mutates a CALayer's internal matrix so that it is rotated to a given value (in this case, 2 * M_PI, or 360˚) from it's current position (because you haven't specified a fromValue) over the given duration. A given by value tells the animation that over the given period of time, you want the animation to interpolate (or move) by the given value for the provided duration (for instance, you could chunk the animation into 45˚ "blocks" by specifying a byValue of #(M_PI/2)). The default byValue is a division of the difference of the toValue and fromValue over the duration of the animation such that the animation is smooth and continuous.
So, you can think about the animation as going from the layer's initial rotational value, to the layer's new rotational value, by interpolating a given amount or value for a period of time.
You can comprehend "byValue" its means plus the value in original.

iPhone animation based on input values (touches) not time

For an animation effect perfectly suited to an animation group approach as shown in Brad Larson's answer here, I need the animation to proceed according to inputs. Specifically touch and position of detected touches. It is easy to handle touchesMoved: and to set the position of the elements for every touch but it just isn't smooth like the core animation approach.
Imagine a marble in a grooved track. I want to push the marble along to any position at any speed in one direction or the other. The animation has to do something like that, moving a visual element along a path in response to touches. CAKeyframeAnimation has the path bit exactly but seems to always want to base the transition from frame to frame on time elapsed, not on any other factor, and in one direction.
31 January update - Thanks all for the responses so far however none is really solving the problem. I have a circular menu that is dragged to select an option. All of it needs to move together and I have worked around it by using an view that has a rotational transform applied and the inverse rotational transform applied to its subviews so the icons all rotate with appropriate ferris wheel orientation. It really looks better when the icons are animated along a slightly ovoid path though... the marble description is an attempt to make clear what I'm trying to do. Better perhaps to imagine magnets oriented to repel all travelling in a groove - move one and its neighbours move too but not necessarily in the direction that the dragged magnet moves as the path curves.
Right now the problem is one of following a simple path created with one circle but I'd really like to know how to animate objects along an arbitrary path, position controlled purely by touch with no calculations involving velocity or direction.
You may be able to use the hierarchical nature of timelines in layer trees to achieve what you’re looking for. Objects implementing CAMediaTiming, which include both CAAnimation and CALayer, inherit a timespace from their parent, which they can modify (via scaling, shifting and repeating) and propagate to their children. By setting the speed property of a layer to 0.0 and adjusting the timeOffset property manually, you can decouple that layer (and all of its sublayers) from the usual notion of time.
What you’d do in your case is define the animations for all your menu items to animate their position along your desired CGPath from time t0 to t1, with the appropriate timeOffset on each animation to keep your items appropriately spaced. Note that a beginTime of 0.0 on an animation is usually interpreted as starting from the time the animation was added to its layer, so if you want t0 to be 0.0, you'll probably have to set it to a tiny epsilon > 0.0.
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
animation.beginTime = 1e-100;
animation.duration = 1.0;
animation.fillMode = kCAFillModeBoth;
animation.removedOnCompletion = NO;
animation.path = path;
animation.calculationMode = kCAAnimationPaced;
animation.timeOffset = timeOffset;
You’d then set the speed property to 0.0 on a parent layer (containing only these menu items) and update its timeOffset to values between t0 and t1 in response to your touch events.
This approach has two potential caveats. Because you’ve taken over the nature of time on this layer subtree, you probably won’t be able to animate other properties at the same time. Additionally, if you want coasting behavior for a fast flick, you’ll probably need to animate time forward on your own.
If you are targeting iOS 4.0 or greater then you can use the new block based class methods to start animatable changes to view objects. So from your touch event you can initiate an animatable change to properties on the view:
[UIView animateWithDuration:1.0 animations:^{
yourView.alpha = 0.0; // fade out yourView over 1 second
}];
N.B. This could just as easily be a change to another property on the view, like its location. You can animate the following properties on the view this way:
#property frame
#property bounds
#property center
#property transform
#property alpha
#property backgroundColor
#property contentStretch
If you are targetting earlier versions of iOS you will need to use UIView beginAnimations and commitAnimations methods to create an animation block:
[UIView beginAnimations:nil context:context];
[UIView setAnimationDuration:1.0];
yourView.alpha = 0.0;
[UIView commitAnimations];
This stuff works really well and once you start using it, you will have to be careful you don't get addicted. ;)
Update for comment:
You can bind the position of the marble to the location of the touch event. Once you get the touchesEnded event you can then animate the location of the marble using an animation block.
velocity = distance/time. You can try by giving delay according to touches moved and time. You can calculate time between touchesBegan and touchesEnded methods.
I'm not entirely certain I understand exactly what you want to do but... Why not just draw the marble wherever the user's finger is? No animation required.
If you want to keep the marble moving after the user lets go, you need to maintain a concept of momentum and use that to animate the marble slowing down. You could use either CoreAnimation for that or a timer.
I would make a velocity calculation in touches moved, and add it to a local variable that a block can mutate via a timer.
in other words, in touches moved make a velocity calculation bearing in mind the direction of a previous velocity. In touches ended, fire a block that translates the 'marble' decaying the velocity as you disire. If the user moves the marble again, modify the local variable, this will in turn speed up the animation of the marble or slow it down/change direction depending on on the direction of the touch.

iOS - Animating text size change in UILabel or UITextView?

In an application showing chunks of text, I'm having the font size increase when the device is turned to a landscape orientation. I don't like how it does the whole animation and then suddenly jumps to the new size, so I'd like to animate the size change over the course of the rotation.
I read somewhere that throwing this change in a UIView animation block doesn't work because the font property is not animatable, so what are my options for doing this?
For my specific implementation I'm not simply scaling the UILabel/UITextView as-is; The bounds of the box is increasing more (proportionally) than the font-size, so there will be re-flow in the text. That's fine by me.
Edit: I would be fine with simply scaling the UITextView.
Also, I was considering "animating" it manually: I have a method that lays out my views and adjusts for font size. If I knew when the rotation was about to start, and the duration of the animation, I could time it so it renders an intermediate font size or two in the middle of the animation. Any help with getting those would be appreciated.
One option is to fade the old text out, change the font size, and fade it back in. The font property may not be animatable, but alpha is. Since alpha is a property of UIView, you can treat all your text-bearing views the same way: UILabel, UITextView, etc. It looks good, too.
If I knew when the rotation was about
to start, and the duration of the
animation...
Funny you should mention that. Just before the animation starts, your view controller will receive a willAnimateRotationToInterfaceOrientation:duration: message that gives you the exact information you need.
A way to proceed could be to:
Create a CAKeyframeAnimation
Define the scaling and rotation you want to achieve during that animation using a set of CATransform3D objects
Add these transforms to your keyframed animation
Send the addAnimation message to your label layer object: [[label layer] addAnimation];
Here would be a code sample assuming yourLabel is the UILabel you want to scale and rotate:
CAKeyframeAnimation *scale = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
CATransform3D scaleUp = CATransform3DMakeScale(1.5, 1.5, 1); // Scale in x and y
CATransform3D rotationScaled = CATransform3DRotate (scaleUp, 90, 0, 0, 1); // Rotate the scaled font
[scale setValues:[NSArray arrayWithObjects:
[NSValue valueWithCATransform3D:CATransform3DIdentity],
[NSValue valueWithCATransform3D:rotationScaled],
nil]];
// set the duration
[scale setDuration: 1.0];
// animate your label layer
[[yourLabel layer] addAnimation:scale forKey:#"scaleText"];
This is typically how a text bouncing around would be animated for instance.
You could start this when the device starts rotating and retrieve the animation when it's done rotating so you can update your label with the proper scale/position.
You will need tuning to find the proper timing and rotation.
Change the font size when didAnimateFirstHalfOfRotationToInterfaceOrientation: is called.
That way the user won't see the change once the rotation completed. It would be pretty hard to see the font size change then, as the rotation is happening!

Can I animate a radial gradient in iPhone?

I would like to animate a radial gradient to shrink and grow the inner radius, as if it were pulsing.
Right now I'm rendering the gradient with CGGradient, but I'm not sure how to animate it. I've seen this topic
Can you animate gradients using Quartz in an iPhone?
Which explains how animate a linear gradient with CAGradientLayer, but it doesn't seem like this will draw a radial gradient.
Is there an easy way to animate a CGGradient, or some way to create a radial gradient CAGradientLayer?
Any thoughts are appreciated.
If only Core Image was on the phone, this would be trivial. An animatable filter is what you need. ;-)
The CAGradientLayer does allow for animating its properties, however, it currently only supports linear gradients.
The only thing I can think of if you're wanting to animate using Core Animation is to animate the transform of the view into which you're drawing your gradient.
You can animate any view's transform pretty simply. Just draw your gradient in the view as you're probably already doing and then animate the transform using scaling. Using explicit animation, it would be something like:
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"transform"];
[anim setFromValue:[NSValue valueWithCATransform3D:CATransform3DIdentity]];
// Scale x and y up
[anim setToValue:[NSValue valueWithCATransform3D:
CATransform3DMakeScale (1.0f, 1.0f,0.0f)]];
[anim setAutoreverses:YES];
[anim setRepeatCount:HUGE_VALF];
[[gradientView layer] addAnimation:anim];
Unfortunately this would look like expanding and contracting more than pulsing, I think (though I haven't tried it).
I think if you want a true pulsing with your gradient at this point, you probably have to do the animation manually using your own timer. Just re-draw periodically changing your inner radius value as you go. Ugh. I'm not absolutely sure that's the only way, but I have yet to see a compelling pulse animation on the phone with a gradient as you're wanting.
There is one other idea I would like to try at some point. Core Animation now allows animating arbitrary properties/values. You could theoretically set up an animation using some arbitrary keypath that you name (say innerRadius, for example) and override the -drawLayerInContext delegate method. Then you could grab the current value from the presentationLayer while it's in mid-flight and re-draw your gradient there instead. This is just theoretical as I haven't tried it, but it seems like it might be worth looking into.
Best regards.

How do I draw a point inside of an instance of UIImageView?

How do I use Core Graphics to draw inside of a instantiated UIImageView (No class files to override drawrect with -- just a reference to the instance)? I want to draw a simple point of fixed width (say, 10 pixels diameter) and of a certain color.
How do I use Core Graphics to … draw a simple point of fixed width (say, 10 pixels diameter) and of a certain color[?]
Set the fill color.
Move to the point.
Plot an arc, centered at the point, with the desired radius, giving a start angle of 0 and an end angle of 2 * M_PI.
Close the path.
Fill.
… draw inside of a instantiated UIImageView (No class files to override drawrect with -- just a reference to the instance)
I know that on the Mac, that's really not a good idea, as an NSView may redraw at any time if anything has set it as needing display (or told it directly to display), and would then clobber anything you'd drawn over it. No idea about the iPhone, but I do notice that UIView doesn't have methods like NSView's lockFocus and unlockFocus.
There's a more architectural reason not to do this: Drawing is the exclusive domain of views, and you're proposing to break that exclusivity and sprinkle some drawing code in (I'm assuming) a controller. Scattering code for a purpose in an object that doesn't have that purpose is a path to messy, unnavigable code.
Better to make a subview for each point and add those as subviews of the image view, or subclass UIImageView, give your instance of that subclass an array of the points to draw, and have the subclass's drawRect: call up to super before drawing its points.
The above solution, then, goes in drawRect:.
You can also just add a Core Animation layer to the view. Set the corner radius to half the size of the point you want (assuming a square) and it will render as a circle. Something like:
CALayer *layer = [CALayer layer];
[layer setBounds:CGRectMake(0.0f, 0.0f, 10.0f, 1.0f)];
[layer setCornerRadius:5.0f];
[layer setMasksToBounds:YES];
[layer setBackgroundColor:[[UIColor redColor] CGColor]];
// Center the layer in the view.
[layer setPosition:CGPointMake([view bounds].size.width/2,
[view bounds].size.height/2)];
// Add the layer to your view
[[view layer] addSublayer:layer];
Not Core Graphics exactly, but it's easy to implement.
Best Regards,