Run two animations simultaneously - ios

I'm trying to create an animation with two views and I've encountered some unexpected behaviors while performing them.
I want to animate both views position while doing a second animation which is transitionFlipFromBottom
Here's the code:
let initialFrame = CGRect(x: xpos, y: -310, width: 300, height: 300)
let firstView = UIView()
firstView.backgroundColor = .red
firstView.frame = initialFrame
let secondView = UIView()
secondView.backgroundColor = .white
secondView.frame = initialFrame
secondView.isHidden = false
self.view.addSubview(firstView)
self.view.addSubview(secondView)
// Here I try to move the views on screen while fliping them
UIView.animate(withDuration: 1, delay: 0, options: .curveEaseOut, animations: {
secondView.center = self.view.center
firstView.center = self.view.center
self.flip(firstView: firstView, secondView: secondView)
}, completion: nil)
// This function flips the views vertically while it animates the transition from the first to the second view
fileprivate func flip(firstView: UIView, secondView: UIView) {
let transitionOptions: UIViewAnimationOptions = [.transitionFlipFromBottom, .showHideTransitionViews]
UIView.transition(with: firstView, duration: 0.5, options: transitionOptions, animations: {
firstView.isHidden = true
})
UIView.transition(with: secondView, duration: 0.5, options: transitionOptions, animations: {
secondView.isHidden = false
})
}
The code above fails to execute both animations at the same time.
It only works if I place the flip function call inside the completion block, after the first animation (moving frame) finishes, as the following:
UIView.animate(withDuration: 1, delay: 0, options: .curveEaseOut, animations: {
secondView.center = self.view.center
firstView.center = self.view.center
}, completion: {(_) in
self.flip(firstView: dummyView, secondView: newGroupView)
})
I have even tried to use UIView.animateKeyframes but it still doesn't work.
Am I missing something here?
Thank you.

A couple of things:
In transition, specify .allowAnimatedContent option.
Defer the animation:
DispatchQueue.main.async {
UIView.animate(withDuration: 1, delay: 0, options: [.curveEaseOut], animations: {
secondView.center = CGPoint(x: self.view.bounds.midX, y: self.view.bounds.midY)
firstView.center = CGPoint(x: self.view.bounds.midX, y: self.view.bounds.midY)
self.flip(firstView: firstView, secondView: secondView)
}, completion: { _ in
})
}
Somewhat unrelated, you don't want:
secondView.center = self.view.center
Instead, do:
secondView.center = CGPoint(x: self.view.bounds.midX, y: self.view.bounds.midY)
You want to set secondView.center in the coordinate space of the bounds of view, not in view's superview.

Related

Have User be able to Swipe Down on temporary UIView

I wanted to be able to have the user swipe down to dismiss a temporary Notification that comes in from the bottom.
Here's what the code looks like:
func showAnimationToast(...) {
let toastView = UIView(frame: CGRect(x: 10, y: view.frame.size.height - view.safeAreaInsets.bottom, width: view.frame.size.width - 20, height: 60))
...
toastView.tag = 1474
let animationView = AnimationView(name: animationName)
...
toastView.addSubview(animationView)
let messageLabel = UILabel(frame: CGRect(x: toastView.frame.size.height, y: 5, width: toastView.frame.size.width - toastView.frame.size.height, height: 50))
...
toastView.addSubview(messageLabel)
toastView.isUserInteractionAvailable = true
I tried to add a UISwipeGestureRecognizer to toastView, but it never worked. I even tried the simple UITapGestureRecognizer and it STILL didn't work.
Here's what I tried:
//Let Swipe Down Dismiss. Does not work
let swipeDownGesture = UISwipeGestureRecognizer(target: self, action: #selector(dismissToast(_:)))
swipeDownGesture.direction = .down
toastView.addGestureRecognizer(swipeDownGesture)
//Show animation
UIView.animate(withDuration: 0.2, delay: 0, animations: {
toastView.frame.origin.y = self.view.frame.size.height - self.view.safeAreaInsets.bottom - 70
}, completion: {_ in
animationView.play()
})
//Remove after specified time
UIView.animate(withDuration: 0.2, delay: duration, animations: {
toastView.center.y = self.view.frame.size.height + 50
}, completion: {_ in
toastView.removeFromSuperview()
})
}
#objc func dismissToast(_ sender: UIGestureRecognizer) {
print("dismiss")
let toastView = view.subviews.filter { view in
if view.tag == 1474 /*toastView*/ tag{
return true
}
return false
}.last!
UIView.animate(withDuration: 0.2, delay: 0, animations: {
toastView.center.y = self.view.frame.size.height + 50
}, completion: {_ in
toastView.removeFromSuperview()
})
}
The issue seems to be that while a view is waiting for an animation to play (during the "delay" period), it can't receive user interactions.
One way to work around this is to not use the delay parameter, and instead use DispatchQueue.main.asyncAfter:
UIView.animate(withDuration: 0.2, delay: 0, options: [.allowUserInteraction], animations: {
toastView.frame.origin.y = self.view.frame.size.height - self.view.safeAreaInsets.bottom - 70
}, completion: {_ in
DispatchQueue.main.asyncAfter(deadline: .now() + duration + 0.2) {
UIView.animate(withDuration: 0.2, delay: 0, animations: {
toastView.center.y = self.view.frame.size.height + 50
}, completion: {_ in
toastView.removeFromSuperview()
})
}
})

Flip UIView without dim effect

I am trying to create a flip animation in one of my views. I managed to get the animation working but it looks horrible because the card (which has a white background color) dims its color to be darker during the animation.
Do you know if there is a way to get rid of this dim effect to maintain the original color of the cards during the whole animation?
For further reference, please find below a simple view controller reproducing my problem.
import UIKit
class ViewController: UIViewController {
private lazy var cardView: UIView = {
let backgroundView = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 200))
backgroundView.backgroundColor = .white
return backgroundView
}()
private lazy var flipButton: UIButton = {
let flipButton = UIButton(frame: CGRect(x: 0, y: 0, width: 300, height: 200))
flipButton.setTitle("Flip", for: .normal)
flipButton.setTitleColor(.red, for: .normal)
flipButton.addTarget(self, action: #selector(flipCard), for: .touchUpInside)
return flipButton
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .lightGray
view.addSubview(cardView)
cardView.addSubview(flipButton)
cardView.center = view.center
}
#objc
func flipCard() {
UIView.transition(with: cardView, duration: 10, options: .transitionFlipFromRight, animations: nil, completion: nil)
}
}
u can use
UIView.animateKeyframes(withDuration: 6, delay: 0, options: []) {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) {
self.cardView.transform3D = CATransform3DRotate(self.cardView.layer.transform, CGFloat.pi, 0, 1, 0)
}
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 1) {
self.cardView.transform3D = CATransform3DRotate(self.cardView.layer.transform, CGFloat.pi, 0, 1, 0)
}
}
instead of
UIView.transition(with: cardView, duration: 10, options: .transitionFlipFromRight, animations: nil, completion: nil)
You can go through UIView.animate() also in Swift 5,
UIView.animate(withDuration: duration/2, delay: 0, options: .curveLinear) {
targetView.transform = CGAffineTransform.identity.rotated(by: .pi )
} completion: { (_) in
UIView.animate(withDuration: duration/2, delay: 0, options: .curveLinear) {
targetView.transform = CGAffineTransform.identity.rotated(by: .pi * 2)
}
}

iOS Animation is not working after switch to another VC Swift

I have small background animation to change gradient, ex u can see here animation
As you see if i open app first time, animation is working, after the changing View Controller animation is stop.
My code:
func animateGrandient() {
UIView.animate(withDuration: 15, delay: 0, options: [.autoreverse, .curveLinear, .repeat], animations: {
let x = -(self.gradientView.frame.width - self.view.frame.width)
self.gradientView.transform = CGAffineTransform(translationX: x, y: 0)
})
}
And outlet:
#IBOutlet weak var gradientView: UIImageView!
This happens because your self.gradientView.transform is changed already before your animation is executed, so you need to reset your self.gradientView.transform
Add this line self.gradientView.transform = CGAffineTransform.identity in the beginning of that method
fixed code
func animateGrandient() {
self.gradientView.transform = CGAffineTransform.identity
UIView.animate(withDuration: 15, delay: 0, options: [.autoreverse, .curveLinear, .repeat], animations: {
let x = -(self.gradientView.frame.width - self.view.frame.width)
self.gradientView.transform = CGAffineTransform(translationX: x, y: 0)
})
}

Custom Segue, Multiple Animations Not Syncing

There are a lot of excellent answers on this topic, but this one has me confused.
There are two commented out lines which don't seem to work properly. I'd like this custom segue to both slide AND shrink so that the effect is for the fromVC to disappear up into nothing and the toVC to arrive from nothing from the bottom.
However, if I try to do both simultaneously it fails. Either individually work fine (a zoom or a slide), but not together.
class UnwindScaleSegue: UIStoryboardSegue {
override func perform() {
scaleDown()
}
func scaleDown() {
let toVC = self.destination
let fromVC = self.source
let screenHeight = UIScreen.main.bounds.size.height
toVC.view.transform = CGAffineTransform.init(translationX: 0.0, y: screenHeight)
//toVC.view.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
fromVC.view.superview?.insertSubview(toVC.view, at: 0)
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseInOut, animations: {
//fromVC.view.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
fromVC.view.transform = CGAffineTransform.init(translationX: 0.0, y: -screenHeight)
toVC.view.transform = CGAffineTransform.identity
}, completion: { success in
fromVC.dismiss(animated: false, completion: nil)
})
}
}
Solved! It turns out it has to do with how the transforms are combined. I'm not certain I understand, but something having to do with the vector nature of the transformations means you have to be careful when doing multiple transforms in the same animation.
By "concatenating" them carefully it works! However, for me, if I swapped the order, it didn't work.
The key lines are starred.
class UnwindScaleSegue: UIStoryboardSegue {
override func perform() {
scaleAway()
}
func scaleAway() {
let toVC = self.destination
let fromVC = self.source
let screenHeight = UIScreen.main.bounds.size.height
var translate = CGAffineTransform(translationX: 0.0, y:screenHeight) //**
let scale = CGAffineTransform(scaleX: 0.001, y: 0.001) //**
toVC.view.transform = scale.concatenating(translate) //**
fromVC.view.superview?.insertSubview(toVC.view, at: 0)
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseInOut, animations: {
translate = CGAffineTransform(translationX: 0.0, y: -screenHeight) //**
fromVC.view.transform = scale.concatenating(translate) //**
toVC.view.transform = CGAffineTransform.identity
}, completion: { success in
fromVC.dismiss(animated: false, completion: nil)
})
}
}

Combine translation, alpha and scale animations in swift3

I'm totally newbies with iOS Swift developement and i try to combine three parameters in a single animations but i don't succeed.
I think the solution is here -Apple Dev Core Animation Programming Guide by grouping the animations but being a beginner and after a lot of Internet research i can't find what i'm looking for.
What do you think of my code and what is for you the best solution to combine performance and stability.
I want to point out that the purpose of this animation is to create an animated Splashscreen. There are other elements (UIImage) that will be to animates.
Here is my code:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
logoImg.alpha = 0
logoImg.transform = CGAffineTransform(translationX: 0, y: -200)
logoImg.transform = CGAffineTransform(scaleX: 0, y: 0)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 1.0, delay: 1.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 10, options: [.curveEaseOut], animations: {
self.logoImg.transform = CGAffineTransform(translationX: 0, y: 0)
self.logoImg.transform = CGAffineTransform(scaleX: 1, y: 1)
self.logoImg.alpha = 1
}, completion: nil)
}
Based on what I am seeing you are wanting to preset the animation and translate it back. In that case I would do this.
self.logoImg.transform = CGAffineTransform(translationX: 0, y: -200).concatenating(CGAffineTransform(scaleX: 0, y: 0))
self.logoImg.alpha = 0
UIView.animate(withDuration: 1.0, delay: 1.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 10, options: [.curveEaseOut], animations: {
self.logoImg.transform = .identity
self.logoImg.alpha = 1
}, completion: nil)
I think you may not be seeing all the animation so try to start the scale at 0.5
self.logoImg.transform = CGAffineTransform(translationX: 0, y: -200).concatenating(CGAffineTransform(scaleX: 0.5, y: 0.5))
self.logoImg.alpha = 0
UIView.animate(withDuration: 1.0, delay: 1.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 1, options: [.curveEaseOut], animations: {
self.logoImg.transform = .identity
self.logoImg.alpha = 1
}, completion: nil)
The key here is that the animation is animating back the original identity. Hope this helps
You can use concatenating method to combining two existing affine transforms.
UIView.animate(withDuration: 1.0, delay: 1.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 10, options: [.curveEaseOut], animations: {
let translation = CGAffineTransform(translationX: 0, y: 0)
let scale = CGAffineTransform(scaleX: 1, y: 1)
self.logoImg.transform = translation.concatenating(scale)
self.logoImg.alpha = 1
}, completion: nil)
Look at Apple Document for more info. Hope it help. :)
Rotate and Translate and make it like parallax effect in tableview header:
func setupTableHeaderView() {
self.customHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: 200))
self.customHeaderView?.backgroundColor = .white
self.customImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: 200))
self.customImageView?.image = ImageNamed(name: "camera")
self.customImageView?.contentMode = .center
self.customHeaderView?.addSubview(self.customImageView!)
self.tableHeaderView = self.customHeaderView
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let yPos = scrollView.contentOffset.y
if yPos < 0 {
let scaleX = ((yPos * -1) / 200) + 1
let translateY = yPos / 2
var commonTransform = CGAffineTransform.identity
commonTransform = commonTransform.translatedBy(x: 0, y: translateY)
commonTransform = commonTransform.scaledBy(x: scaleX, y: scaleX)
self.customImageView?.transform = commonTransform
}else{
self.customImageView?.transform = CGAffineTransform.identity
}
}
NOTE : Make sure the View you apply transform to is SUBVIEW of the header view, not header view itself. In above example customImageView is subview of main headerview.
You can use this also with swift 3.0.1:
UIView.transition(with: self.imageView,
duration:0.5,
options: .transitionCrossDissolve,
animations: { self.imageView.image = newImage },
completion: nil)
Reference: https://gist.github.com/licvido/bc22343cacfa3a8ccf88

Resources