trying to animate a subview inside a collection view cell but am only getting an abrupt change between states.
...
func animate (){
if self.signOut.hidden == false{
UIView.animateWithDuration(0.2) {
self.signOut.hidden = true
}
}else{
UIView.animateWithDuration(0.2) {
self.signOut.hidden = false
}
}
...
any tips much appreciated!
You need to decrease the alpha inside an animation block for the view to disappear smoothly
UIView.animateWithDuration(0.33, delay: 0.0, options: [.CurveEaseInOut], animations: {
self.signOut.alpha = 0.0
}) { finished in
self.signOut.hidden = true
}
Related
I am trying to create an interactive animation to switch the navigation bar display mode when scrolling the contents of a table view in a method scrollViewDidScroll(_ scrollView: UIScrollView). Below is a piece of code using UIViewPropertyAnimator and the result of code execution.
private lazy var animator: UIViewPropertyAnimator = {
return UIViewPropertyAnimator(duration: 0.25, curve: .easeInOut)
}()
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if canTransitionToLarge && scrollView.contentOffset.y <= 0 {
animator.addAnimations {
self.navBar.items?.last?.largeTitleDisplayMode = .always
self.navBar.layoutIfNeeded()
self.view.layoutIfNeeded()
}
animator.startAnimation()
canTransitionToLarge = false
}
else if !canTransitionToLarge && scrollView.contentOffset.y > 0 {
animator.addAnimations {
self.navBar.items?.last?.largeTitleDisplayMode = .never
self.navBar.layoutIfNeeded()
self.view.layoutIfNeeded()
}
animator.startAnimation()
canTransitionToLarge = true
}
}
Result of code execution
What changes should I make to the code in order to achieve an interactive animation like in the video below?
interactive animation
Im using SnapKit to alter the height of a UIView based on a touch event. So when the user touches the cell it expands. This works fine like this:
#objc func didRecieveTouch() {
let originalHeight: CGFloat = 90
let expandedHeight: CGFloat = 244
if !expanded {
self.snp.updateConstraints { make in
make.height.equalTo(expandedHeight)
}
} else {
self.snp.updateConstraints { make in
make.height.equalTo(originalHeight)
}
}
self.expanded = !self.expanded
UIView.animate(withDuration: 0.25, animations: {
self.superview?.layoutIfNeeded()
})
}
The problem is that the direction the view grows in looks weird and wrong to me. The view actually "hops" down to the new allocated space and grows upwards instead of growing downwards which is the desired behaviour I want.
Add your code inside the animation block.
UIView.animate(withDuration: 0.25) {
if !expanded {
self.snp.updateConstraints { make in
make.height.equalTo(expandedHeight)
}
} else {
self.snp.updateConstraints { make in
make.height.equalTo(originalHeight)
}
}
self.expanded = !self.expanded
self.superview?.layoutIfNeeded()
}
I have a UIView which contains subviews. The subviews are animated with constraints changes and layoutIfNeeded() function :
for i in 1...cardViews.count - 1 {
let currentCard = cardViews[i]
let previousCard = cardViews[i-1]
// Set new offset
currentCard.topConstraint?.constant = previousCard.contentView.frame.height + configuration.expandedOffset
}
// To animate constraints's changes
UIView.animate(withDuration: TimeInterval(duration), delay: 0, options: [.curveEaseOut], animations: {
self.layoutIfNeeded()
}, completion: nil)
But when I do this, it also animate the contraints changes of the parent. Something like this :
self.Height == containerView.Height
How can I call layoutIfNeeded() to animate my subviews but not the parent ?
EDIT : The side effect :
http://gph.is/2noI3w9
You can call this inside of your UIView.animate
for i in 1...cardViews.count - 1 {
let currentCard = cardViews[i]
currentCard.layoutIfNeeded()
}
Instead of
self.layoutIfNeeded()
I have an AccountsViewController which has UIVisualEffectView with blur effect in background (subview at index 0), so that it covers previous controller with blur during transition. But when I then switch to another app and open multitasking menu again to switch back to my app, its blurry UIVisualEffectView seem to be damaged (as shown on the screenshot 1). When I make my app active again, the view gets "repaired" and looks ok again (as shown on the second screenshot)
Some code from my custom transitioning class where I add the blur view
// in the animateTransition.. method
guard let container = transitionContext.containerView() else {
return
}
let blurEffectView: UIVisualEffectView
if container.viewWithTag(101) != nil {
blurEffectView = container.viewWithTag(101)! as! UIVisualEffectView
}
else {
blurEffectView = UIVisualEffectView(frame: container.frame)
blurEffectView.translatesAutoresizingMaskIntoConstraints = false
blurEffectView.tag = 101
container.addSubview(blurEffectView)
}
if self.isPresenting {
// We're presenting a view controller over another
toViewController.view.transform = CGAffineTransformMakeScale(0.7, 0.7)
container.addSubview(toViewController.view)
container.sendSubviewToBack(blurEffectView)
toViewController.view.alpha = 0
UIView.animateWithDuration(self.transitionDuration(transitionContext), delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.8, options: [], animations: { () -> Void in
fromViewController.view.transform = CGAffineTransformMakeScale(0.8, 0.8)
toViewController.view.transform = CGAffineTransformMakeScale(1, 1)
blurEffectView.effect = UIBlurEffect(style: .Dark)
toViewController.view.alpha = 1
}, completion: { (completed) -> Void in
self.context?.completeTransition(completed)
})
}
else {
// We're dismissing a view controller
UIView.animateWithDuration(self.transitionDuration(transitionContext), delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.8, options: [], animations: { () -> Void in
fromViewController.view.transform = CGAffineTransformMakeScale(0.7, 0.7)
toViewController.view.transform = CGAffineTransformMakeScale(1, 1)
blurEffectView.effect = nil
fromViewController.view.alpha = 0
}, completion: { (completed) -> Void in
fromViewController.view.removeFromSuperview()
self.context?.completeTransition(completed)
})
}
Seems like I've figured it out myself: this issue appears only when there is an UIWindow back (which is black) showing through.
In this question I had a transition animation container with these views:
fromViewController's view (with a scale transform 0.8;0.8)
UIVisualEffectView with .Dark style blur applied to it
toViewController's view
Because of fromViewController's view size (which was smaller than the screen because of scale transform), the black UIWindow view was showing through. And according to my research this causes UIVisualEffectView to produce a glitch described in this question.
The solution is to add a black UIView with the frame of an UIWindow and add it to the bottom of the container's view hierarchy:
let blackView = UIView(frame: container.frame)
blackView.backgroundColor = UIColor.blackColor()
container.insertSubview(blackView, atIndex: 0)
And then this glitch seem to appear no longer.
A view that is anchored to the centre of the screen on both axis is animated (transitions) to a new location with a new size.
When the animation finishes, the completion block calls an animation to occur on another view, which has its leading anchor constrained some distance away from the trailing anchor of the view that first animated.
The problem that I have is that I do not know the solution of making the second view's animation take into account the new frame of the view that has just finished. The second view needs to end up to the right and centred on the Y to the first view.
Every thing that I have tried has the same result: the second view takes into account the position of the first view before it animated to its new location and size. How do I update the constraints for it?
I have tried with NSLayoutConstraint variables, updating the constraints, but nothing works. Relevant code listed, thank you.
func setupViews() { // called in viewDidLoad
view.addSubview(programIcon)
view.addSubview(programTitle)
let iconConstraints = [
programIcon.centerXAnchor.constraint(equalTo: view.centerXAnchor),
programIcon.centerYAnchor.constraint(equalTo: view.centerYAnchor),
programIcon.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -37.5),
programIcon.heightAnchor.constraint(equalTo: programIcon.widthAnchor)
]
NSLayoutConstraint.activate(iconConstraints)
titleHiddenX = programTitle.leadingAnchor.constraint(equalTo: view.trailingAnchor)
titleHiddenX.isActive = true
titleHiddenY = programTitle.topAnchor.constraint(equalTo: view.topAnchor) // could do without?
titleHiddenY.isActive = true
titleVisibleX = programTitle.leadingAnchor.constraint(equalTo: programIcon.trailingAnchor, constant: 10)
titleVisibleX.isActive = false
titleVisibleY = programTitle.centerYAnchor.constraint(equalTo: programIcon.centerYAnchor)
titleVisibleY.isActive = false
}
#objc func animateIcon() {
if animationHasBeenShown {
print("animation has beed shown already")
} else {
//I have removed the math part as its not relevant here.
UIView.animate(withDuration: 0.5, animations: {
self.programIcon.transform = scale.concatenating(translation)
}) { (true) in
self.programTitle.setNeedsUpdateConstraints()
self.animateElementsOnScreen()
print("icon frame \(self.programIcon.frame)")
}
}
}
func animateElementsOnScreen() {
UIView.animate(withDuration: 0.5, animations: { // this needs to change.
self.titleHiddenX.isActive = false
self.titleHiddenY.isActive = false
self.titleVisibleX.isActive = true
self.titleVisibleY.isActive = true
self.view.layoutIfNeeded()
}) { (true) in
print("title frame \(self.programTitle.frame)")
} // and so on, more views animating.
From Apple's docs (https://developer.apple.com/documentation/uikit/uiview/1622459-transform):
In iOS 8.0 and later, the transform property does not affect Auto Layout. Auto layout calculates a view’s alignment rectangle based on its untransformed frame.
If you want to move or scale a view AND maintain its auto-layout / constraints properties, you need to animate a change to the view's constraints instead of applying a .transform.