Execute animation of subview subclass - ios

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;
}];
}

Related

Animate UILabel width with auto layout

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;
}];
}
}

UIView returns to its initial frame after UITextField subview becomeFirstResponder

I have a UIView subclass that contains a UITextField as subview. The idea is to animate this UIView and make the UITexField becomeFirstResponder when animation has ended. For some reason, after animation completion block has been executed and the UITextField has becomeFirstResponder, the UIView returns to its initial frame.
Here is the dummy sample code:
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UIView *redView;
#property (strong, nonatomic) UITextField *textField;
#property (strong, nonatomic) UITapGestureRecognizer *gesture;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.textField = [[UITextField alloc] initWithFrame:CGRectMake(0.f, 0.f, self.redView.frame.size.width, 30.f)];
self.textField.backgroundColor = [UIColor lightGrayColor];
[self.redView addSubview:self.textField];
self.gesture = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(animateRedView:)];
[self.view addGestureRecognizer:self.gesture];
}
- (IBAction)animateRedView:(id)sender
{
[UIView animateWithDuration:.5
delay:0
usingSpringWithDamping:1.2
initialSpringVelocity:5
options:0
animations:^{
CGRect redViewFrame = self.redView.frame;
[self.redView setFrame:CGRectMake((self.view.frame.size.width - redViewFrame.size.width),
redViewFrame.origin.y,
redViewFrame.size.width,
redViewFrame.size.height)];
} completion:^(BOOL finished) {
[self.textField becomeFirstResponder];
}];
}
#end
Any idea?
Did you create redView in interface builder? If so it's going to have layout constraints attached to it.
You can remove them at runtime. Try adding this before you call setFrame
[self.redView removeConstraints:self.redView.constraints];
Also, you might try calculating the destination frame before the animation block (since you're calculating redFrame's new frame based on its current frame).
- (IBAction)animateRedView:(id)sender
{
CGRect redViewFrame = self.redView.frame;
CGRect newFrame = CGRectMake((self.view.frame.size.width - redViewFrame.size.width),
redViewFrame.origin.y,
redViewFrame.size.width,
redViewFrame.size.height);
[UIView animateWithDuration:.5
delay:0
usingSpringWithDamping:1.2
initialSpringVelocity:5
options:0
animations:^{
[self.redView setFrame:newFrame];
} completion:^(BOOL finished) {
[self.textField becomeFirstResponder];
}];
}

How To Add button in a Slide-out Sidebar Menu in ios programmatically

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?

How to reverse a UIVIew animation on second uibutton press?

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];
}

Need to correct the way my app moves an image

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];

Resources