I have two blocks of code, one working and the second not really. The first adds a view, constraints its height to 0 and then cancel this constraint in an animation block so it grows up. Works perfect. The second one is supposed to reactivate the 0 height constraint so that its height shrinks back to 0 and then remove it, but instead it is removed instantaneously. Here's the code:
self.pickupAddressViewModel.infoViewBlock = ^(MandatoryPickupView * _Nonnull pickupView) {
if (weakSelf.mandatoryPickupView) {
return;
}
weakSelf.mandatoryPickupView = pickupView;
[weakSelf.view addSubview:pickupView];
CGFloat gap = weakSelf.orderButton.originY;
[[pickupView.bottomAnchor constraintEqualToAnchor:weakSelf.view.bottomAnchor constant:-gap] setActive:YES];
[[pickupView.leadingAnchor constraintEqualToAnchor:weakSelf.view.leadingAnchor
constant:16.0] setActive:YES];
[[pickupView.trailingAnchor constraintEqualToAnchor:weakSelf.view.trailingAnchor
constant:-16.0] setActive:YES];
weakSelf.mandatoryPickupViewHeight = [pickupView.heightAnchor constraintEqualToConstant:0];
[weakSelf.mandatoryPickupViewHeight setActive:YES];
[weakSelf.view layoutIfNeeded];
[UIView animateWithDuration:0.5 animations:^{
[weakSelf.mandatoryPickupViewHeight setActive:NO];
[weakSelf.view layoutIfNeeded];
}];
};
self.pickupAddressViewModel.closeViewBlock = ^{
if (!weakSelf.mandatoryPickupView) {
return;
}
[weakSelf.view layoutIfNeeded];
[UIView animateWithDuration:10.5
animations:^{
[weakSelf.mandatoryPickupViewHeight setActive:YES];
[weakSelf.view layoutIfNeeded];
} completion:^(BOOL finished) {
[weakSelf.mandatoryPickupView removeFromSuperview];
weakSelf.mandatoryPickupView = nil;
weakSelf.mandatoryPickupViewHeight = nil;
}];
};
It's all happening on the main thread.
I tried settings the frame's height to 0 instead, also didn't work
weakSelf.mandatoryPickupViewHeight is a strong property and it is not nil when I activated the second time.
Any suggestions? Thanks!
When you are using autolayout for animations, you do it as follows:
Make sure autolayout is done:
self.view.layoutIfNeeded()
Then you change the constraints BEFORE the animation block. So for example:
someConstraint.constant = 0
Then after changing the constraint, you tell the autolayout that constraints have been changed:
self.view.setNeedsLayout()
And then you add an animation block with simply calling layoutIfNeeded():
UIView.animate(withDuration: 1, animations: {
self.view.layoutIfNeeded()
})
Notice that you don't deactivate the constraint - it has to be active the whole time.
I guess that in your code the problem is here:
[UIView animateWithDuration:0.5 animations:^{
[weakSelf.mandatoryPickupViewHeight setActive:NO];
[weakSelf.view layoutIfNeeded];
}];
Try changing it to
[weakSelf.mandatoryPickupViewHeight setActive:NO];
[weakSelf.view setNeedsLayout];
[UIView animateWithDuration:0.5 animations:^{
[weakSelf.view layoutIfNeeded];
}];
Although what you are doing seems a bit obscure. If the constraint is deactivated, are you sure the autolayout has enough constraints to calculate the proper height? If so, are you sure that mandatoryPickupViewHeight constraint is not in collision with those?
This is the methodology to shrink a view to zero height and to make it grows as height as of it's subviews.
-(void)manageViewCollapse
{
if(animationRunning)
{
return ;
}
animationRunning = YES;
if(supportShown)
{
// Hide the view by setting its height constant to zero
NSLayoutConstraint*full=[NSLayoutConstraint constraintWithItem:self.supportView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:0.0];
[self.supportView addConstraint:full];
// retrieve the bottom constraint of the view with bottom most item and remove it
for (NSLayoutConstraint*con in self.supportView.constraints)
{
if (con.firstAttribute == NSLayoutAttributeBottom && con.firstItem == self.bottomItemView)
{
[self.supportView removeConstraint:con];
break;
}
}
[UIView animateWithDuration:0.5 animations:^{
[self.view layoutIfNeeded];
}completion:^(BOOL finished){
supportShown = NO;
animationRunning = NO ;
} ];
}
else
{
// show the view first by deleting it's zero height
for (NSLayoutConstraint*con in self.supportView.constraints)
{
if (con.firstAttribute == NSLayoutAttributeHeight)
{
[self.supportView removeConstraint:con];
break;
}
}
// hook the bottom of the view again to the bottom most item to make it have the correct height
NSLayoutConstraint*full1=[NSLayoutConstraint constraintWithItem:self.bottomItemView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.supportView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-15.0];
full1.priority = UILayoutPriorityDefaultHigh;
[self.supportView addConstraint:full1];
[UIView animateWithDuration:0.5 animations:^{
[self.view layoutIfNeeded];
}completion:^(BOOL finished){
supportShown = YES;
animationRunning = NO;
} ];
}
}
Related
I am using this message to animate a customer view to the lower edge of the device screen. I've connected the layout constraint and set initial constant as -60. When the value is set to 0, I expect the view to animate to top. But unfortunately the expected functionality is not working. Even the method is called, the view is hoped up when interface orientation changes. I am using Xcode 6.4 and iOS 8 as base sdks. Any help will be appreciated.
if (alertVisible)
{
self.alertViewVerticalConstraint.constant = 0;
[UIView animateWithDuration:0.25 animations: ^{
[self.view bringSubviewToFront:self.alertView];
self.alertView.alpha = 0.5;
[self.alertView layoutIfNeeded];
}];
}
else
{
self.alertViewVerticalConstraint.constant = -60;
[UIView animateWithDuration:0.25 animations: ^{
self.alertView.alpha = 0.0;
[self.alertView layoutIfNeeded];
}];
}
Whenever you want to animate the constraint changes, you have to defines the line within the animation block.
So your code will become something like this,
if (alertVisible)
{
[UIView animateWithDuration:0.25 animations: ^{
self.alertViewVerticalConstraint.constant = 0;
[self.view bringSubviewToFront:self.alertView];
self.alertView.alpha = 0.5;
[self.view layoutIfNeeded];
}];
}
else
{
[UIView animateWithDuration:0.25 animations: ^{
self.alertViewVerticalConstraint.constant = -60;
self.alertView.alpha = 0.0;
[self.view layoutIfNeeded];
}];
}
I need to present a view controller at the centre of another view controller with some animation effect. I want the transition is reusable, so I defined a a class to implement the UIViewControllerAnimatedTransitioning protocol. I just simply add constrains to the subView to locate it to the centre, change the color of the container, and perform the animation:
-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
if (self.status == TransitioningStatusPresent) {
UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
UIView *containerView = [transitionContext containerView];
containerView.backgroundColor = [UIColor colorWithWhite:0.0f alpha:0.25f];
[containerView addSubview:toView];
toView.translatesAutoresizingMaskIntoConstraints = NO;
id c1 = [NSLayoutConstraint constraintWithItem:toView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:containerView
attribute:NSLayoutAttributeCenterX
multiplier:1.0f constant:0.0f];
id c2 = [NSLayoutConstraint constraintWithItem:toView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:containerView
attribute:NSLayoutAttributeCenterY
multiplier:1.0f constant:0.0f];
[containerView addConstraints:#[c1, c2]];
toView.alpha = 0.0f;
[UIView animateWithDuration:TRANSITION_DURATION animations:^{
toView.alpha = 1.0f;
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}else{
UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
fromView.alpha = 1.0f;
[UIView animateWithDuration:TRANSITION_DURATION animations:^{
fromView.alpha = 0.0f;
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
}
the code works if animated:YES.
However, it doesn't work as what I expect when without animation:
[self presentViewController:messageBoxViewController animated:NO completion:nil];
It simply because the function -animateTransition: will not be called when there is not animation. Therefore, I think I should not put the constrains in this function, but where should I put it to?
My App needs to be compatible to iOS 7, so presentation controller is not allow. But I need to access the container.
So How could I present the view controller with custom transitioning with the -presentViewController:animated:completion: method.
So how do I solve the problem?
Do not add constraints, try animating with frames. It will work. But if you make animated as NO then obviously it will never get executed.
If you facing many problems then what you can do it is, take the snapshot of sourceController and add that to your destinationController and then animate your view to the center. Make sure your animated property is false.
i am trying to change tableview frame by using autolayout in animationWithDuration block. Like That;
[UIView animateWithDuration:0.3f animations:^{
weekdayOffersVerticalConstraint.constant = 80;
[headerView layoutIfNeeded];
[weekdayTableView layoutIfNeeded];
self.isTableViewSmall = YES;
} completion:^(BOOL finished) {
}];
my tableview's frames are changing with this code block, but when i scroll to down, cells coming from left side to right side, i thought it is about with [weekdayTableView layoutIfNeeded]; layoutIfNeeded effect to all subviews. How can i obstruct to effect to my tableview's cells?
Sorry for my bad english, Thank you very much for your answers and advices.
Call layoutIfNeeded before you begin the animation to update the constraints:
[headerView layoutIfNeeded];
[weekdayTableView layoutIfNeeded];
[UIView animateWithDuration:0.3f animations:^{
weekdayOffersVerticalConstraint.constant = 80;
[headerView layoutIfNeeded];
[weekdayTableView layoutIfNeeded];
self.isTableViewSmall = YES;
} completion:^(BOOL finished) {
//
}];
You probably also don't want to set that BOOL property inside the animation block unless you have a method that overrides it intentionally during the animation.
I have the following tableview at the bottom of my view
It has a height constraint (priority 250) and a constraint to the bottom of the view (priority 1000). The height constraint points to a `IBOutlet in my view controller.
I want to change the height of the table view from 44.0f to 7*44.0f, so what I am doing is this;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (self.categoriesShown) {
[self hideCategories];
} else {
[self showCategories];
}
self.categoriesShown = !self.categoriesShown;
}
- (void)showCategories
{
self.categoriesHeightConstraint.constant = self.categories.count * 44.0f;
}
- (void)hideCategories
{
self.categoriesHeightConstraint.constant = 44.0f;
}
It works fine. But when I'm trying to animating all this with the following code:
- (void)showCategories
{
[self.categoryTableView layoutIfNeeded];
[UIView transitionWithView:self.categoryTableView duration:0.3f options:UIViewAnimationOptionCurveEaseOut animations:^{
self.categoriesHeightConstraint.constant = self.categories.count * 44.0f;
[self.categoryTableView layoutIfNeeded];
} completion:nil];
}
- (void)hideCategories
{
[self.categoryTableView layoutIfNeeded];
[UIView transitionWithView:self.categoryTableView duration:0.3f options:UIViewAnimationOptionCurveEaseOut animations:^{
self.categoriesHeightConstraint.constant = 44.0f;
[self.categoryTableView layoutIfNeeded];
} completion:nil];
}
Then the constraints between the tableview and the bottom of the view is somehow broken and this is what I get when I show and then hide the tableview
Does anyone know why, the constraint is broken but only when I try to animate the changes?
Update: UIButton Constraints
Both buttons have width and height constraints as well a a constraint to the bottom of the view. The button on the left has a leading constraint to the view. The one of the right has a trailing constraint to the view. The also both have a horizontal spacing constraint to the table view as mentioned above.
I think your problem is in this method call:
[UIView transitionWithView:self.categoryTableView duration:0.3f options:UIViewAnimationOptionCurveEaseOut animations:^{
self.categoriesHeightConstraint.constant = 44.0f;
[self.categoryTableView layoutIfNeeded];
} completion:nil];
This should do what you're looking for:
self.categoriesHeightConstraint.constant = 44.0f;
[UIView animateWithDuration:0.3f animations:^{
[self.view layoutIfNeeded];
//This is assuming the method is called from a view controller.
//You need to call layoutIfNeeded on the superview of what you're animating
}];
Also, Apple documentation says it's good form to make an extra [self.view layoutIfNeeded] call before you change the constraint so that any incomplete constraint changes and be updated. I'll leave that up to you though.
So I think I got the functionality you're looking for:
I gave the table view a height constraint that matched the top of the buttons, but it could be anything.
Then I get the calculated height the table should be and set it:
tableArray = [[NSMutableArray alloc]init];
[tableArray addObject:#"Row 1"];
[tableArray addObject:#"Row 2"];
etc.......
double newHeight = ([tableArray count] * 44);
CGRect newFrame = CGRectMake(basicTable.frame.origin.x, basicTable.frame.origin.y, basicTable.frame.size.width, newHeight);
[basicTable setFrame:newFrame];
And then for your animation I used [UIView animateWithDuration] as shown:
[UIView animateWithDuration:1.0 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
tableHgtConst.constant = newHeight;
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
}];
I just left the bottom constraint alone, then changing the height constraint to match the height forces the table up instead of down.
Screens:
Simulator: http://i.stack.imgur.com/T7MBr.png
Code File: http://i.stack.imgur.com/GGOkq.png
I am creating a custom NSLayoutConstraint subclass and I need to know if the layout constraint's constant property is currently animating for internal state handling. In other words, I need to distinguish between:
{ //no animation
myLayoutConstraint.constant = 100;
}
and
{ //animated
myLayoutConstraint.constant = 100;
[UIView animateWithDuration:0.2 animations:^{
[self.myViewThatHasTheConstraintAttached layoutIfNeeded];
} completion:^(BOOL finished) {
[...]
}];
}
So that I can handle corner cases for receiving a message on the middle of an animation. Is this possible?
The only way to do this would be to have a boolean wherever you want to access this and do something like...
{ //no animation
theView.animatingChange = NO;
myLayoutConstraint.constant = 100;
}
{ //animated
theView.animatingChange = YES;
myLayoutConstraint.constant = 100;
[UIView animateWithDuration:0.2 animations:^{
[self.myViewThatHasTheConstraintAttached layoutIfNeeded];
} completion:^(BOOL finished) {
[...]
theView.animatingChange = NO;
}];
}
The property on the view changes immediately to the "end" value of the animation. It doesn't get changed to all the intermediate values while it is animating. Just the drawing on the screen is animated.