UIView frame animation not updating subviews layout - UIViewPropertyAnimator - ios

I am trying to to a simple frame animation with UIViewPropertyAnimator. If I animate the frame, it works great.
The problem arrives when you add subviews with autolayout constraints. Subviews frames are calculated at the beginning of the animation and changed immediately instead of being done as the animation progresses.
Any idea on how to animate subViews as the view frame changes?
let frameChange = UIViewPropertyAnimator(duration: duration, curve: .easeInOut) {
animatableCustomView.frame = finalFrame
}
frameChange.startAnimation()
My custom view is just a view with an ImageView inside constrained to all 4 edges to its superview.

Related

Keep UIScrollView scrolled to bottom

I need somehow to keep scrollview always scrolled to bottom while its height decreases.
It's hard to solve while bounds change animated. Is there some "gravity"-property for purpose like that?
You can achieve this behaviour by using a non-animated version of setContentOffset within the same animation block you use for resizing the UIScrollView — in this case both changes will be animated simultaneously. Something like:
UIView.animateWithDuration(0.5, animations: { () -> Void in
let delta: CGFloat = 100
/// Change UIScrollView's height with some delta value, for instance:
self.scrollView.frame.size.height -= delta
/// Update content offset with the same delta value:
self.scrollView.contentOffset.y += delta
})
Try using setContentOffset:animated: to scroll to the bottom every time you shrink the UIScrollView's frame.
You are decreasing the height of scrollview with animation. You can use setContentOffset:animated: in the animation completion block. It will keep the scrollview always scrolled to bottom.

UIScrollView and PanGestureRecognizer

I’m have a view that contains a regular UIView and a UIScrollView. The UIView sits above the UIScrollView offscreen. The UIScrollView typically occupies the entire screen. (It should be noted that I’m not using Autolayout). When the user scrolls to the top of the scrollview content I would like the UIView to start appearing on the screen. And when it reaches a certain threshold have the UIView snap into place and occupy the screen.
My initial thought was to use the UIScrollView delegate method, and adjust the superview.frame.orgin.y value when the scrollview contentOffset.y value is negative.
func scrollViewDidScroll(scrollView: UIScrollView) {
pullDownInProgress = scrollView.contentOffset.y <= 0.0
if pullDownInProgress {
self.view. = (-self.view.height / 2) - scrollView.contentOffset.y
}
}
However, this creates a stretching between the UIView and the UIScrollView due the scrollview bounce setting. If I turn off the bounce setting then the scrollview.contentOffset is never less then zero, therefore my superview frame is never adjusted.
Any suggestions on how to accomplish this?
You don't need to change the superview.frame, instead move the offscreen view down by its height so that it can appears and any bouncing effect for the scroll view might be hidden by that view.Or you can even move both the scroll view and the offscreen view with the height of the offscreen view. It really depends whether your offscreen view is transparent or not

Animate subview on UIViewController view

I have UITableView as a subview over UIView (parent view) of UIViewController. When i'm animating my parent view as below:
UIView.animateWithDuration(duration, animations: {
viewController?.view.frame = CGRectMake(0,0,320,568)
}, completion: {_ in
})
I observed that my subview (table view) is not animating. I tried setting Autoresizing Mask of subviews to flexibleWidth and flexibleHeight, but din't get any success. Anybody having any idea why its happening.
If you are using auto layout (which is the default in iOS 8/9 on Xcode 6/7) you need to change the constraints of your view inside he UIView animation closure, instead of changing the frame.
If you are not using auto layout, then you can update the frame as above, however, I'd imagine you are. Thus you need to call layoutSubviews, then programatically set new constraints that represent your desired change to the layout, and finally call layoutSubviews again, inside the animation closure.
Example:
func AnimateBackgroundHeight() {
self.view.layoutSubviews()
UIView.animateWithDuration(0.5, animations: {
self.heightCon.constant = 600 // heightCon is the IBOutlet to the constraint
self.view.layoutSubviews()
})
}

Animating a view's height with autolayout when there's no height constraint

I have a view (let's call it "contentView") that its height is determined by its subviews, which is mostly UILabels.
The UILabels content is dynamic and may grow or shrink at runtime.
The UILabels have numberOfLines = 0, and no height constraint, which allow their height to grow with the content.
The UILabels have 0 leading/trailing/top/bottom constraints to their neighbors and/or superview, as follows:
The result is having contentView's height equal the total height of all its sub UILabels (with their height being determined by the height of their text).
I now need to hide contentView on a press of a button (and show it again when the button is pressed again).
Also note, that while animating, if there are other views below "contentView" they need to move in to take the empty space (and move back out when the button is pressed again).
It seems the most natural animation is to change the height of contentView to 0, while animating the views under contentView up at the same time (with the reverse animation changing the height back to its original height).
Any suggestions on how to animate contentView's height?
Unfortunately I was not able to create a smooth enough animation for this layout, but I found a workaround.
Constraints change the layout while animating, and I needed the opposite effect - the layout had to remain fixed while the animation is in progress.
So I disabled the constraints for the duration of the animation, and that seemed to work well.
The full recipe is:
I disabled the constraints on contentView:
contentView.translatesAutoresizingMaskIntoConstraints = YES
I then did a simple frame animation. I made sure to calle layoutIfNeeded on superview to make sure the view that's under contentView moves up during the animation:
CGRect frame = view.frame;
frame.size.height = 0;
[[view superview] layoutIfNeeded];
[UIView animateWithDuration:0.5 animations:^{
view.frame = frame;
[[view superview] layoutIfNeeded];
}];
I also had to change some properties for the subviews to have them animate correctly (this step might not be needed. It depends to the base layout of the subviews):
for UITextView: scrollEnabled = YES
for UIButton: clipsToBounds = YES
At the end of the animation, on the completion block, I set contentView.translatesAutoresizingMaskIntoConstraints back to NO and restored the subviews properties:
for UITextView: scrollEnabled = NO
for UIButton: clipsToBounds = NO
(I ended up using UITextViews instead of UILabels because I needed the text to be selectable. I then set scrollEnabled to NO to size the textviews based on their content).
Yes: add a height constraint with a constant of 0.
You can use deactivateConstraints() to temporarily disable other constraints that might conflict with this, and activateConstraints() to re-enable them.
You could put the relevant constraints in an array programmatically, or use an outlet collection if you're using a storyboard.

"Magic Move" Effect Custom Transition

I trying to get a custom transition between two View Controllers. First, here's a picture to illustrate what I want :
I want a UICollectionViewCell to expand to the whole screen. In this cell, the subviews are placed with Autolayout in IB.
I just want each subview to go to the new position. So I tried subview.frame = newSubview.frame in the animation block, but it doesn't work (because of Autolayout I think).
I thought to delete the constraints while the animation is occuring, but it doesn't work.
I also tried to make #IBOutlets of the constraints and to change constant property.
Here's my code :
let detailView = detailViewController.view
let cellView = self.selectedCell
container.addSubview(cellView!)
let duration = self.transitionDuration(transitionContext)
UIView.animateWithDuration(duration, delay: 0.0, options: .CurveEaseInOut, animations: {
let newFrame = detailViewController.view.frame
cellView!.frame = newFrame
cellView!.imageView.frame = newFrame
cellView!.labelTopConstraint.constant = 27
cellView!.labelRightConstraint.constant = 8
cellView!.layoutIfNeeded()
}
...
Actually, when the animation begins the labels snap to a position, then they move and at the end they are not at the right position...
Any ideas ? Thanks
Check out this repo, I think this is what you are looking for BCMagicTransition: https://github.com/boycechang/BCMagicTransition
The issue with your code is that you aren't converting the frames of your subviews of your collection view cell that you want to animate to the coordinates of the container view controller of the transition, and same with the final frames for their positions. Even though you are using AutoLayout you can still manipulate views with their frames.
To accomplish this, checkout this method (documentation):
func convertRect(rect: CGRect, fromView view: UIView?) -> CGRect
You also should look into animating snapshots of the views you wish to animate. Then just add the snapshots to the container view, and animate them to the converted final frames, then remove them when the animation is complete.
Hope this helps !

Resources