Objective-c animation with constraints instability issue for trailing constraint - ios

I'm developing an IOS app using objective-c. I came across with an instability problem when animating a view's constraints. The animation works well regarding the final position of the frame, but randomly, the trailing(right) constraint is omitted for the first animation block(left constraint never reach to -20 constant value), however leading(left), top and bottom constraints are working as expected.
Please see the video I uploaded. In the first tap, the animation is working as expected, but in the second tap the issue occurs. Please advice.
Video showing the problem
- (void)animateTransition:(nonnull id<UIViewControllerContextTransitioning>)transitionContext {
// 1
UIView *containerView = transitionContext.containerView;
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
// 2
[containerView addSubview:toViewController.view];
if([toViewController isKindOfClass:[ImageViewController class]]){
ImageViewController *vcImage = (ImageViewController*)toViewController;
vcImage.constraintLeft.constant = 20;
vcImage.constraintTop.constant = self.selectedCardFrame.origin.y + 60.0;
vcImage.constraintRight.constant = 20.0;
vcImage.constraintBottom.constant = 0;//self.CollectionView.bounds.size.height - (self.selectedCardFrame.origin.y + self.selectedCardFrame.size.height);
[toViewController.view layoutIfNeeded];
NSTimeInterval duration = [self transitionDuration:transitionContext];
[toViewController.view layoutIfNeeded];
[UIView animateWithDuration:duration animations:^{
//First animation block
vcImage.constraintRight.constant = -20.0;
vcImage.constraintLeft.constant = -20.0;
vcImage.constraintTop.constant = -20.0;
vcImage.constraintBottom.constant = 0;
[toViewController.view layoutIfNeeded];
//toViewController.configureRoundedCorners(shouldRound: false)
} completion:^(BOOL finished) {
//Second animation block
[UIView animateWithDuration:duration animations:^{
vcImage.constraintLeft.constant = 0;
vcImage.constraintTop.constant = 0.0;
vcImage.constraintRight.constant = 0;
vcImage.constraintBottom.constant = 0;
[toViewController.view layoutIfNeeded];
//toViewController.configureRoundedCorners(shouldRound: false)
} completion:^(BOOL finished) {
[transitionContext completeTransition:finished];
}];
}];
}
}
Updated code after #Sh_Khan 's comment.
- (void)animateTransition:(nonnull id<UIViewControllerContextTransitioning>)transitionContext {
// 1
UIView *containerView = transitionContext.containerView;
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
// 2
[containerView addSubview:toViewController.view];
if([toViewController isKindOfClass:[ImageViewController class]]){
ImageViewController *vcImage = (ImageViewController*)toViewController;
NSTimeInterval duration = [self transitionDuration:transitionContext];
[UIView animateWithDuration:0 animations:^{
vcImage.constraintLeft.constant = 20;
vcImage.constraintTop.constant = self.selectedCardFrame.origin.y + 60.0;
vcImage.constraintRight.constant = 20.0;
vcImage.constraintBottom.constant = 0;
[toViewController.view layoutIfNeeded];
} completion:^(BOOL finished) {
[UIView animateWithDuration:duration animations:^{
//First animation block
vcImage.constraintRight.constant = -20.0;
vcImage.constraintLeft.constant = -20.0;
vcImage.constraintTop.constant = -20.0;
vcImage.constraintBottom.constant = 0;
[toViewController.view layoutIfNeeded];
//toViewController.configureRoundedCorners(shouldRound: false)
} completion:^(BOOL finished) {
//Second animation block
[UIView animateWithDuration:duration animations:^{
vcImage.constraintLeft.constant = 0;
vcImage.constraintTop.constant = 0.0;
vcImage.constraintRight.constant = 0;
vcImage.constraintBottom.constant = 0;
[toViewController.view layoutIfNeeded];
//toViewController.configureRoundedCorners(shouldRound: false)
} completion:^(BOOL finished) {
[transitionContext completeTransition:finished];
}];
}];
}];
}
}

Before setting your imageView try reorienting the photo. Something tells me the image view is having a hard time determining the orientation of the image. I had this happen during a transition once and would explain why you are seeing the behavior only some of the time. I am a bit rusty with Objective C but give this a shot and use it to before setting your imageView image.
-(UIImage*)normalizeImage:(UIImage*)currentImage {
if (currentImage.imageOrientation == UIImageOrientationUp){
return currentImage;
}
UIGraphicsBeginImageContextWithOptions(currentImage.size, NO, currentImage.scale);
[currentImage drawInRect:CGRectMake(0, 0, currentImage.size.width, currentImage.size.height)];
UIImage *_newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return _newImage;
}

Related

popspringanimation very choppy in UINavigationController Delegate

In UINavigationController Delegate I add pop animation in the transition, what I did is as following:
- (NSTimeInterval) transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
return self.duration;
}
- (void) animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
UIView *containerView = [transitionContext containerView];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
// I get some cell from initialization
UIImageView *avatarImage = [[UIImageView alloc] initWithFrame:frame];
avatarImage.image = _cell.avatarImageView.image;
avatarImage.clipsToBounds = YES;
avatarImage.layer.cornerRadius = avatarImage.frame.size.width/2.0;
CGFloat originSize = avatarImage.frame.size.width;
[fromViewController.view addSubview:avatarImage];
CGFloat avatarSize = CURRENT_SCREEN_WIDTH * 0.15;
CGFloat avatarPositionY = CURRENT_SCREEN_WIDTH * 0.4;
POPSpringAnimation *animation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerPosition];
animation.toValue = [NSValue valueWithCGPoint:CGPointMake(CURRENT_SCREEN_WIDTH/2.0, avatarPositionY + avatarSize/2.0 - 64)];
animation.springBounciness = 2.0;
[avatarImage.layer pop_addAnimation:animation forKey:#"move"];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
toViewController.view.alpha = 1;
} completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
[imageView removeFromSuperview];
[avatarImage removeFromSuperview];
}];
}
But the problem is, it's not smooth at all, what is the problem ?
There can be multiple problems;
Your image is very big (in memory)
The corner radius isn't helping either
You're doing something else on the main thread during during this animation
Try the following and see if the animation is smooth after each change:
Remove/comment:
avatarImage.image = _cell.avatarImageView.image;
Remove/comment:
avatarImage.clipsToBounds = YES;
avatarImage.layer.cornerRadius = avatarImage.frame.size.width/2.0;

How to implement nice animation for rectangle UIButton to circle shape in iOS?

When i clicked on "Login" button i need to make my rectangle login button
Login Screenshot
to animate and change it shape to circle like below link
Login Button after i click
I have seen this code in stackoverflow but its not working the way i want
CGFloat width = self.login.frame.size.width;
CABasicAnimation *morph = [CABasicAnimation animationWithKeyPath:#"cornerRadius"];
morph.fromValue = #0;
morph.toValue = #(width/2);
morph.duration = 0.5;
[self.login.layer addAnimation:morph forKey:#"morph"];
self.login.layer.cornerRadius = width/2;
Please help
Thanks for answering this.
This is the final code
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationCurveLinear
animations:^{
self.layoutWidth.constant = 70;
[self.view layoutIfNeeded];
self.btnLogin.clipsToBounds = YES;
self.btnLogin.layer.cornerRadius =self.btnLogin.bounds.size.height/2.0f;
}
completion:^(BOOL finished){
}];
Try this,
Demo Animation
Code:
- (IBAction)clicklogin:(id)sender {
[UIView animateWithDuration:0.1
delay:1.0
options:UIViewAnimationCurveLinear
animations:^{
self.layoutWidth.constant = 70;
[self.view layoutIfNeeded];
}
completion:^(BOOL finished){
NSLog(#"Done!");
[UIView animateWithDuration:0.1
delay:0.0
options:UIViewAnimationCurveLinear
animations:^{
self.btnLogin.clipsToBounds = YES;
self.btnLogin.layer.cornerRadius =self.btnLogin.bounds.size.height/2.0f; //or use ( self.login.bounds.size.height/2);
self.btnLogin.layer.borderColor=[UIColor redColor].CGColor;
self.btnLogin.layer.borderWidth=2.0f;
[self.view layoutIfNeeded];
}
completion:^(BOOL finished){
NSLog(#"Done!");
}];
}];
}
Output :
How about this?
[UIView animateWithDuration:1.0 animations:^{
CGRect frame = btn.frame;
CGPoint center = btn.center;
frame.size.width = btn.frame.size.height;
frame.size.height = btn.frame.size.height;
btn.frame = frame;
btn.center = center;
btn.layer.cornerRadius = frame.size.width/2;
btn.clipsToBounds = YES;
}];
This is what i get
And after a 1 sec animation i get this
If you want to get circle,just follow simple way
-(IBAction)actionClick:(id)sender
{
[UIView animateWithDuration:0.7f
animations:^
{
[sender setAlpha:0.5f];
}
completion:^(BOOL finished)
{
[sender setAlpha:1.0f];
button.layer.cornerRadius = button.frame.size.width / 2;
button.layer.borderColor=[UIColor redColor].CGColor;
button.layer.borderWidth=2.0f;
];
}
}

Animation of Views at ViewController Load

I am performing an animation when one of my ViewControllers loads. I am calling it in the viewDidAppear method. It looks like this.
[UIView animateKeyframesWithDuration:2.5f delay:0.0f options:0 animations:^{
[UIView addKeyframeWithRelativeStartTime:0.0f relativeDuration:1.0f animations:^{
CGRect rocketFrame = _rocketImageView.frame;
rocketFrame.origin.y = rocketFinishPosition;
_rocketImageView.frame = rocketFrame;
CGRect flameFrame = _flameImageView.frame;
flameFrame.origin.y = rocketFinishPosition + rocketFrame.size.height - 3;
_flameImageView.frame = flameFrame;
_flameImageView.hidden = false;
CGRect earthFrame = _earthImageView.frame;
earthFrame.origin.y = earthFinishPosition;
_earthImageView.frame = earthFrame;
CGRect moonFrame = _moonImageView.frame;
moonFrame.origin.y = moonFinishPosition;
_moonImageView.frame = moonFrame;
}];
[UIView addKeyframeWithRelativeStartTime:0.0f relativeDuration:0.6f animations:^{
_flameImageView.transform = CGAffineTransformMakeScale(1.f, 1.f);
}];
} completion:nil];
The issue is when I first load the ViewController, the animation plays but then when finished it snaps back to the starting position. However if I navigate away from the ViewController and back again, the animation plays and the changes persist.
My Question
Are my subviews being played out twice?
When can I be certain that my ViewController has finished loading?
Assuming not-autolayout, in the completion block, set the view properties to the final, desired states...
// taking the rocket frame as an example:
CGRect rocketFrame = _rocketImageView.frame; // do this with the other frames, too
[UIView animateKeyframesWithDuration:2.5f delay:0.0f options:0 animations:^{
[UIView addKeyframeWithRelativeStartTime:0.0f relativeDuration:1.0f animations:^{
rocketFrame.origin.y = rocketFinishPosition;
_rocketImageView.frame = rocketFrame;
CGRect flameFrame = _flameImageView.frame;
flameFrame.origin.y = rocketFinishPosition + rocketFrame.size.height - 3;
_flameImageView.frame = flameFrame;
_flameImageView.hidden = false;
CGRect earthFrame = _earthImageView.frame;
earthFrame.origin.y = earthFinishPosition;
_earthImageView.frame = earthFrame;
CGRect moonFrame = _moonImageView.frame;
moonFrame.origin.y = moonFinishPosition;
_moonImageView.frame = moonFrame;
}];
[UIView addKeyframeWithRelativeStartTime:0.0f relativeDuration:0.6f animations:^{
_flameImageView.transform = CGAffineTransformMakeScale(1.f, 1.f);
}];
} completion:^(BOOL finished) {
// do this with the other frames, too
_rocketImageView.frame = rocketFrame;
}];

Animation of UIButton decreases its size

Here is a small clip of a view being animated by me:
Animation flash
I am having problems when animation from ? to S and vice versa. I am setting the appropriate frames to go off screen, and then back on from the other size. Why does the size of the black button decrease with every move around it?
-(void)moveToRight
{
//just made this method up, but my actual code
//special case
if (currentDay == 7) {
//move off the screen to the right
CGRect offScreenRightFrame = CGRectMake(self.circle.frame.origin.x + 60, self.circle.frame.origin.y, self.circle.frame.size.width, self.circle.frame.size.height);
//move to the left of the screen so we can animate it in
CGRect offScreenLeftFrame = CGRectMake(-40, self.circle.frame.origin.y, self.circle.frame.size.width, self.circle.frame.size.height);
if (self.delegate) {
if ([self.delegate respondsToSelector:#selector(dayChangedTo:)]) [self.delegate dayChangedTo:[self getCurrentDay]];
}
[self pulseCircle];
[UIView animateWithDuration:0.25f delay:0.0f options:UIViewAnimationOptionCurveLinear animations:^{
self.circle.frame = offScreenRightFrame;
}completion:^(BOOL finished) {
if (finished) {
self.circle.alpha = 0.0f;
self.circle.frame = offScreenLeftFrame;
[UIView animateWithDuration:0.25f delay:0.0f options:UIViewAnimationOptionCurveLinear animations:^{
self.circle.center = self.labelSun.center;
self.circle.alpha = 1.0f;
}completion:^(BOOL finished) {
if (finished) {
[UIView animateWithDuration:0.25f animations:^{
[self changeColors];
[self pulseCircle];
}];
}
}];
}
}];
}
-(void)moveLeft
{
if (currentDay == 8) {
CGRect circleFrame = self.circle.frame;
CGRect offScreenLeft = CGRectOffset(circleFrame, -20, 0);
CGRect offScreenRightFrame = CGRectMake(self.labelQuestion.frame.origin.x + 30, self.labelQuestion.frame.origin.y, circleFrame.size.width, circleFrame.size.height);
[self pulseCircle];
[UIView animateWithDuration:0.25f delay:0.0f options:UIViewAnimationOptionCurveLinear animations:^{
self.circle.frame = frame;
}completion:^(BOOL finished) {
if (finished) {
self.circle.alpha = 0.0f;
self.circle.frame = frameFinal;
[UIView animateWithDuration:0.25f delay:0.0f options:UIViewAnimationOptionCurveLinear animations:^{
self.circle.center = self.labelQuestion.center;
self.circle.alpha = 1.0f;
}completion:^(BOOL finished) {
if (finished) {
[UIView animateWithDuration:0.25f animations:^{
[self changeColors];
[self pulseCircle];
}];
}
}];
}
}];
}
- (void)pulseCircle
{
__block UILabel *day = [self getLabelOfDay];
[UIView animateWithDuration:TRANLATE_DURATION/2 delay:0.0f options:UIViewAnimationOptionBeginFromCurrentState animations:^{
self.circle.layer.transform = CATransform3DMakeScale(1.35f, 1.35f, 1);
day.transform = CGAffineTransformMakeScale(1.35f, 1.35f);
}completion:^(BOOL finished) {
[UIView animateWithDuration:TRANLATE_DURATION/2 animations:^{
self.circle.layer.transform = CATransform3DIdentity;
day.transform = CGAffineTransformIdentity;
}];
}];
}
I just put my logic.. honestly i don't read/see your code/logic but may be my idea work for you ;)
So, I think.. you should add size of circle in public variable as default size. And set this size whenever you rich at.
if (currentDay == 0 || currentDay == 8)
{
// Here you need to set your default size.
}
May be my logic work for you, Because 1 to 7 day its work fine but issue created at when currentDay rich at 0 or 8 day.
Thanks :)

Slide UILabel Onto View Controller

I'm trying to move a UILabel from a starting position of off the top of my UIViewController in a smooth slide type animation so that it slides down from the top when the view loads and then stops at a y position for about 10 seconds. After 10 seconds I want to slide back off the view again.
-(void) animateInstructionLabel
{
float newX = 50.0f;
float newY = 100.0f;
[UIView transitionWithView:self.lblInstruction
duration:10.0f
options:UIViewAnimationCurveEaseInOut
animations:^(void) {
lblInstruction.center = CGPointMake(newX, newY);
}
completion:^(BOOL finished) {
// Do nothing
}];
}
But I don't know how to do the 10 second delay and then move back within the method above. The end result is that I want this to be a label that appears like a notification and then moves off screen again.
Could someone hep me put these pieces described above together?
EDIT
I am adding this based on answer below:
-(void) animateInstructionLabel
{
float newX = lblInstruction.frame.origin.x;
float newY = 20.0f;
lblInstruction.center = CGPointMake(lblInstruction.frame.origin.x, -20.0f);
lblInstruction.bounds = CGRectMake(lblInstruction.frame.origin.x, -20.0f, 650.0f, 40.0f);
[UIView animateWithDuration:3.0f
delay:0
options:UIViewAnimationCurveEaseInOut
animations:^(void) {
lblInstruction.center = CGPointMake(newX, newY);
}
completion:^(BOOL finished) {
[UIView animateWithDuration:5.0f
delay:5.0
options:UIViewAnimationCurveEaseInOut
animations:^(void) {
lblInstruction.center = CGPointMake(newX, -20);
}
completion:^(BOOL finished) {
// Do nothing
}
];
}
];
}
But even though I am setting the label.center off the screen before the animation starts I am seeing that it moves from top left corner of the viewcontroller to the center of the viewcontroller. It is not keeping the x position that it should be set to before the animation begins.
Try adding this after your animation block (oldX,oldY are the old coordinates, before animating the label in):
double delayInSeconds = 10.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[UIView transitionWithView:self.lblInstruction
duration:10.0f
options:UIViewAnimationCurveEaseInOut
animations:^(void) {
lblInstruction.center = CGPointMake(oldX, oldY);
}
completion:^(BOOL finished) {
// Do nothing
}];
});
Something like this should work:
-(void) animateInstructionLabel {
float newX = 50.0f;
float newY = 100.0f;
labelInstruction.center = CGPointMake(newX, -20); // start off screen
[UIView animateWithDuration:10.0f
delay:0
options:UIViewAnimationCurveEaseInOut
animations:^(void) {
lblInstruction.center = CGPointMake(newX, newY);
}
completion:^(BOOL finished) {
[UIView animateWithDuration:10.0f
delay:10.0
options:UIViewAnimationCurveEaseInOut
animations:^(void) {
lblInstruction.center = CGPointMake(newX, -20);
}
completion:^(BOOL finished) {
// Do nothing
}
];
}
];
}
Here is what worked in case it helps somebody else. I had to use .frame instead of .center to set the initial position and then the subsequent animation back to that position.
-(void) animateInstructionLabel
{
lblInstruction.frame = CGRectMake(lblInstruction.frame.origin.x, -40.0f, 650.0f, 40.0f);
[self.view addSubview:lblInstruction];
lblInstruction.hidden = NO;
float newX = lblInstruction.frame.origin.x;
float newY = 20.0f;
[UIView animateWithDuration:3.0f
delay:0
options:UIViewAnimationCurveEaseInOut
animations:^(void) {
lblInstruction.center = CGPointMake(newX, newY);
}
completion:^(BOOL finished) {
[UIView animateWithDuration:3.0f
delay:5.0
options:UIViewAnimationCurveEaseInOut
animations:^(void) {
lblInstruction.frame = CGRectMake(lblInstruction.frame.origin.x, -40.0f, 650.0f, 40.0f);
}
completion:^(BOOL finished) {
lblInstruction.hidden = YES;
}
];
}
];
}
This smoothly makes the label slide down from off the view, stop for 5 seconds and then smoothly moves it back vertically off the view.
Thanks for the previous answers which lead me to the above final solution.

Resources