I'm using the Facebook POP framework to animate UILabels and UITextFields when the user entered a wrong input. I want to simulate the kind of spring animation when you enter the wrong password in on a mac.
I've writting this method for doing the shake animation:
- (void)shakeAnimation:(UITextField *)textField :(UILabel *)label
{
//This method takes a textField and a label as input, and animates them accordingly.
//When the animation is done, the button is available for touch again, and the animation is removed.
POPSpringAnimation *positionAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerPositionX];
positionAnimation.springBounciness = 20; //just parameters, nothing fancy
positionAnimation.velocity = #4000;
[positionAnimation setCompletionBlock:^(POPAnimation *animation, BOOL finished) {
self.signUpButton.userInteractionEnabled = YES;
}];
[textField.layer pop_addAnimation:positionAnimation forKey:#"positionAnimation"];
[label.layer pop_addAnimation:positionAnimation forKey:#"positionAnimation"];
[positionAnimation removedOnCompletion];
}
However, When I'm calling this method like so: [self shakeAnimation:self.emailField :self.emailLabel];
the UILabels only move about 50px to the right and then stop moving, whereas the UITextFields don't do anything. It doesn't seem like an animation at all, just an adjustment of the frame.
Instead of adding the same POPSpringAnimation to both the textField layer and the label layer, make two POPSpringAnimations and add one to each. Like this :
POPSpringAnimation *anim1 = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerPositionX];
anim1.springBounciness = 20; //just parameters, nothing fancy
anim1.velocity = #400;
[anim1 setCompletionBlock:^(POPAnimation *animation, BOOL finished) {
self.button.userInteractionEnabled = YES;
}];
POPSpringAnimation *anim2 = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerPositionX];
anim2.springBounciness = 20; //just parameters, nothing fancy
anim2.velocity = #400;
[textField.layer pop_addAnimation:anim1 forKey:#"positionAnimation"];
[label.layer pop_addAnimation:anim2 forKey:#"positionAnimation"];
[anim1 removedOnCompletion];
[anim2 removedOnCompletion];
Related
I thought I was used to completion handlers but for some reason I think I have implemented this wrong.
The animation breaks if I tap the 'button' in quick succession. By breaks I mean seems to increase the X position.
I am looking for the animation to not be affected by the touch until its complete
if(self.segmentedView.selectedSegmentIndex == 0 && !animating){
//NORMAL CODE IS HERE
POPSpringAnimation *layerPositionAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerPositionX];
animating = YES;
layerPositionAnimation.toValue = #(self.triangle.layer.position.x - secondSegmentWidth);
layerPositionAnimation.springBounciness = 10;
layerPositionAnimation.springSpeed = 20;
[layerPositionAnimation setCompletionBlock:^(POPAnimation *animation, BOOL finished) {
animating = NO;
}];
[self.triangle.layer pop_addAnimation:layerPositionAnimation forKey:#"layerPositionAnimation"];
}
I have a UIView that draws itself using -drawRect: and I want to animate the colors used for the drawing. Basic UIView animation stuff doesn’t work for obvious reasons (drawRect).
I can’t simply use CAShapeLayer to draw and animate the view contents. I’d like to try faking the animation by hand, using a timer or CADisplayLink in combination with setNeedsDisplay. Is there a reasonably simple way to hide this magic behind the usual UIView animation API?
For example, let’s say there’s a color property I’d like to animate:
[UIView animateWithDuration:0.2 animations:^{
[customDrawingView setColor:[UIColor redColor]];
}];
Is there a way to “intercept” the animation call to read the animation parameters (time, target value) and process them by hand? I don’t want to swizzle UIView.
I had to do something like you did quite a few times. In general you can create some classes to handle stuff like floating point interpolation or CGPoint interpolation... and do it all properly but since the whole drawing already exists there is not much point in it.
So the UIView animateWithDuration will not work here but you can add your display link and do the interpolation manually. It is best done if no existing code is changed, only add a few methods:
Assuming you currently have something like this:
- (void)setColor:(UIColor *)color
{
_color = color;
[self setNeedsDisplay];
}
Now you can add setColorAnimated: and do all the additional functionality:
You need some additional parameters (properties), use what you want:
UIColor *_sourceColor;
UIColor *_targetColor;
NSDate *_startDate;
NSDate *_endDate;
CADisplayLink *_displayLink;
And the methods:
- (void)setColorAnimated:(UIColor *)color {
_sourceColor = self.color; // set start to current color
_targetColor = color; // destination color
_startDate = [NSDate date]; // begins currently, you could add some delay if you wish
_endDate = [_startDate dateByAddingTimeInterval:.3]; // will define animation duration
[_displayLink invalidate]; // if one already exists
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(onFrame)]; // create the display link
}
- (UIColor *)interpolatedColor:(UIColor *)sourceColor withColor:(UIColor *)targetColor forScale:(CGFloat)scale {
// this will interpolate between two colors
CGFloat r1, g1, b1, a1;
CGFloat r2, g2, b2, a2;
[sourceColor getRed:&r1 green:&g1 blue:&b1 alpha:&a1];
[targetColor getRed:&r2 green:&g2 blue:&b2 alpha:&a2];
// do a linear interpolation on RGBA. You can use other
return [UIColor colorWithRed:r1+(r2-r1)*scale
green:g1+(g2-g1)*scale
blue:b1+(b2-b1)*scale
alpha:a1+(a2-a1)*scale];
}
- (void)onFrame {
// scale is valid between 0 and 1
CGFloat scale = [[NSDate date] timeIntervalSinceDate:_startDate] / [_endDate timeIntervalSinceDate:_startDate];
if(scale < .0f) {
// this can happen if delay is used
scale = .0f;
}
else if(scale > 1.0f)
{
// end animation
scale = 1.0f;
[_displayLink invalidate];
_displayLink = nil;
}
[self setColor:[self interpolatedColor:_sourceColor withColor:_targetColor forScale:scale]];
}
Animated (Fade in) new frame created via drawRect
- (void)drawRect:(CGRect)rect {
CABasicAnimation *animation;
animation = [CABasicAnimation animation];
[animation setDuration:0.5];
[[self layer] addAnimation:animation forKey:#"contents"];
.. your stuff here
}
i am new to IOS programming. i have a background image on my screen. What i want is when app launches screen shows with the background image and then after 1 sec 2 buttons comes up from right side of the screen. how can i do this. here is the image what i am trying to do
button 1 and button2 have to be invisible first. after 1 second they comes up from right side.
if there are any tutorials related to this kind of animation then please share
You can use simple UIView Animations to achieve this.
Set your button x origin any value greater than screen size so that it is not visible first. Then reset the button frame to a visible value with view animation. For example call the following function in your viewWillAppear: method.
- (void) animateButton {
// Setting button origin to value greater than the view width.
CGRect frame = button.frame;
frame.origin.x = self.view.frame.size.width+10;// or set any value > 320
button.frame = frame;
[UIView animateWithDuration:0.5
delay:2.0
options: UIViewAnimationCurveEaseOut
animations:^{
button.frame = CGRectMake(20, 100, 60, 25) ;// Any value according to your requirement
}
completion:^(BOOL finished){
NSLog(#"Done!");
}];
}
Refer Tutorial
You can do it this way, please note that, if you want to animate the way your button shows up, you must hide them using alpha = 0.0f (and not hidden = true). This way, you will have a smooth showing up animation !
Here it is :
First, on your viewDidLoad of your UIViewController, you have to set up a NSTimer of 1sec, then let it call the method that will let your buttons appear :
- (void) viewDidLoad {
[super viewDidLoad];
// Do your init stuff here
// We will call your buttons as if they are declared as properties of your class, ask if you don't know how to set them
self.button1.alpha = 0.0f;
self.button2.alpha = 0.0f;
// This will wait 1 sec until calling showButtons, where we will do the trick
NSTimeInterval timeInterval = 1.0;
[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self selector:#selector(showButtons) userInfo:nil repeats:NO];
}
- (void) showButtons {
[UIView beginAnimations:#"buttonShowUp" context:nil];
[UIView setAnimationDuration:0.5]; // Set it as you want, it's the length of your animation
self.button1.alpha = 1.0f;
self.button2.alpha = 1.0f;
// If you want to move them from right to left, here is how we gonna do it :
float move = 100.0f; // Set it the way you want it
self.button1.frame = CGRectMake(self.button1.frame.origin.x - move,
self.button1.frame.origin.y,
self.button1.frame.size.width,
self.button1.frame.size.height);
self.button2.frame = CGRectMake(self.button2.frame.origin.x - move,
self.button2.frame.origin.y,
self.button2.frame.size.width,
self.button2.frame.size.height);
// If you want to set them in the exact center of your view, you can replace
// self.button1.frame.origin.x - move
// by the following
// (self.view.center - self.button1.frame.size.width/2)
// This way, your button will be centered horizontally
[UIView commitAnimations];
}
Ask if you have any questions !
Set the buttons' x co-ordinates such that the button is just outside the screen on the right side.
And then try the following in your viewDidLoad()
[UIView animateWithDuration:0.5
delay:1.0
options:NO
animations:^{ change your button x co- ordinate here to the co-ordinate you need it on finally}
completion:NULL];
I have a UISegmentedControl that is shown/hide like a modal window. Initially it does not have a selected segment. In IB, the Value Changed event is wired to the method - (IBAction)cardClassificationChanged:(UISegmentedControl *)sender. Here is that method:
- (IBAction)cardClassificationChanged:(UISegmentedControl *)sender
{
NSLog(#"%d", sender.selectedSegmentIndex);
// block for updating the categorization of current card asynchronously
[self.cardActionSheet hideWithAnimation];
}
If I comment out the last line (the call to -hideWithAnimation), the selection changes as expected, everything works. However, with the call to that animation method, the UISegmentedControl selection will not visually change before the animation. Here is the hideWithAnimation method:
- (void)hideWithAnimation
{
CATransition *animation = [CATransition animation];
animation.type = kCATransitionFade;
animation.duration = globalAnimationLength;
[self.layer addAnimation:animation forKey:nil];
self.hidden = YES;
}
The next time this view appears (from a touch gesture), the UISegmentedControl will have the correct segment selected though.
It seems like I should not have to call setNeedsDisplay for the UISegmentedControl, but even when I experiment with it in the cardClassificationChanged method or the hideWithAnimation method, it does not refresh.
I'm obviously missing something related to the UI update, what do I need to call to update the UISegmentedControl selection before the animation?
I suggest you should use UIKit animations for fading out your control. Try the following code
- (void)hideWithAnimation
{
[UIView animateWithDuration:0.2
animations:^{
self.alpha = 0.;
} completion:^(BOOL finished) {
self.alpha = 1.;
self.hidden = YES;
}];
}
I have a UITextView, and I am trying to animate a change of frame when the user taps on a button. Basically the text view grows larger to fit the screen so it can display more text, and then when the user taps the button again it shrinks to its original frame.
I perform the animation using blocks, like this:
if(!isDisplayingDetailView)
{
//Expand view
[UIView animateWithDuration:0.5
animations:^{
self.detailView.frame = self.view.frame;
}
completion:^(BOOL finished){
isDisplayingDetailView = YES;
}];
}
else{
//Shrink view.
[UIView animateWithDuration:0.5
animations:^{
self.detailView.frame = self.detailFrame;
}
completion:^(BOOL finished){
isDisplayingDetailView = NO;
}];
}
Where self.detailView is the UITextView, and self.detailFrame is just a CGRect holding the original frame. isDisplayingDetailView is just a BOOL to check if the view is expanded or not.
My problem is that the text resize is not animating. I made some screenshots from a test app to illustrate my problem:
App default view:
The expanded view:
The textview just a moment after tapping the button:
As you can see the text automatically shrinks to the final frame size, without any kind of animation, while the bounds still are animating. I guess that's the right behavior, but I'd like to know if it's possible to animate the text along with its view.
The text in textviews doesn't always act as expected. For this you'll have to set up a NSTimer and set the frame size on every tick.
Do something like:
textview.frame = CGRectMake (textview.frame.origin.x, textview.frame.origin.y, textview.frame.size.width-1, textview.frame.size.height-1);
Then when it's done I would completely remove the textview from superview and set it to nil, and then init a new one. The reason for this is that when changes the frames of textviews for some reason padding is added around the text's box. You won't notice it unless you change the frame a probably at least 100 times.
[textview removeFromSuperview];
textview = nil;
textview = [[UITextView alloc] initWithFrame:CGRectMake(x, y, width, height)];
textview.text = yourTextString;
//You will also have to set whatever non default properties you want, such as text or background color
[view addSubview:textview];
Another solution is to animate the bounds change.
There are a few ways, but here's a simple subclass of UITextView which does exactly this.
import Foundation
import UIKit
import QuartzCore
class SmoothTextView : UITextView {
override func actionForLayer(layer: CALayer!, forKey key: String!) -> CAAction! {
if key == "bounds" {
let x = super.actionForLayer(layer, forKey: "backgroundColor")
if let action:CAAnimation = x as? CAAnimation {
let transition = CATransition()
transition.type = kCATransitionFade
transition.beginTime = action.beginTime
transition.duration = action.duration
transition.speed = action.speed
transition.timeOffset = action.timeOffset
transition.repeatCount = action.repeatCount
transition.repeatDuration = action.repeatDuration
transition.autoreverses = action.autoreverses
transition.fillMode = action.fillMode
transition.timingFunction = action.timingFunction
transition.delegate = action.delegate
return transition
}
}
return super.actionForLayer(layer, forKey: key)
}
}
This as of iOS 8.
As an extra tweak, you might want to configure the text of your text view instance by adjusting the text container by zeroing all padding or insets:
textView.textContainer.lineFragmentPadding = 0.0
textView.textContainerInset = UIEdgeInsetsZero