How do I get a UIStackView to update its layout? - ios

I have a UIStackView with 5 subviews. I want to be able to animate the width of these subviews by updating their intrinsicContentSize or updating their constraints. How can I make the stackView redraw after updating the subview's layout?
UIView.animateWithDuration(0.3, animations: {
viewToResize.compressed = true //changes intrinsicContentSize
viewToResize.invalidateIntrinsicContentSize()
self.stackView.layoutIfNeeded() //makes no difference
anotherSubview.hidden = true //adding this makes everything work
})
I have tried various ways of trying to get the stackView to update, but nothing happens. The only way I can make it work is if I show/hide another subview inside the animation block, then the new layout is animated correctly, but I don't want to do this.
I have seen this question
Animating UIStackView arrangedSubview content size change
but the suggested answer does not work (nothing animates).

You don't need viewToResize.invalidateIntrinsicContentSize(), update constraint of your subviews instead. In the code below, we have two subviews in the stackView, and we create a widthConstraint outlet from storyboard. Then we can animate the width of the subview by updating the constant of widthConstraint in animation block. Note: If a view is contained in a stack view and you try to modify the view frame, it should not change
class ViewController: UIViewController {
#IBOutlet var widthConstraint: NSLayoutConstraint!
#IBOutlet var stackView: UIStackView!
var compressed: Bool = true
#IBOutlet var start: UIButton!
#IBAction func onStart(sender: UIButton) {
UIView.animateWithDuration(3.0,
delay: 0.0,
usingSpringWithDamping: 0.3,
initialSpringVelocity: 10.0,
options: .CurveLinear,
animations: { () -> Void in
//self.stackView.invalidateIntrinsicContentSize()
self.widthConstraint.constant = (self.compressed == false) ? 100.0 : 200.0
self.compressed = !self.compressed
self.view.layoutIfNeeded()
}, completion: nil)
}
For more details, you can have a look at this great blog UIStackView, Auto Layout and Core Animation

Related

Scroll down to gradually hide a menu bar or view and scroll up

I want to create a menu bar on top of a collectionview. When the user scroll down, the menu bar will gradually hide but while the user scroll up , the menu bar will appear immediately. The behavior similar to navigation bar's hidewhenswipe function. Is that any solution to create such behaviour on this menu bar? Thanks.
[screenshot]
Give your header view a height constraint if not given already. Then wired that constraint e.g.
#IBOutlet weak var headerViewHeightConstraint: NSLayoutConstraint!
Confirm your ViewController to UICollectionViewDelegate and UIScrollViewDelegate
set collectionView.delegate = self in ViewDidLoad()
UICollectionView is a subclass of UIScrollView so you can override the delegate method of scrollViewDidScroll and use the following code
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y > 50 {// the value when you want the headerview to hide
view.layoutIfNeeded()
headerViewHeightConstraint.constant = 0
UIView.animate(withDuration: 0.5, delay: 0, options: [.allowUserInteraction], animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}else {
// expand the header
view.layoutIfNeeded()
headerViewHeightConstraint.constant = 100 // Your initial height of header view
UIView.animate(withDuration: 0.5, delay: 0, options: [.allowUserInteraction], animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
}
I've been using HidingNavigationBarManager to do what you just described
and it is very easy to use. If you have a tableView in your ViewController then
it's as easy as adding these lines to your code.
var hidingNavBarManager: HidingNavigationBarManager?
...
...
override func viewDidLoad() {
super.viewDidLoad()
self.hidingNavBarManager = HidingNavigationBarManager(viewController: self, scrollView: tableView)
}

Animating collection view height change (Swift)

I am trying to animate the height change for a collection view, but I can't figure out how to get the animation to work. The height changes correctly, but it happens immediately, not animated.
#IBOutlet weak var collectionViewHeight: NSLayoutConstraint!
UIView.animateWithDuration(0.3, delay: 0, options: .CurveEaseOut, animations: {
self.collectionViewHeight.constant = 0
}
I tried searching for solutions, but couldn't find anything specific to this situation. Also tried layoutIfNeeded() suggested here, but it didn't help: Animate view height with Swift
Any help much appreciated!
You should update the constant of the constraint outside of the animation block:
self.collectionViewHeight.constant = 0
UIView.animateWithDuration(0.3, delay: 0, options: .CurveEaseOut,
animations: view.layoutIfNeeded, completion: nil)
(view is the superview of the collection view in question.)

Set back to original uilayoutconstraint - zoom in animation

Using a Storyboard, I have a height constraint with 2 size class (one for regular at 200pt, one for compact at 100pt).
Because I'm animating it when the view appears, the height of the element goes from 0 (initial state) -> 200pt for regular or 100pt for compact (final state).
It is a simple "zoomIn" animation.
But the thing is that because I change programmatically the constant, I'm losing the class sizes meaning when I rotate the phone, I have to set the constant to the right size instead of having Interface Builder's automatic class size.
So how would you apply an animation to an UIElement with auto-layout (and without having to create spaghetti code in viewWillLayoutSubviews, viewDidLayoutSubviews)?
Without your code, it's not clear exactly what you're doing, but here goes anyway :) First, the best way to animate when you are using autolayout is to animate the constraint changes, e.g.:
myConstraint.constant = myConstraintInitialConstant
UIView.animateWithDuration(animationSpeed) {
self.view.layoutIfNeeded()
}
The most important thing to note here is that the constraint change is outside the animation block, what you animate is layoutIfNeeded().
But you want to know what your initial constant was when the nib was loaded, yes? Then save it in viewDidLoad(), e.g.:
private var myConstraintInitialConstant: CGFloat = 65
override func viewDidLoad() {
super.viewDidLoad()
myConstraintInitialConstant = myConstraint.constant
}
Did you try to play with the layoutIfNeeded() / layoutSubviews() methods ? This will update the frame of your UIElement after added new constraints to it
I had a top constraint set to 0 and I animate it like that :
override func layoutSubviews() {
super.layoutSubviews()
// I reset my constraint's constant if already animate
if(topConst.constant > 0){
topConst.constant = 0
}
self.viewToAnimate.layoutIfNeeded()
//Animate the constraint
topConst.constant = 100
UIView.animateWithDuration(0.8, delay: 0.2, usingSpringWithDamping: 0.7, initialSpringVelocity: 0, options: .CurveEaseIn, animations: { () -> Void in
self.viewToAnimate.layoutIfNeeded()
}, completion: nil)
}
I used layoutSubviews it works fine :D

A view that animates upward without filling the screen

So this is what I am trying to do:
Here is the initial screen:
When the bottom pointing arrow is clicked there is a smaller view that transitions from bottom to top like this:
And as you can see the view in the back is dimmed. And when I click on the back view, the smaller sized view goes away by animating downwards.
There were several things that I tried:
1. I tried to segue modally which seemed to animate properly, namely, from bottom to top, but it covers the entire back view.
2. I tried to make the modal view only half the parent size by trying to replicate this post: Present modal view controller in half size parent controller. However, it did not work.
3. So I decided to put a UIView on top of my back view like so:
And I connected the grey colored view with the #IBOutlet weak var messageView: UIView!. And I tried using this code: UIView.transitionWithView(messageView, duration: 1.0, options: UIViewAnimationOptions.CurveEaseIn, animations: nil, completion: nil). However, nothing seems to be happening. Any suggestions on how to accomplish this?
For the Animation of button:
Add autolayout constraint to the BottomLayout Guide.And create an IBOutlet as
#IBOutlet weak var bottomConstraint: NSLayoutConstraint!
var shouldAnimateView:Bool = true
On the Action of the button you need to animate the View using constraint
#IBAction func showOrHideViewBtn(sender: AnyObject) {
if shouldAnimateView {
self.bottomConstraint.constant = self.view.frame.size.height/2+5
UIView.animateWithDuration(Double(0.2), animations: {
self.bottomConstraint.constant = self.view.frame.size.height/2 - self.toanimateView.frame.size.height
self.view.layoutIfNeeded()
})
}else{
self.bottomConstraint.constant = self.view.frame.size.height/2 - self.toanimateView.frame.size.height
UIView.animateWithDuration(Double(0.2), animations: {
self.bottomConstraint.constant = self.view.frame.size.height/2+5
self.view.layoutIfNeeded()
})
}
shouldAnimateView = !shouldAnimateView
}

How to animate a UIView with constraints in Swift?

In my contrived example, I have the following single view:
As you can see, it consists of a few simple constraints:
Align horizontal and vertical centers,
Height (set to a constant)
Leading and trailing spaces (set to constant)
What I'm seeking to achieve is to have this redish/pinkish view "come in" from the top. Traditionally, in a constraint-less world, I would simply modify the frame inside of UIView.animateWithDuration, however I'm not sure how I do the similar in a constraint world.
To reiterate my question, how can I make my view start out of scene and animate the view flying in from the top?
I've considered animating the vertical centers constraint (and subsequently calling layoutIfNeeded), but it's not achieving the desired effect.
Thanks for your help.
What you can do is to add the following code in your viewDidAppear method. You firstly make a IBOutlet property of your view's center Y constraint, and then change its constant value.
self.centerYConstraint.constant = 500.0
self.view.layoutIfNeeded()
UIView.animateWithDuration(Double(0.5), animations: {
self.centerYConstraint.constant = 0
self.view.layoutIfNeeded()
})
What you want is in fact rather simple, and I will detail how it should be done without messing with priorities or funky constants. This way we can get an animation that will work on any screen size.
The TL;DR is that you need to configure your constraints and then call layoutIfNeeded inside your animation block to animate the changes. For more see this SO post.
So we need two sets of constraints for hiding and showing the view. In viewDidLoad the view will be hidden, and then in viewDidAppear or wherever we want that view to slide in.
Storyboard/XIB Setup
So the first thing is to configure the constraints that won't be changing in IB (or code, whichever you are comfortable with). Namely; the height of the red view, and its leading and trailing space to the container. So your constraints might look like this (note: I haven't set a width constraint but let the leading and trailing spaces define the width):
Now you will see that IB will warn you that there is no constraint configured for the Y position of your view (hence the red dotted lines)
You can now add the top space to container constraint and set it as a placeholder constraint (checkbox that says "remove at build time"). We do this because we want to control where the view is programatically.
This means that this constraint will not exist once the view is loaded but serves to remove all your warnings as you are telling IB that you know how to deal with this constraint when the view is loaded.
Coding Time
Now we need a method to hide the view, a method to show the view. For this we are going to store the Y positioning constraint on the view and then animate it. Our viewController could look like this:
#IBOutlet weak var redView: UIView!
var redViewYPositionConstraint: NSLayoutConstraint?
override func viewDidLoad() {
super.viewDidLoad()
self.hideRedViewAnimated(false)
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.showRedViewAnimated(true)
}
Hiding
Our method to hide the view can simply remove the position constraint and then add one with the red views bottom equal to the view controller's view top:
func hideRedViewAnimated(animated: Bool) {
//remove current constraint
self.removeRedViewYPositionConstraint()
let hideConstraint = NSLayoutConstraint(item: self.redView,
attribute: .Bottom,
relatedBy: .Equal,
toItem: self.view,
attribute: .Top,
multiplier: 1,
constant: 0)
self.redViewYPositionConstraint = hideConstraint
self.view.addConstraint(hideConstraint)
//animate changes
self.performConstraintLayout(animated: animated)
}
Showing
Similarly our show constraint will move the view's center Y to the controllers center Y:
func showRedViewAnimated(animated: Bool) {
//remove current constraint
self.removeRedViewYPositionConstraint()
let centerYConstraint = NSLayoutConstraint(item: self.redView,
attribute: .CenterY,
relatedBy: .Equal,
toItem: self.view,
attribute: .CenterY,
multiplier: 1,
constant: 0)
self.redViewYPositionConstraint = centerYConstraint
self.view.addConstraint(centerYConstraint)
//animate changes
self.performConstraintLayout(animated: animated)
}
Convenience Methods
For completeness the convenience methods I have used look like this:
func performConstraintLayout(animated animated: Bool) {
if animated == true {
UIView.animateWithDuration(1,
delay: 0,
usingSpringWithDamping: 0.5,
initialSpringVelocity: 0.6,
options: .BeginFromCurrentState,
animations: { () -> Void in
self.view.layoutIfNeeded()
}, completion: nil)
} else {
self.view.layoutIfNeeded()
}
}
func removeRedViewYPositionConstraint() {
if redViewYPositionConstraint != nil {
self.view.removeConstraint(self.redViewYPositionConstraint!)
self.redViewYPositionConstraint = nil
}
}
You can also check if the red view is visible by using some CGRect math:
func isRedViewVisible() -> Bool {
return CGRectContainsPoint(self.view.bounds, self.redView.frame.origin)
}
Try this:
class ViewController: UIViewController {
// red view
#IBOutlet weak var myView: UIView!
// vertical align contraint
#IBOutlet weak var verticalConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
verticalConstraint.constant = (myView.bounds.height + self.view.bounds.height)/2
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.view.layoutIfNeeded()
UIView.animateWithDuration(0.5) {
self.verticalConstraint.constant = 0
self.view.layoutIfNeeded()
}
}
}
Be careful about firstItem and secondItem order in the constraint. The above code assumes Superview is the firstItem:
Alternative way is to define two constraints:
Center Y Alignment constraint (Superview.CenterY == View.CenterY) priority = 750
Vertical Space Constraint (SuperView.Top == View.Bottom) priority = 500
And adjust the priority to determine which contraint should be adopted.
class ViewController: UIViewController {
// vertical align contraint
#IBOutlet weak var centerYConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
centerYConstraint.priority = 250
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.view.layoutIfNeeded()
UIView.animateWithDuration(0.5) {
self.centerYConstraint.priority = 750
self.view.layoutIfNeeded()
}
}
}
For anyone looking for simple animation like the request:
For the slide up animation you need to use [...] the
CGAffineTransformMakeTranslation(x, y).
The reason is that for a slide up animation you need first move the
view off screen and then bring it back to its original position. So in
your viewDidLoad just use:
yourView.transform = CGAffineTransformMakeTranslation(0, 500)
This moves the view off screen, in this case at the bottom. Now in the
viewDidAppear you can show your view with:
UIView.animateWithDuration(0.7, delay: 0.0, usingSpringWithDamping: 0.5,
initialSpringVelocity: 0.5, options: [], animations: {
self.yourView.transform = CGAffineTransformMakeScale(1, 1)
}, completion: nil)
With these lines you can add the constraints that you want on your UIView and place it where you need in your ViewController.
For me the best way for animate Swift 3 & 4
self.constraint.constant = -150
UIView.animate(withDuration: 0.45) { [weak self] in
self?.view.layoutIfNeeded()
}

Resources