UIPageViewController views jump when resizing superview after swiping - ios

I have a view controller which adds a UIPageViewController to a subview (let's call it "overlay") programmatically. Since the page view controller's pages each have a different height, I added an NSLayoutConstraint on the "overlay" subview.
On every swipe I calculate the height of the upcoming view controller and resize the overlay view accordingly:
- (void)resizeOverlayToBottomOf:(UIView *)element {
// resize overlay in parent view controller so it shows the element
float bottomOfElement = element.frame.origin.y + element.frame.size.height + 20;
[self.parentViewController.overlayHeightConstraint setConstant:bottomOfElement];
[self.parentViewController.overlayView setNeedsUpdateConstraints];
[self.parentViewController.overlayView setNeedsLayout];
[self.parentViewController.overlayView layoutIfNeeded];
}
Everything works as expected... until I want to change the overlay size with an animation.
I wrapped the last two lines of the above method with an animation block:
[UIView animateWithDuration:0.25f animations:^{
[self.parentViewController.overlayView setNeedsLayout];
[self.parentViewController.overlayView layoutIfNeeded];
}];
Now after a swipe completes, the height of the overlay view changes with an animation.
The Problem is that while the animation plays, the page view controller's content snaps back to some place off screen and swipes back in. I tried adding constraints to ensure a fixed width of the page view controller content, but nothing seems to do the trick.
Any hint on how to animate the parent view without affecting the page view controller views will be highly appreciated!

I recently had the same problem. This apparently works:
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
{
if (completed)
{
CGFloat height = [self calculateHeight];
[CATransaction begin];
[CATransaction setCompletionBlock:^{
self.heightConstraint.constant = height;
[UIView animateWithDuration: 0.25
delay: 0
options: UIViewAnimationOptionCurveEaseInOut
animations:^{
[self.view layoutIfNeeded];
}
completion:^(BOOL finished) {
}];
}];
[CATransaction commit];
}
}
My understanding is that one animation interferes with the other.
CATransaction intercepts the current animation and will do it's completion block after the current animation is done.
This works fine for me.

Well, this bit of code did the trick:
[UIView animateWithDuration:0.25f animations:^{
// [self.parentViewController.overlayView setNeedsLayout];
// [self.parentViewController.overlayView layoutIfNeeded];
CGRect frame = self.parentViewController.overlayView.frame;
frame.size.height = bottomOfElement;
self.parentViewController.overlayView.frame = frame;
}];
The funny thing is, it does not seem to matter what value I use for frame.size.height, the constraint seems to be put in currently anyway.
If someone could explain why this works, I'd be delighted.

Related

How To Animate UIView Frame with ViewWillLayoutSubview

I have created two UIViews Programmatically and set its frame in ViewWillLayoutSubview function, Now I want to create a swapping animation so that each view swap each others position with animation on a click. For getting this animation I have to interchange its frame inside an animation but at the same time ViewWillLayoutSubview get called which sets frame to initial position.
How can I get my UIViews to be swapped with animation using ViewWillLayoutSubview?
I am not using any type of constraint or xib or storyboard. Whole screen is designed programatically.
Thanks in advance.
-(void)viewWillLayoutSubviews
{
if(Once)
{
// create 2 views here
once = NO;
}
}
put this code in button click
CGRect view2Frame = self.view2.frame;
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
//code with animation
self.view2.frame = self.view1.frame;
self.view1.frame = view2Frame;
} completion:^(BOOL finished) {
//code for completion
}];

How can I animate the height constraint of a UIPickerView?

I've learned that the way to animate constraints in Cocoa Touch is to just set them and then put [self.view layoutIfNeeded] in an animation block, like so:
self.someViewsHeightConstraint = 25.0;
[UIView animateWithDuration:0.5 animations:^{
[self.view layoutIfNeeded];
}];
This is working fine, for example with a simple UIView. However, it does not work with a UIPickerView. It just snaps into the new position without animating.
Any ideas why this might be the case? What ways are there to work around this?
The effect I'm going for is that the Picker View should shrink to just show the chosen item, as the user goes on to input other things. One idea I had is to make a snapshotted view and animate that instead, but I couldn't quite get that working either.
I found trying to animate the height or the placement constraint of a UIPickerView to be problematic. However, doing transforms seems to work well -- even if you have Auto Layout Constraints everywhere, including in the view to be transformed.
Here's an example of what works for me. In this case, I've placed the picker view inside a blurring effects view -- but you don't even need to put your picker view inside another view to animate it.
In the code below, when I call show, it animates up vertically. When I call the hide method, it animates downwards.
- (void)showPickerViewAnimated:(BOOL)animated;
{
__weak MyViewController *weakSelf = self;
[UIView animateWithDuration:(animated ? kPickerView_AppearanceAnimationDuration : 0.0)
delay:(animated ? kPickerView_AppearanceAnimationDelay : 0.0)
options:(UIViewAnimationOptionCurveEaseInOut)
animations:^{
weakSelf.pickerViewContainerView.transform = CGAffineTransformMakeTranslation(0,0);
}
completion:^(BOOL finished) {
[weakSelf.view layoutIfNeeded];
}];
}
- (void)hidePickerViewAnimated:(BOOL)animated;
{
__weak MyViewController *weakSelf = self;
[UIView animateWithDuration:(animated ? kPickerView_DisappearanceAnimationDuration : 0.0)
delay:(animated ? kPickerView_DisappearanceAnimationDelay : 0.0)
options:(UIViewAnimationOptionCurveEaseInOut)
animations:^{
weakSelf.pickerViewContainerView.transform = CGAffineTransformMakeTranslation(0, kPickerView_Height);
}
completion:^(BOOL finished) {
[weakSelf.view layoutIfNeeded];
}];
}
picker view, If you have added constraint To TopLayout for yPosition remove it and add constraint to bottom layout instead.
That will solve the problem. here is my code and its working:
self.timePickerHeightConstraint.constant = pickerIsClosed ? 216 :
0;
[UIView animateWithDuration:0.5 animations:^{
[self.view layoutSubviews];
} completion:^(BOOL finished){
}];

iOS view appears before animation

I'm trying to maintain existing code and added a new UITextView to a View Controller. All of the views enter the screen by sliding in from the left side. My newly added view also slides in from the left side along with the other views (without me doing anything because the animation is set on their container). The problem is that my view also appears for a second or so before the animation starts. How can I prevent this from happening?
Here is the code for the animation on the container
- (void) showAndAnimateContainerView{
[self.containerView setHidden:NO];
[self hideContainerView:self.containerView];
self.marginLeft.constant = 0;
[UIView animateWithDuration:STD_ANIMATION_TIME delay:ZERO_ANIMATION_TIME options:UIViewAnimationOptionCurveLinear animations:^{
[self.containerView layoutIfNeeded];
} completion:^(BOOL finished) {
}];
}
- (void) hideContainerView:(UIView *) view{
self.widthView.constant = WIDTH_SCREEN;
self.marginLeft.constant = -WIDTH_SCREEN;
[view layoutIfNeeded];
}
Don't know what else I can add to the question to make it more clear.

UIView animation happens in the wrong direction

For some reason my UIImageView animates to its start position, from its end position. I can’t work out why but have a feeling it’s something to do with using constraints (I use constraints on all UIViews throughout my app).
My animation should animate logoImageView off the top of the screen.
Here is my code:
NSLog(#"%#", NSStringFromCGPoint(self.logoImageView.center));
[self.logoImageView updateConstraintsIfNeeded];
[UIView animateWithDuration:3.0f
animations:^{
[self.logoImageView setCenter:CGPointMake(CGRectGetMidX(self.logoImageView.frame),
CGRectGetMaxY(self.view.frame) + CGRectGetHeight(self.logoImageView.frame))];
[self.logoImageView updateConstraintsIfNeeded];
} completion:^(BOOL finished) {
NSLog(#"%#", NSStringFromCGPoint(self.logoImageView.center));
[self performSegueWithIdentifier:#"Play" sender:nil];
}];
Here is my log of positions:
2014-09-05 18:08:56.673 Cups[2142:89629] {160, 111.5}
2014-09-05 18:09:01.250 Cups[2142:89629] {160, 645}
But the animation plays the other way round. Any ideas?
This probably has something to do with the fact that you're calling -updateLayoutConstraints without actually updating constraints.
A better approach would be to animate the constraints rather than setting the position and frames of your views. A good trick to remember is that you can create outlets in your XIB to your constraints.
Let's assume you have an NSLayoutConstraint called logoImageViewTopConstraint set up as an IBOutlet in your view controller which sets the top of the logoImageView to it's superview's top layout guide. At design time set that constraint's constant to 100 or so (moving it down 100 points from the top of the view). Then with that constraint you could do something like this for your animation:
[self.view layoutIfNeeded];
self.logoImageViewTopConstraint.constant = 0; // moves image view back to the top
[self.view setNeedsUpdateConstraints];
[UIView animateWithDuration:0.8 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
[self.view layoutIfNeeded]; // interpolates animation from changes to constraints
} completion:^(BOOL finished) {
// done
}];
If you get it working just play with the constants of the constraints until you get the exact effect that you want.
What happens if you do this:
[UIView animateWithDuration:3.0f
animations:^
{
[self.logoImageView setCenter:CGPointMake(CGRectGetMidX(self.logoImageView.frame),
-(CGRectGetHeight(self.logoImageView.frame)/2)];
} completion:^(BOOL finished)
{
[self performSegueWithIdentifier:#"Play" sender:nil];
}];
animate to y center = -(logoHeight/2). This is "off the top of the screen" in iOS coordinates
don't call updateConstraints in animation block
It turns out a CABasicAnimation running on the layer of the UIView was interrupting the UIView animation. Thanks for suggestions

How to resize a view mid-way during a moving animation?

During a sliding animation(down, pause, then up back to the original position) of a subview, the device is rotated, so the superview is rotated. I want to keep the subview's width the same as the superview, so I need to resize it during its sliding animation.
Here is the sliding animation code:
[UIView animateWithDuration:0.3 animations:^{
self.frame = finalFrame;
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 delay:3 options:0 animations:^{
self.frame = initFrame;
} completion:^(BOOL finished) {
[self removeFromSuperview];
}];
}];
This is the method that is called when I detect rotation:
- (void)rotate:(NSNotification *)notif {
// What to do here to adjust the width but also keep the sliding animation going.
}
It seems there is no auto-resizing magic that can be used here. One must:
Record the progress of the animations.
On detection of rotations, cancel the old animations, adjust the view size, and add new animations starting from the current progress.
Here is a sample project for reference: http://d.pr/f/M4UW.
You can animate the bounds of the layer to change the width. Just make the height the same and apply an animation for the bounds.
If you want both animations to have the same duration, timing function etc. then you could add them both to an animation group and add that group to the layer you are animating.

Resources