I have a method that drops down a UIView. That works fine. How would I go about having it slide back up when the button is touched a second time?
I also need to deactivate it somehow so that it doesn't repeat the animation if it's already displayed.
- (void)slideDownTableView {
self.tableViewCities = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
self.tableViewCities.frame = CGRectMake(0,0,300,180);
self.tableViewCities.autoresizingMask = ( UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth );
self.tableViewCities.dataSource = self;
self.tableViewCities.delegate = self;
self.tableViewCities.backgroundColor = [UIColor whiteColor];
UIView *myview=[[UIView alloc] initWithFrame: CGRectMake(10, 0,300,180)];
myview.backgroundColor=[UIColor redColor];
[myview addSubview:self.tableViewCities];
[self.view addSubview:myview];
myview.frame = CGRectMake(10, 0,300,-180); // offscreen
[UIView animateWithDuration:0.5
animations:^{
myview.frame = CGRectMake(10, 0,300,180); // final location move
}];
}
iOS provides autoreverse property by which animation reverses automatically.
UIViewAnimationOptionAutoreverse will give you an effect as the animation reverses .
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionAutoreverse
animations:^{
// do whatever animation you want, e.g.,
someView.frame = someFrame1;
}
completion:NULL];
Using BeginFromCurrentState option worked for me.
if(!enabled){
[UIView animateWithDuration:0.4 delay:0.0 options:UIViewAnimationTransitionCurlUp
animations:^{
myview.frame = CGRectMake(10, 0,300,180);
}
completion:nil];
enabled=YES;
}
else{
[UIView animateWithDuration:0.4 delay:0.0 options:UIViewAnimationTransitionCurlUp
animations:^{
myview.frame = CGRectMake(10, 0,300,-180);
}
completion:nil];
enabled=NO;
}
With a simple BOOL to store the current state of the sliding UIView you can achieve this.
The initial state is not visible if I understand well? So initially the BOOL citiesVisible is set to N0, then the button's action check its value, and performs a different animation accordingly. Simple.
Finally, I also disable the button during the animation to prevent the user to trigger a second animation while a first one is already performing.
I rewrote a bit your code to follow some best practices, and improve readability/maintainability:
Setting up the views beforehand because the button's action responsibility shouldn't be to setup the views, but only to animate them.
Changed the signature of the target action, that's my personal style guide to state in the sender's name and the ControlEvent, it makes it more tidy when you have multiple buttons/actions ;). Also per Apple guidelines, methods receiving actions should have one "id" parameter, the sender.
Store the frames in variables (you could also have them as #define, that'd be even better I think), so you don't have to hard-code them twice, it reduces the risk of breaking the animation by having a view sliding to different positions...
Code update:
#interface ViewController ()
#property (nonatomic, strong) UIButton *showCitiesButton;
#property (nonatomic, strong) CGRect citiesInitialFrame;
#property (nonatomic, strong) CGRect citiesVisibleFrame;
#property (nonatomic, strong) UITableView *tableViewCities;
#property (nonatomic, strong) UIView *citiesContainer;
#property (nonatomic, strong) BOOL citiesVisible;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Setting up the cities view
self.citiesInitialFrame = CGRectMake(10, -180, 300, 180);
self.citiesVisibleFrame = CGRectMake(10, 0, 300, 180);
self.tableViewCities = [[UITableView alloc] init ...];
self.citiesContainer = [[UIView alloc] init ...];
// ...
self.citiesVisible = NO;
[self.showCitiesButton addTarget:self
action:#selector(showCitiesButtonDidTouchUpInside:)
forControlEvents:UIControlEventTouchUpInside];
}
- (void)showCitiesButtonDidTouchUpInside:(id)sender
{
[self.showCitiesButton setEnabled:NO];
if (self.citiesVisible)
{
// Cities view is visible, sliding it out of the screen
[UIView animateWithDuration:0.5
animations:^{
self.citiesContainer.frame = self.citiesInitialFrame;
} completion:^(BOOL finished) {
self.citiesVisible = NO;
[self.showCitiesButton setEnabled:YES];
}];
}
else
{
// Cities view is not visible, sliding it in
[UIView animateWithDuration:0.5
animations:^{
self.citiesContainer.frame = self.citiesVisibleFrame;
} completion:^(BOOL finished) {
self.citiesVisible = YES;
[self.showCitiesButton setEnabled:YES];
}];
}
}
#end
You can reverse UIView animation with this code:
if (isMarked) {
[UIView animateWithDuration:3
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:(void (^)(void)) ^{
self.transform=CGAffineTransformMakeScale(1.1, 1.1);
}
completion:nil];
} else {
[UIView animateWithDuration:3
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:(void (^)(void)) ^{
self.transform=CGAffineTransformIdentity;
}
completion:nil];
}
Related
I'm trying to animate UILabel's width. For some reason animation doesn't work and label changes it's size immediately. I use autolayout. Here is the code from the sample project I wrote to reproduce this. Tap on the button changes trailing constrains for both the button and the label.
#interface ViewController ()
#property (assign, nonatomic) BOOL controlsResized;
#property (weak, nonatomic) IBOutlet UIButton *button;
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *buttonTrailingConstraint;
#property (weak, nonatomic) IBOutlet MyLabel *label;
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *labelTrailingConstraint;
- (IBAction)doTapButton:(id)sender;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.button setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.label setTranslatesAutoresizingMaskIntoConstraints:NO];
self.label3.text = #"asdfasdfds sklfjfjls sdlkfj jjjjkfjkfdsjkdfsjklffsjkfdsjkl";
}
- (IBAction)doTapButton:(id)sender
{
if (!self.controlsResized)
{
[UIView animateWithDuration:0.5 animations:^{
self.buttonTrailingConstraint.constant = 0;
[self.button layoutIfNeeded];
self.labelTrailingConstraint.constant = 0;
[self.label layoutIfNeeded];
self.controlsResized = YES;
}];
}
else
{
[UIView animateWithDuration:0.5 animations:^{
self.buttonTrailingConstraint.constant = 100;
[self.button layoutIfNeeded];
self.labelTrailingConstraint.constant = 100;
[self.label layoutIfNeeded];
self.controlsResized = NO;
}];
}
}
#implementation MyLabel
- (void)drawTextInRect:(CGRect)rect
{
NSLog(#"drawTextInRect");
[super drawTextInRect:rect];
}
#end
As you can see on the video, it works for the button, but doesn't work for the label.
I tried to investigate and subclassed label from MyLabel class. It appears, that - (void)drawTextInRect:(CGRect)rect gets called immediately on the button tap, as a reason of [self.label layoutIfNeeded]; in the animation block. Why is it so? I would've expect the frame to be changed animated, as it is set inside the animation block. And it works as expected for the UIButton.
Why UILabel doesn't resize animated? Is there a way to fix it?
EDIT:
I tried the approaches suggested in current answers, but they don't work either. I think the animation code is not a problem here, because it works for the button in my example. The problem seems to be related to UILabel specific. UILabel seems to handle animation of size changes differently than other UIView subclasses, but I can't understand why.
I would post a comment but I do not have the necessary reputation for that. Here is what I would do:
self.buttonTrailingConstraint.constant = x;
self.labelTrailingConstraint.constant = x;
[UIView animateWithDuration:0.5 animations:^{
[self.view layoutIfNeeded];
}];
When you update a constraint you need to take into account that the respective view's constraint you are modifying is not the only one that needs updating, so a layout method call is better made on the parent view.
This worked for me everytime. Hope this helps.
You must set constants of constraints before animate.
Try this :
- (IBAction)doTapButton:(id)sender
{
if (!self.controlsResized)
{
self.buttonTrailingConstraint.constant = 0;
self.labelTrailingConstraint.constant = 0;
[UIView animateWithDuration:0.5 animations:^{
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
self.controlsResized = YES;
}];
}
else
{
self.buttonTrailingConstraint.constant = 100;
self.labelTrailingConstraint.constant = 100;
[UIView animateWithDuration:0.5 animations:^{
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
self.controlsResized = NO;
}];
}
}
There are a couple parts to making this 100% work as expected:
Calling layoutIfNeeded first
Setting the constraint constant outside of the animation block
Calling layoutIfNeeded inside the block
Copied from a working implementation:
[self.view layoutIfNeeded];
self.topContainerConstraint.constant = constraintValue;
[UIView animateWithDuration:0.25 animations:^{
[self.view layoutIfNeeded];
} completion:^(BOOL finished){ }];
I'd change your function to something like the following (obviously untested):
- (IBAction)doTapButton:(id)sender
{
if (!self.controlsResized)
{
[self.view layoutIfNeeded];
self.labelTrailingConstraint.constant = 0;
self.buttonTrailingConstraint.constant = 0;
[UIView animateWithDuration:0.5 animations:^{
[self.view layoutIfNeeded];
self.controlsResized = YES;
}];
}
else
{
[self.view layoutIfNeeded];
self.labelTrailingConstraint.constant = 100;
self.buttonTrailingConstraint.constant = 100;
[UIView animateWithDuration:0.5 animations:^{
[self.view layoutIfNeeded];
self.controlsResized = NO;
}];
}
}
I have a UIViewController called ParentViewController.
And i have a UIView custom class called CustomView. It including some ImageView and the function to execute animation.
CustomView.h
#interface CustomView : UIView
#property (weak, nonatomic) IBOutlet UIImageView *human;
#property (weak, nonatomic) IBOutlet UIImageView *shadow;
+ (id)CustomView;
- (void)executeAnimation;
#end
And in CustomView.m i ave executeAnimation as the following:
-(void)executeAnimation{
self.animation1InProgress = YES;
[UIView animateKeyframesWithDuration:3.0 delay:0.0 options:UIViewAnimationCurveLinear animations:^{
self.human.frame = CGRectMake(self.human.frame.origin.x, self.human.frame.origin.y + 300, self.human.frame.size.width, self.human.frame.size.height);
} completion:^(BOOL finished){
self.animation1InProgress = NO;
}];
}
Now in ParentViewController.m, i add CustomView without any animation
//init custom
customView = [CustomView initCustomView];
[self.view addSubview:centerLocationView];
This code is OK. I could init and addSubview to ParentViewController.
But whenever i would like to execute animation about CustomView. I call the following coding in ParentViewController.m:
[customView executeAnimation];
There is nothing changed at Parent View.
Is anybody know the way to execute this animation at ParentViewController?
Thank in advance.
If you really want to use +[UIView animateKeyframesWithDuration:delay:options:animations:completion:], you should add keyframes to your animations block:
-(void)executeAnimation{
self.animation1InProgress = YES;
[UIView animateKeyframesWithDuration:3.0 delay:0.0 options:UIViewAnimationCurveLinear animations:^{
[UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:1.0 animations:^{
self.human.frame = CGRectMake(self.human.frame.origin.x, self.human.frame.origin.y + 300, self.human.frame.size.width, self.human.frame.size.height);
}];
} completion:^(BOOL finished){
self.animation1InProgress = NO;
}];
}
Otherwise, just use [UIView animateWithDuration:animations:completion:]:
-(void)executeAnimation{
self.animation1InProgress = YES;
[UIView animateWithDuration:3.0 delay:0.0 options:UIViewAnimationCurveLinear animations:^{
self.human.frame = CGRectMake(self.human.frame.origin.x, self.human.frame.origin.y + 300, self.human.frame.size.width, self.human.frame.size.height);
} completion:^(BOOL finished){
self.animation1InProgress = NO;
}];
}
I am pretty new in objective c so hopefully this all make sense.. I have declared UIView in .h file
#property (strong, nonatomic) UIView *menuViewone;
In .m file i declared UIView in viewdidload
menuViewone =[[UIView alloc ]initWithFrame:CGRectMake(-400, 0, 200, 568) ];
[menuViewone setBackgroundColor:[UIColor whiteColor]];
[self.view addSubview:menuViewone];
and set the view frame in Button method
- (IBAction)collectionmenutwo:(id)sender {
if (menuViewone.frame.origin.x >=0 ) {
[UIView animateKeyframesWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
menuViewone.frame=CGRectMake(-400, 100, 300, 568);
} completion:^(BOOL finished) {
}];
}
else
{
[UIView animateKeyframesWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
menuViewone.frame=CGRectMake(200, 100, 300, 568);
} completion:^(BOOL finished) {
}];
}}
Now i want to declare the buttons in this UIView ...How i can do that programmatically?
Just add buttons to the subview of that menuview of yours.
It would look something like this in the viewDidLoad of yours right after you declared your menuview.
UIButton *btn = [[UIButton] alloc] init];
btn.frame = CGRectMake(0,0,100,100);
//set other button properties here.
[menuViewone addSubview:btn];
For more specifics you can check this out:
How do I create a basic UIButton programmatically?
I have a UIButton in a UIViewController that I currently have "fading in" to view when the app is opened. I would like to get the UIButton to slide in from left to right instead though.
I can't figure out how to get it to slide in from left to right and my attempts have failed, even though I've got the "fade in" thing down solid.
Any help you could give me? Thanks! I can add any more code as needed-
ViewController.h
#property (weak, nonatomic) IBOutlet UIButton *buttonOne;
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
NSTimer *timermovebutton = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(movebutton) userInfo:nil repeats:NO];
[timermovebutton fire];
}
-(void) movebuttontwo
{
buttonTwo.alpha = 0;
[UIView beginAnimations:Nil context:Nil];
[UIView setAnimationDelay:0.5];
[UIView setAnimationCurve:UIViewAnimationTransitionCurlUp];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:1.0];
buttonTwo.alpha = 1.0;
[UIView setAnimationDidStopSelector:#selector(animationDidStop:finished:context:)];
[UIView commitAnimations];
}
You need to animate the buttons frame, not use an animation curve.
Since iOS 4, we've had UIView animation blocks:
// first set the UIButton frame to be outside of your view:
// we are only concerned about it's location on the X-axis so let's keep all properties the same
[buttonTwo setFrame:CGRectMake(CGRectGetMaxX(self.view.frame), CGRectGetMinY(buttonTwo.frame), CGRectGetWidth(buttonTwo.frame), CGRectGetHeight(buttonTwo.frame))];
[UIView animateWithDuration:0.5
delay:0
options:UIViewAnimationCurveEaseOut
animations:^{
// set the new frame
[buttonTwo setFrame:CGRectMake(0, CGRectGetMinY(buttonTwo.frame), CGRectGetWidth(buttonTwo.frame), CGRectGetHeight(buttonTwo.frame))];
}
completion:^(BOOL finished){
NSLog(#"Done!");
}
];
There's a good tutorial over at Ray Wenderlich you can check out to learn more.
//Set your button off the screen to the left
yourButton.frame = CGRectMake(-yourButton.frame.size.width, yourButton.frame.origin.y, CGRectGetWidth(yourButton.frame), CGRectGetHeight(yourButton.frame));
//Create the ending frame or where you want it to end up on screen
CGRect newFrm = yourButton.frame;
newFrm.origin.x = 100;
//Animate it in
[UIView animateWithDuration:2.0f animations:^{
yourButton.frame = newFrm;
}];
I need my app to move an image when the user clicks on a certain button. This works perfectly:
ViewController.m
#import "ndpViewController.h"
#interface ndpViewController ()
#property (nonatomic, retain) IBOutlet UIImageView *image;
-(void)movetheball;
#end
#implementation ndpViewController
#synthesize image;
int ballx, bally;
-(void)movetheball {
[UIView beginAnimations: #"MovingTheBallAround" context: nil];
[UIView setAnimationDelegate: self];
[UIView setAnimationDuration: 1.0];
[UIView setAnimationCurve: UIViewAnimationCurveEaseInOut];
image.frame = CGRectMake(ballx,bally,image.frame.size.width,image.frame.size.height);
[UIView commitAnimations];
}
- (void)viewDidLoad {
}
- (IBAction)calculatePush:(id)sender {
ballx = 600;
bally = 800;
[self movetheball];
}
#end
But as soon as the app has to do something else (when user clicks the button), like adding a text to a label, which I do in this way:
_lizfwLabel.text=[NSString stringWithFormat:#"%.2f",lizfw];
then the app will move the image only upon clicking the button a second time.
Any idea on why is this happening? Thank you!
Try to use another one method to animate UIView's in iOS. Apple advices to use [UIView animate...] rather then old style [UIView begin/commitAnimations];.
In your case you should replace movetheball method code to that one:
[UIView animateWithDuration:1.0 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
CGRect frame = image.frame;
frame.origin.x = ballx;
frame.origin.y = bally;
image.frame = frame;
} completion:nil];