Setup: have a containerView on screen inside a UIViewController (for the sake of simplicity let's say containerView takes up the whole screen).
Problem: create and add an overlayView as a subView to containerView and animate the appearance so that it would animate into position from the right as a response to a user action.
UIView *overlayView = [[UIView alloc] initWithFrame:containerView.frame];
overlayView.translatesAutoresizingMaskIntoConstraints = NO;
[containerView addSubview:overlayView]; // Can't add constraints before inserting into view hierarchy
Approach: tie the leading edge of the overlayView to the leading edge of the containerView with a constraint that I'll call overlayLeadingConstraint. Set overlayLeadingConstraint.constant to the width of containerView so that it would be initially positioned just off screen to the right.
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|[overlayView(width)]" options:0 metrics:metrics views:views];
[containerView addConstraints:constraints];
NSLayoutConstraint *overlayViewLeadingConstraint = [constraints objectAtIndex:0];
overlayViewLeadingConstraint.constant = containerView.frame.size.width;
// Height constraint not shown for simplicity
Animation: now onto the real problem; animate overlayView into position from the right. First approach is something like this:
overlayViewLeadingConstraint.constant = 0.0;
[UIView animateWithDuration:1.0 animations:^{
[containerView layoutIfNeeded];
}];
But this does not work as all the code above would execute on the same run loop and so only the end result would be shown.
Second approach: try to defer the animation to a future run loop until after the initial layout after addSubview: has already taken place.
dispatch_async(dispatch_get_main_queue(), ^{
overlayViewLeadingConstraint.constant = 0.0;
[UIView animateWithDuration:1.0 animations:^{
[containerView layoutIfNeeded];
}];
});
This does not work either and it hints that addSubview: and the setup of constraints can take up multiple run loops.
Third approach: try to delay the animation even further: several run loops into the future.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
overlayViewLeadingConstraint.constant = 0.0;
[UIView animateWithDuration:1.0 animations:^{
[containerView layoutIfNeeded];
}];
}];
The time delay is small, but it allows several run loops to complete before the animation and this seems to achieve the desired effect.
Questions:
The above approach seems to be a workaround and not the real solution. So I'm wondering if there is a better approach to this. I have thought about using the hosting viewController's viewDidLayoutSubviews method to get to know when overlayView is in place and it's ok to fire up the animation, but the documentation explicitly advises against this:
However, this method being called does not indicate that the individual layouts
of the view's subviews have been adjusted. Each subview is responsible for
adjusting its own layout.
I'm starting to think that Apple's idea was to have all subViews added at initialization time and just hide those that you don't need immediately. So that when the time comes to animate a subView it would already be a member of the view hierarchy tied up properly with constraints.
How would you do it? Any input is much appreciated.
I'm not sure what you're doing with some of the things you don't show, but this code, worked fine for me. I made a view in IB (containerView) that was 200 x 200, and used this code,
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
UIView *overlayView = [UIView new];
overlayView.backgroundColor = [UIColor redColor];
overlayView.translatesAutoresizingMaskIntoConstraints = NO;
[containerView addSubview:overlayView];
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|[overlayView]|" options:0 metrics:nil views:#{#"overlayView":overlayView}];
NSArray *constraints2 = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[overlayView]|" options:0 metrics:nil views:#{#"overlayView":overlayView}];
[containerView addConstraints:constraints];
[containerView addConstraints:constraints2];
NSLayoutConstraint *overlayViewLeadingConstraint = [constraints objectAtIndex:0];
overlayViewLeadingConstraint.constant = containerView.frame.size.width;
[containerView layoutIfNeeded];
overlayViewLeadingConstraint.constant = 0.0;
[UIView animateWithDuration:1.0 animations:^{
[containerView layoutIfNeeded];
}];
}
This animated correctly. Notice that I put in a call to layoutIfNeeded before the animation block. That may or may not be necessary in your case, depending on some aspects of your code that you didn't show.
I don't know if using [constraints objectAtIndex:0] to get the constraint you want to modify is safe; it worked in this case, but I don't know if the order of the constraints set up with the visual format is guaranteed.
You have not added anything to the animations block that will animate. The animations block is generally considered to be the "I want my UI to end up in this state; animate stuff to make that happen" block.
[UIView animateWithDuration:1.0 animations:^{
overlayViewLeadingConstraint.constant = 0.0;
}];
Related
I need to change size and position of a subview (call it playerView) of a view of a view controller. I have created properties for constraints I need to change. But when I animate the changes, only playerView is animated, but all of it's subviews are not: their size is changed instantly.
Here's the text:
self.playerViewTop.constant = screenHeight - MinizedPlayerTopOffset;
self.playerViewLeading.constant = MinimizedPlayerOffset;
self.playerViewWidth.constant = MinimizedPlayerWidth;
self.playerViewHeight.constant = MinimizedPlayerHeight;
[UIView animateWithDuration: 2.0 animations: ^
{
[self.view layoutSubviews];
}];
I do not understand why that happens since I use autoLayout. Please help.
You need to call layoutIfNeeded() in the animations block, not layoutSubviews().
(You should actually never call layoutSubviews() yourself. It's a method called by the system during a layout pass and only intended for that purpose.)
[self.view layoutIfNeeded];
[self.subView setNeedsUpdateConstraints];
[self.subView updateConstraintsIfNeeded];
[UIView animateWithDuration:1.0f delay:0.0f
options:UIViewAnimationOptionLayoutSubviews animations:^{
[self.view layoutIfNeeded];
} completion:nil];
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 have a UIScrollView placed inside of a ViewController to which I have given AutoLayout Constraints and all of them are satisfied.
The scrollView contains around 20 UIButton subviews. I have given autolayout constraints to them also and they all are also satisfied.
What I need to do is I want to animate those buttons when the view is first launched. This is my code for animation
for (UIButton *button in [scrollView subviews]) {
CGRect mainFrame = [button frame];
[UIView animateWithDuration:0.0 animations:^{
button.frame = CGRectMake(button.center.x, button.center.y, 0, 0);
} completion:^(BOOL finished) {
[UIView animateWithDuration:1 delay:0.5 usingSpringWithDamping:0.3 initialSpringVelocity:0.2 options:UIViewAnimationOptionCurveEaseOut animations:^{
button.frame = mainFrame;
} completion:^(BOOL finished) {
}];
}];
}
The subviews are not animating when I use this code.
But when I set the
[button setTranslatesAutoresizingMaskIntoConstraints:YES];
it works. But this time I get the error Unable to Simultaneously Satisfy constraints.
Is there any other method to do it or should I just ignore the error message and just go with it?
Don't modify the frames when using Auto Layout.
You should modify the constraints to achieve the animation and call [yourView layoutIfNeeded] in the animation block.
It works by setting setTranslatesAutoresizingMaskIntoConstraints because then setting the frame causes constraints to be created. These finally conflict with your already existing constraints and cause the constraint warning.
Alternatively you can use the view's transform property and animate that one to achieve your desired effect, since I think you only want something like a presentation animation. Then you don't have to modify the constraints.
You would do something like:
[UIView animateWithDuration:1.0 animations:^{
button.transform = CGAffineTransformMakeTranslation(10.0, 10.0);
}];
You should animate the values for the constants in the constraints instead of the frame for each button. Create internal properties for each constraint you would like to animate and then use UIView's animateWithDuration. For example:
self.myConstraint.constant = 10;
[button setNeedsUpdateConstraints];
[UIView animateWithDuration:1 animations:^{
[button layoutIfNeeded];
}];
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'm making a layout editor for a comic book app. The page of the comic book is divided into rows and then each row has at least one column. When the user performs a vertical swipe over a column, it's supposed to divide into two separate columns. This part already works fine, but I wanted to add a simple curl-up transition for that using +transitionWithView:. Everything still works fine (the column divides nicely) but there's no animation. Here's the code:
- (void)divideColumnView:(CCLayoutColumnView *)columnView atPoint:(CGPoint)_divisionPoint {
// divides a single column into two smaller ones
// define frames for new columns
CGRect frameLeft = columnView.frame;
CGRect frameRight = frameLeft;
CGFloat width = _divisionPoint.x - frameLeft.origin.x;
frameLeft.size.width = width;
frameRight.size.width -= width;
frameRight.origin.x += width;
// set up the container for transition
UIView *rowView = [columnView superview];
UIView *containerView = [[UIView alloc] initWithFrame:columnView.frame];
[rowView addSubview:containerView];
// move the column to the container
columnView.frame = [containerView convertRect:columnView.frame fromView:rowView];
[containerView addSubview:columnView];
// set up columns that will be inserted to the container during transition
CCLayoutColumnView *leftColumnView = [[CCLayoutColumnView alloc] initWithFrame:[containerView convertRect:frameLeft fromView:rowView]];
CCLayoutColumnView *rightColumnView = [[CCLayoutColumnView alloc] initWithFrame:[containerView convertRect:frameRight fromView:rowView]];
[UIView transitionWithView:containerView
duration:0.5
options:UIViewAnimationOptionTransitionCurlUp
animations:^{
[columnView removeFromSuperview];
[containerView addSubview:leftColumnView];
[containerView addSubview:rightColumnView];
}
completion:^(BOOL finished){
// add columns to their row
leftColumnView.frame = frameLeft;
rightColumnView.frame = frameRight;
[rowView addSubview:leftColumnView];
[rowView addSubview:rightColumnView];
// clean up
[containerView removeFromSuperview];
[containerView release];
[leftColumnView release];
[rightColumnView release];
}];
}
I already checked the geometry of all views and it seems ok. Also, the completion block is executed properly.
What may be the problem?
I was also stuck at animating views with transitionWithView.
I don't know the hierarchy of your project, but if it is possible for you to create a separate class for the view you want to animate with this, it would be easy:
[UIView transitionWithView:self.view duration:1.0 options:UIViewAnimationOptionTransitionFlipFromLeft animations:^{} completion:^(BOOL f){}];
works..i.e. this function works properly with self.view.
I hope it helps.