I want to replicate this animation in my project
So what I did try is:
To transform scale X .. but the problem is it also shrinks the title of a button.
self.transform = CGAffineTransform.identity
UIView.animate(withDuration: duration, delay: delayTime,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 0.7,
options: [.curveEaseIn],
animations: {
self.transform = CGAffineTransform(scaleX: 0.5, y: 1.0)
}, completion: nil)
This is what it gives (title of button also shrinks)
Use of CASpringAnimation
let shrinkAnim = CASpringAnimation(keyPath: "bounds.size.width")
shrinkAnim.damping = 0.7
shrinkAnim.initialVelocity = 0.7
shrinkAnim.fromValue = frame.width
shrinkAnim.toValue = width
shrinkAnim.dura[![enter image description here][3]][3]tion = duration
shrinkAnim.timingFunction = getTimingFunction(curve: curve)
shrinkAnim.fillMode = kCAFillModeForwards
shrinkAnim.isRemovedOnCompletion = false
layer.add(shrinkAnim, forKey: shrinkAnim.keyPath)
So it can change the width but also position of the title
So my question is whats going wrong or what I need to add to replicate first image?
My constraints for button is pinned to left, right and bottom edges and fix height. and more thing is I am making a class for this so I cant change constants because I have to use this in many screens .. so I want one stop solution.
Don't use CGAffineTransform, rather take and NSLayoutConstraint attribute outlet for Button's width and change its constant inside animation block/closure.
UIView.animate(withDuration: duration, delay: delayTime,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 0.7,
options: [.curveEaseIn],
animations: {
self.buttonWidthConstraint.constant = desired_width_here
}, completion: nil)
Put your button inside a UIView
your view tree would then look like this
Superview > UIView > Button
By doing this you now have a fixed width size your button can follow which is the uiview
It would look like this
And then animate using
//let's say the current left and right constraint are 8
leftConstraint.constant = 50
rightConstraint.constant = 50
UIView.animate ... {
self.view.layoutIfNeeded()
}
By using this you'd only need to set the UIView's frame to your desired frame and have the button follow suit and your animations would just be set proportionate to how you'd code it
//let's say the current left and right constraint are 8
// get view frame
...
// calculate distance
var calculatedDistance = ......
// set distance
let distanceToAnimate = calculatedDistance
leftConstraint.constant = distanceToAnimate
rightConstraint.constant = distanceToAnimate
UIView.animate ... {
self.view.layoutIfNeeded()
}
Have you tried content content hugging and compression resistance. Can you please try doing below at highest priorty.
Apply the transform on the frame of the button. When you scale an UIView in animation it does not take consideration of subviews/layout it will just uniformly scale whatever is being drawn.
self.transform = CGAffineTransform.identity
UIView.animate(withDuration: duration, delay: delayTime,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 0.7,
options: [.curveEaseIn],
animations: {
self.frame = CGRectMake(<#CGFloat x#>, <#CGFloat y#>, <#CGFloat width#>, <#CGFloat height#>)
}, completion: nil)
Related
So I'm trying to animate an UILabel to match another UILabel size and position. At first I was only working with the animation of the position part using constraints like this:
private lazy var constraintsForStateA: [NSLayoutConstraint] = [
firstLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
firstLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor)
]
private lazy var constraintsForStateB: [NSLayoutConstraint] = [
firstLabel.leadingAnchor.constraint(equalTo: secondLabel.leadingAnchor),
firstLabel.topAnchor.constraint(equalTo: secondLabel.topAnchor)
]
So I basically have two arrays with these constraints above (constraintsForStateA and constraintsForStateB), and when I need to animate the position of my firstLabel I just do something like:
// From state A to state B
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseOut) {
NSLayoutConstraint.deactivate(self._constraintsUnselected)
NSLayoutConstraint.activate(self._constraintsSelected)
self.layoutIfNeeded()
} completion: { _ in
self.firstLabel.alpha = 0
self.secondLabel.alpha = 1
}
}
So far this has been working exactly as I was expecting, but the part with the size is giving me some trouble, since I can't apply the same strategy to the text size I'm using a transform like this:
let scaleX = secondLabel.bounds.width / firstLabel.bounds.width
let scaleY = secondLabel.bounds.height / firstLabel.bounds.height
and then in the same .animate method above from state A to state B I do this:
firstLabel.transform = CGAffineTransform(scaleX: scaleX, y: scaleY)
and from state B to state A:
firstLabel.transform = .identity
Which works as I want but the problem I'm facing is that the position is no longer in the expected place. I think this is happening because the transformation is happening having in consideration the anchorPoint at the center of the label. I've tried sort of blindly making it work changing the anchorPoint to be at (0,0), but it's not working either. I've lost 2 days on this already, any help or guidance is welcome!
When you animate, it would be much simpler if you just forget about the constraints and just deal with frames:
NSLayoutConstraint.deactivate(self.constraintsForStateA)
firstLabel.translatesAutoresizingMaskIntoConstraints = true
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseOut) {
firstLabel.frame = secondLabel.frame
}
Then to transition from state B to A:
firstLabel.translatesAutoresizingMaskIntoConstraints = false
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseOut) {
NSLayoutConstraint.activate(self.constraintsForStateA)
self.layoutIfNeeded()
}
I'm trying to hide UIStackView's subview like this:
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 2.0,
delay: 0, options: [.curveEaseOut], animations: {
self.label.isHidden = true
self.label.alpha = 0.0
self.stackView.layoutIfNeeded()
})
However, the label disappears instantly with using this code. I suspect this is because of setting isHidden to true, which is required for collapsing.
Is there a way how to hide and collapse UIStackView's subvew with animation? Or it might be better to not to use UIStackView at all?
According to Apple's documentation:
You can animate both changes to the arranged subview’s isHidden property and changes to the stack view’s properties by placing these changes inside an animation block.
I've tested the below code using iOS 12.1 Simulator and it works as expected.
UIView.animate(
withDuration: 2.0,
delay: 0.0,
options: [.curveEaseOut],
animations: {
self.label.isHidden = true
self.label.alpha = 0.0
})
You can animate view properties like alpha, color, etc. However, some things happen instantly - isHidden in this case.
Here's an example using UIView.animate:
UIView.animate(withDuration: 2, delay: 0, options: .curveEaseOut, animations: {
self.label.alpha = 0 // Changes the label's layer alpha value
}, completion: { finished in
self.label.isHidden = true // Hides the label
self.label.layer.alpha = 1 // Resets the label's alpha without un-hiding it
})
Using UIViewPropertyAnimator:
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 2, delay: 0, options: .curveEaseOut, animations: {
self.label.alpha = 0 // Sets the label's alpha
}) { _ in
self.label.isHidden = true // Hides the label
self.label.alpha = 1 // Resets the label's alpha without un-hiding it
}
I have tried your code. Its animating
if self.stackView.subviews.count > 0 {
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1.0, delay: 0, options: [.curveEaseOut], animations: {
self.stackView.subviews[0].isHidden = true
self.stackView.subviews[0].alpha = 0.0
self.stackView.layoutIfNeeded()
}) { (position) in
self.stackView.subviews[0].removeFromSuperview()
}
}
Just you can use simple solution with animateKeyframes to fade alpha , then hide , i think this will give you what you need So hide after 1 Sec and 0.8 Sec fading
// showLabel is Bool to handle status declare it at you File
#IBAction func toggleStackLabelTapped(_ sender: UIButton) {
showLabel = !showLabel
UIView.animateKeyframes(withDuration: 1, delay: 0, options: .calculationModeLinear, animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.8) {
self.label.alpha = (self.showLabel) ? 1 : 0
}
UIView.addKeyframe(withRelativeStartTime: 0.8, relativeDuration: 1) {
self.label.isHidden = !self.showLabel
}
})
}
make sure you have not given height constraint to the stackview.
and try this.
UIView.animate(withDuration: 0.5) {
self.stackView.subviews[INDEX_OF_LABEL_IN_STACK]?.alpha = 0
self.stackView.subviews[INDEX_OF_LABEL_IN_STACK]?.isHidden = true
self.view.layoutSubviews()
}
I programmatically created a UIStackView, which consists of two UICollectionView stacked vertically. One of them is a menubar, and one of them is a grid that displays media content.
let contentStack = UIStackView()
contentStack.addArrangedSubview(menuBar)
contentStack.addArrangedSubview(grid)
self.view.addSubview(contentStack)
I want the UIStackView to appear at the bottom of the screen, once a button is tapped. However, when it appears, I want it to do an animation, so it slides up from below the screen. I have looked at older solutions to posts similar to mine, but I could not find anything helpful because I manually set the constraints like this:
contentStack.translatesAutoresizingMaskIntoConstraints = false
contentStackBotAnchor = contentStack.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0)
contentStackBotAnchor!.isActive = true
contentStack.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
contentStack.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
contentStack.axis = .vertical
contentStack.spacing = 0
I have attempted solutions like such:
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseIn, animations: {
self.contentStack.frame = CGRect.init(x: 0, y: self.view.frame.height - self.contentStack.frame.height, width: self.contentStack.frame.width, height: self.contentStack.frame.height)
}, completion: nil)
This didn't work for me.
As Reinier stated, you can animate your constraints. Another option could be using a CGAffineTransform. When the view loads, simply change the y value of your UIStackView with something like:
contentStack.transform = CGAffineTransform(translationX: 0, y: view.frame.height)
And then when you want to animate it, use the animation of your choice:
UIView.animate(withDuration: 0.5, animations: {
contentStack.transform = .identity
})
This is what I prefer in such cases, hope it helps.
If you are using constraint then you must animate using your constraints, Try with this, I am not sure about the values but must be like this
self.view.layoutIfNeeded()
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseIn, animations: {
contentStackBotAnchor.constant = (self.contentStack.bounds.size.height * -1)
self.view.layoutIfNeeded()
}, completion: nil)
Hope this helps
I am adding and removing attributed text from a UITextView. I wish to use UIView.animate to add an animation to when text is appended to the text view and when that appended text is removed from the text view. So far, I have this, but it does not cause any noticeable animation on the text view:
UIView.animate(withDuration: 1, delay: 0, options: .CurveLinear, animations: {
self.view.layoutIfNeeded()
self.textView.attributedText = newAttributedText
}, completion: { finished in
print("Animation completed")
}
// prints "Animation completed", but no animation occurs
You cannot animate changing of text in that manner. There is a list of animatable properties of CALayer class and UIView class.
Set text cannot be animated by UIView.animate. Only changes like transparency, colors, shape, location, etc can be animated by the UIView.animate.
Here is an example of animating origin and transparency change with code looks like
self.textView.text = "test Animation"
self.textView.alpha = 0
UIView.animate(withDuration: 3, delay: 0, options: .curveLinear, animations: {
var frame = self.textView.frame
frame.origin.x = frame.origin.x + 50
frame.origin.y = frame.origin.y + 50
self.textView.frame = frame
self.textView.alpha = 1
}, completion: { finished in
print("Animation completed")
})
And animation looks like
Here is my solution (swift 3 ) :
let animator = UIViewPropertyAnimator(duration: 0.2, curve: .easeOut, animations: {
self.textView2.frame = self.textView2.frame.offsetBy(dx: 20 , dy: 25)
} )
animator.startAnimation()
I have two buttons in a view controller that I animate up and then back to their original positions when pressed. 50% of the time there is a discernible gap that appears between the top of the button and it's background image. I think it has to do with using spring/damping but I don't want to not use that. I tried rounding to the nearest whole CGFloat both the button frame and destination coordinates, no luck. Is there a work around to this?
let profileMainScreenButtonAnimateToY = -ceil((profileMainButton.frame.origin.y - 45.0))
let profileStandingsButtonAnimateToY = -ceil((profileStandingsButton.frame.origin.y - 45.0))
UIView.animateWithDuration(0.7, delay: 0.0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.8, options: [.CurveEaseInOut], animations: { () -> Void in
self.profileMainButton.transform = CGAffineTransformMakeTranslation(0.0, profileMainScreenButtonAnimateToY)
self.profileStandingsButton.transform = CGAffineTransformMakeTranslation(0.0, profileStandingsButtonAnimateToY)
}) { (Bool) -> Void in
}
It's difficult to see in the image but there is a yellow horizontal line at the top of the image.