UIView animation with autolayout and child views - ios

I have a weird problem that seems to be fairly easy to solve. I think I could use some workaround to be able to have the behavior that I want but I want to know if there is a better way to do it.
I have a view called contentView (blue in the image) that it will expand its height using a UIView.animation, this is no problem at all and works as expected.
The problem here is that this view has a child component (a button) that has an autolayout constraint to its bottom equal to 22 like this:
This is the autolayout constraint:
If I do the height resize without the animation it works fine, the view height change and the button are always 22 points of the bottom of the contentView. But when I use an animation to make this change more smoothy and user-friendly the button moves to the end position before the animation even start.
I want to know how I could achieve a smooth animation here but with the button moving along its parent view
The part of the code that handles the animation is pretty straightforward but I'll post it in here:
#IBAction func openDetail(_ sender: ExpandCourseDetail) {
let rotation = sender.getOpen() ? CGAffineTransform.identity : CGAffineTransform(rotationAngle: CGFloat.pi)
UIView.animate(withDuration: 0.5, delay: 0.1, options: [.curveEaseInOut], animations: {
sender.transform = rotation
}, completion: {
success in
sender.setOpen(!sender.getOpen())
})
UIView.animate(withDuration: 1.0, delay: 0.5, options: [.curveEaseInOut], animations: {
self.contentView.frame.size.height = sender.getOpen() ? self.contentView.frame.height - 300 : self.contentView.frame.height + 300
}, completion: nil)
}
As a side note, the button itself has an animation that rotates the button 180 degrees to show the user that the view is expanding.
Thank you so much for your help.

It's super easy with constraints, just create a superView height constraint IBOutlet and change its constant value.
#IBAction func btnPressed(_ sender: UIButton) {
self.toggleButton.isSelected = !sender.isSelected
//Animation starts here
self.view.layoutIfNeeded()
UIView.animate(withDuration: 0.7) {
if self.toggleButton.isSelected {
//transform button
self.toggleButton.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi))
//change your ParentView height to desired one
self.constContentViewHeight.constant = self.view.frame.size.height - 220
self.view.layoutIfNeeded()
} else {
self.toggleButton.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi*2))
// Set height constraint to original value
self.constContentViewHeight.constant = 250
self.view.layoutIfNeeded()
}
}
}
I have created a demo, check it out.

The issue you are facing is due to two animation blocks. So I have changed some lines and put both button transformation and height animation into one animation block.
func openDetail(_ sender: ExpandCourseDetail) {
let isOpen = sender.getOpen() ? CGAffineTransform.identity : CGAffineTransform(rotationAngle: CGFloat.pi)
UIView.animate(withDuration: 1.0, delay: 0.5, options: [.curveEaseInOut], animations: {
sender.transform = rotation
self.contentView.frame.size.height = self.contentView.frame.height + (isOpen ? 300 : -300)
}, completion: { (success) in
sender.setOpen(!isOpen)
})
}

Related

updating width of custom view is not going in the right direction

I've created a custom view. added width, height, and centerY constraints.
I used it in a storyboard.
I want to add a label, increase the width animately. and after 1.5 seconds I need to remove the label and decrease the width animatedly.
I wrote the below code inside my custom view.
fileprivate func unfoldMeAnimatedly() {
self.labelLeadingToStatusImageView.isActive = true
self.updateStatusLabel()
self.layoutIfNeeded()
self.constraints.forEach { (constraint) in
if constraint.identifier == "widthOfCardDraftStatus" {
constraint.constant = 95
}
}
UIView.animate(withDuration: 0.6, delay: 0.0, options: UIView.AnimationOptions.curveLinear, animations: {
self.layoutIfNeeded()
}) { (finished) in
}
}
fileprivate func foldMeAnimatedly() {
self.constraints.forEach { (constraint) in
if constraint.identifier == "widthOfCardDraftStatus" {
constraint.constant = 24
}
}
UIView.animate(withDuration: 0.5, delay: 0.0, options: UIView.AnimationOptions.curveLinear, animations: {
//self.statusLabel.alpha = 0.0
self.layoutIfNeeded()
}) { (finished) in
}
}
invoking animations
unfoldMeAnimatedly()
DispatchQueue.main.asyncAfter(deadline: .now()+1.5) {
self.foldMeAnimatedly()
}
privew of result:
I did not understand why it is moving. the desired one is just the width needs to be increased/decrease at only one end. i.e right side.
Few observations:
It's strange that I changed the animation durations to 0.5, 0.5 and wait time to 0.5. now it is working as expected.
Why like this? Anybody can explain why it's behaving like this or some issue with code?

UIView animation that mimics presentViewController easing

I have a view which animates from the bottom of my ViewController, and I'm trying to get the animation to be the same as the default
present(viewController, animated:true, completion:nil)
I want to animate a view with the same easing and timing as the above code. Right now I'm using this:
UIView.animate(withDuration: 0.35, delay: 0, options: .curveEaseInOut, animations: {
self.myView.frame = newFrame
}, completion: nil)
The animations aren't similar though, and I don't know what values to use in my UIView animation to make it look like a viewController present. Anyone know what values could get me close?
here is sample code with somewhat animation like presenting viewcontroller
var subViewNew = UIView()
make initial dream setup of you subview which you want to make to add on main view as below
override func viewDidLoad() {
super.viewDidLoad()
subViewNew.frame = CGRect(x: 100,y: 100,width:self.view.frame.width - 200,height:200)
self.subViewNew.frame.origin.y = self.view.frame.origin.y + self.view.frame.size.height
subViewNew.backgroundColor = UIColor.gray
}
for appearance of the view subview you can go like this
UIView.animate(withDuration: 0.7, animations: {
self.subViewNew.frame.origin.y = 300 + 20
self.view.addSubview(self.subViewNew)
})
for dismiss or hiding it you can go like this
UIView.animate(withDuration: 0.7, animations: {
self.subViewNew.frame.origin.y = self.view.frame.origin.y + self.view.frame.size.height
self.subViewNew.removeFromSuperview() //If you want then only
})
Hope this help you

Trying to animate a UIView height from the bottom up, instead of the top down

I'm trying to animate the height of a UIView from a height of 1px to a height of 41px. It sort of works now, but instead of the view animating from the bottom to the top, it goes from the top to the bottom.
Here is the code that I am using to make it animate up and down
func animateBackgroundHeightUp() {
print("animate")
UIView.animate(withDuration: 0.5, animations: {
self.personBg.frame.size.height = 41
})
}
func animateBackgroundHeightDown() {
print("animate")
UIView.animate(withDuration: 0.5, animations: {
self.personBg.frame.size.height = 1
})
}
I think I have to figure out how to set the origin y, but I can't seem to find a swift 3 example.
EDIT
func animateBackgroundHeightUp() { print("animate") UIView.animate(withDuration: 0.5, animations: { self.personBg.frame.size.height = 41 self.personBg.frame.origin.y = -41 }) }
func animateBackgroundHeightDown() { print("animate") UIView.animate(withDuration: 0.5, animations: { self.personBg.frame.size.height = 1 self.personBg.frame.origin.y = 41 }) }
What ended up working for me is this configuration. But I'm confused as to why the origin needs to be 20?
func animateBackgroundHeightUp() {
print("animate")
UIView.animate(withDuration: 0.25, animations: {
self.personBg.frame.size.height = 41
self.personBg.frame.origin.y = 20
})
}
func animateBackgroundHeightDown() {
print("animate")
UIView.animate(withDuration: 0.25, animations: {
self.personBg.frame.size.height = 1
self.personBg.frame.origin.y = 59.9
})
}
The 59.9 number was figured out by printing out the origin y's value on load.
The origin in iOS views is the top left corner of the screen. A view's frame pins the origin at the top left corner, and the height tells how far it extends down.
If you increase the height of a view, its top won't move - it's bottom will extend downward.
If you want a view to appear to grow upward, you'll need to increase the height while decreasing the origin.y by the same amount.

Animate view height with Swift

I have view sView inside my ViewController. It height has constraint - I created IBOutlet for this constraint - sViewHeightConstraint. I want to decrease height of sView with animation.
I created function
UIView.animateWithDuration(5.5, animations: {
self.sViewHeightConstraint.constant = 50
})
Height of view is changing but i don't see any animation. What I am doing wrong?
use layoutIfNeeded()
view.layoutIfNeeded() // force any pending operations to finish
UIView.animateWithDuration(0.2, animations: { () -> Void in
self.sViewHeightConstraint.constant = 50
self.view.layoutIfNeeded()
})
swift 3
view.layoutIfNeeded()
sViewHeightConstraint.constant = 50
UIView.animate(withDuration: 1.0, animations: {
self.view.layoutIfNeeded()
})
In case your view updates but animation isn't working, this solution works for me.
UIView.animate(withDuration: 0.2, animations: {
self.sViewHeightConstraint.constant = 50
self.sView.size = CGSize(width: self.sView.frame.width, height: 50)
self.view.layoutIfNeeded()
})

Swift : Animate width and height of UIButton

Inside of these two functions, I want to animate the button shrinking(gone), then animate it growing(seen). I was able to animate it growing, but not at first have the button shrink. Any help on how to animate a button shrinking?
func progressBarButtonFadeOut(){
UIView.animateWithDuration(0.2, animations: {
//timeCapDesign is a UIButton
self.timeCapDesign.transform = CGAffineTransformMakeScale(0, 0)
})
}
//Progress Bar Fade In Buttons
func progressBarButtonFadeIn(){
UIView.animateWithDuration(0.2, animations: {
self.timeCapDesign.transform = CGAffineTransformIdentity
})
}
got it. So what i had to do was this:
func progressBarButtonFadeOut(){
UIView.animateWithDuration(0.2, animations: {
//timeCapDesign is a UIButton
self.timeCapDesign.alpha = 0
self.timeCapDesign.transform = CGAffineTransformMakeScale(0.1, 0.1)
})
}
//Progress Bar Fade In Buttons
func progressBarButtonFadeIn(){
UIView.animateWithDuration(0.2, animations: {
self.timeCapDesign.alpha = 1
self.timeCapDesign.transform = CGAffineTransformIdentity
})
}
So ultimately to shrink it i set it to a value of the CGAffineTransformMakeScale really low such as (0.1,0.1) and then animated the alpha to 0 to give the effect that its shrinking to nothing.
The scale of "1" is the current size, and if you want to animate it growing, the number should be bigger than 1.
func progressBarButtonFadeOut(){
UIView.animateWithDuration(0.2, animations: {
//timeCapDesign is a UIButton
self.timeCapDesign.transform = CGAffineTransformMakeScale(1.5, 1.5)
})
}

Resources