I'm creating a custom view programmatically, which is working fine, then I'm trying to animate the view. For some strange reason the view is animating at all, I'm not sure why. Here is the code:
override func viewDidLoad() {
super.viewDidLoad()
let customView = UIView(frame: CGRect(x: 10, y: 10, width: 100, height: 100))
customView.backgroundColor = UIColor.blue
customView.layer.cornerRadius = 25
customView.layer.borderWidth = 8
customView.layer.borderColor = UIColor.red.cgColor
self.view.addSubview(customView)
UIView.animate(withDuration: 4, animations: {
customView.transform.translatedBy(x: 40, y: 60)
customView.transform.rotated(by: CGFloat.pi/2)
customView.transform.scaledBy(x: 0, y: 0.5)
})
}
you better call this animation block in viewDidAppear, with some delay.
import UIKit
class ViewController: UIViewController {
var customView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
customView = UIView(frame: CGRect(x: 10, y: 10, width: 100, height: 100))
customView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTap)))
customView.backgroundColor = UIColor.blue
customView.layer.cornerRadius = 25
customView.layer.borderWidth = 8
customView.layer.borderColor = UIColor.red.cgColor
self.view.addSubview(customView)
}
override func viewDidAppear(_ animated: Bool) {
UIView.animate(withDuration: 5, delay: 1, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.customView.transform = CGAffineTransform(scaleX: 1, y: 0.5)
self.customView.transform = CGAffineTransform(rotationAngle: CGFloat.pi/2)
self.customView.transform = CGAffineTransform(translationX: 40, y: 60)
}, completion: nil)
}
func handleTap() {
UIView.animate(withDuration: 5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.customView.transform = CGAffineTransform(scaleX: 1, y: 0.5)
self.customView.transform = CGAffineTransform(rotationAngle: CGFloat.pi/2)
self.customView.transform = CGAffineTransform(translationX: 40, y: 60)
}, completion: nil)
}
}
Your question is not correct. your view is not animating.
Why?
You are using a return function and your customView needs to catch the modified version like this.
customView.transform = customView.transform.translatedBy(x: 40, y: 60)
So your code would be like this.
let customView = UIView(frame: CGRect(x: 10, y: 10, width: 100, height: 100))
customView.backgroundColor = UIColor.blue
customView.layer.cornerRadius = 25
customView.layer.borderWidth = 8
customView.layer.borderColor = UIColor.red.cgColor
self.view.addSubview(customView)
UIView.animate(withDuration: 4, animations: {
customView.transform = customView.transform.translatedBy(x: 40, y: 60)
//customView.transform = customView.transform.rotated(by: CGFloat.pi/2)
//customView.transform = customView.transform.scaledBy(x: 0, y: 0.5)
})
There are lots of online resources how to group animation.Just google it.:)
Related
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)
}
}
I'm trying to create a popup label over the imageview. But in my application it doesn't work. I've made a test application with the same screen: UIImageView and below a UIView with a UIButton on the view.
So, where are two questions.
What could be difference in a code for such a different behaviour?
Why in my application the UILabel doesn't conforms to the initial frame?
The code of the function inside my viewController is the same:
private func showBanner(startY: CGFloat, targetView: UIView) {
let height: CGFloat = 42
let finishY = startY - height
let bannerLabel = UILabel(frame: CGRect(x: 0, y: startY, width: self.view.frame.width, height: height))
bannerLabel.translatesAutoresizingMaskIntoConstraints = false
bannerLabel.font = UIFont.systemFont(ofSize: 13, weight: .regular)
bannerLabel.textColor = .lightGray
bannerLabel.backgroundColor = .black
bannerLabel.textAlignment = .center
bannerLabel.numberOfLines = 1
bannerLabel.text = "You've added the item to the favorites"
targetView.addSubview(bannerLabel)
UIView.animate(withDuration: 0.5, animations: {
bannerLabel.frame = CGRect(x: 0,
y: finishY,
width: self.view.frame.width,
height: height
)
}) {
_ in
UIView.animate(withDuration: 0.5, delay: 1.0, options: .curveLinear, animations: {
bannerLabel.frame = CGRect(x: 0,
y: startY,
width: self.view.frame.width,
height: height
)
}, completion: {
_ in
bannerLabel.removeFromSuperview()
})
}
}
The function is being called so:
showBanner(startY: itemsImageView.frame.maxY, targetView: itemsImageView)
The problem was in the line:
bannerLabel.translatesAutoresizingMaskIntoConstraints = false
As soon as this line was removed, the problem has dissapeared.
I've successfully created an extension on UIViewController that allows me to call a function that triggers a UIView to animate down. However instead of just a plain UIView, I want to improve on the extension so that it is it takes two parameters (background color, and label text) that are a subview on the UIView 'popupAlert?
extension UIViewController {
func showAlert() {
let popupAlert = UIView(frame: CGRect(x: 0, y: -60, width: view.frame.width, height: 60))
popupAlert.backgroundColor = UIColor.brandSuccess()
let window = (UIApplication.shared.delegate as! AppDelegate).window!
window.addSubview(popupAlert)
UIView.animate(withDuration: 0.6, delay: 0, options: .curveEaseIn, animations: {
popupAlert.transform = .init(translationX: 0, y: 60)
}) { (_) in
UIView.animate(withDuration: 0.6, delay: 4, options: .curveEaseIn, animations: {
popupAlert.transform = .init(translationX: 0, y: -60)
}, completion: { (_) in
popupAlert.removeFromSuperview()
})
}
}
}
extension UIViewController {
func showAlert(bgColor: UIColor, lblText: String) {
let popupAlert = UIView(frame: CGRect(x: 0, y: -60, width: view.frame.width, height: 60))
let popupLbl = UILabel(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 60))
popupAlert.backgroundColor = bgColor
popupLbl.text = lblText
popupLbl.textColor = .green
popupLbl.textAlignment = .center
let window = (UIApplication.shared.delegate as! AppDelegate).window!
popupAlert.addSubview(popupLbl)
window.addSubview(popupAlert)
UIView.animate(withDuration: 0.6, delay: 0, options: .curveEaseIn, animations: {
popupAlert.transform = .init(translationX: 0, y: 60)
}) { (_) in
UIView.animate(withDuration: 0.6, delay: 4, options: .curveEaseIn, animations: {
popupAlert.transform = .init(translationX: 0, y: -60)
}, completion: { (_) in
popupAlert.removeFromSuperview()
})
}
}
}
call
showAlert(bgColor: .red, lblText: "testtttt")
This is a simple function I am using for animating a view from top to bottom and vice versa (if is top to bottom animation and else is bottom to top animation) :
#objc func openMenu(sender: UIButton) {
if sender.tag == 1 {
self.buttonView.tag = 2
self.moduleView = ModulesCollectionView(frame: CGRect(x: 0, y: self.frame.origin.y + self.frame.size.height + 20, width: UIScreen.main.bounds.size.width, height: 0), collectionViewLayout: UICollectionViewLayout())
self.window?.addSubview(self.moduleView)
UIView.animate(withDuration: 0.7, animations: {
self.moduleView.frame = CGRect(x: 0, y: self.frame.origin.y + self.frame.size.height + 20, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height - self.frame.size.height - 22)
}, completion: { _ in
})
} else {
self.buttonView.tag = 1
UIView.animate(withDuration: 3, animations: {
self.moduleView.frame = CGRect(x: 0, y: self.frame.origin.y + self.frame.size.height + 20, width: UIScreen.main.bounds.size.width, height: 0)
}, completion: { _ in
self.moduleView.removeFromSuperview()
})
}
}
Top animation works fine and the view is animated from top to bottom pleasantly in 0.7 seconds. However, bottom to top animation does not happen. The view is removed instantly. This is the result I am getting :
But I want the animation to be clean while going from bottom to top as well. Just like here.
Secondary : What I finally plan to achieve is PullUpController with the exact reverse animation. So if anyone knows a similar library (pull down drag) can share there inputs.
Update : The issue is coming only with UICollectionView. I replaced collectionView with a simple UIView and it worked perfect.
You should try to use layoutSubViews() method after at the end of animation block. Change the animation block like this.
For if block:
self.moduleView.frame = CGRect(x: 0, y: self.frame.origin.y + self.frame.size.height + 20, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height - self.frame.size.height - 22)
self.moduleView.layoutSubviews()
For else block:
self.moduleView.frame = CGRect(x: 0, y: self.frame.origin.y + self.frame.size.height + 20, width: UIScreen.main.bounds.size.width, height: 0)
self.moduleView.layoutSubviews()
Here is example code, hope it will help.
Show view:
self.containerView = ModulesCollectionView(frame: UIScreen.main.bounds)
self.containerView.center = CGPoint(x: self.view.center.x,
y: self.view.center.y + self.view.frame.size.height)
self.window?.addSubview(self.moduleView)
self.window?.bringSubview(toFront: self.moduleView)
self.window?.endEditing(true)
UIView.animate(withDuration: 0.3, delay: 0.0,
usingSpringWithDamping: 0.7, initialSpringVelocity: 3.0,
options: .allowAnimatedContent, animations: {
self.moduleView.center = self.view.center
}) { (isFinished) in
self.view.layoutIfNeeded()
}
hide view:
UIView.animate(withDuration: 0.7, delay: 0.0,
usingSpringWithDamping: 1, initialSpringVelocity: 1.0,
options: .allowAnimatedContent, animations: {
self.moduleView.center = CGPoint(x: self.view.center.x,
y: self.view.center.y + self.view.frame.size.height)
}) { (isFinished) in
self.moduleView.removeFromSuperview
}
I'm trying to create a slide in menu that comes in from the left but I've done something and its coming in onto the right instead. I've programmatically done this not used storyboards as most of the time the constraints messes up.
for example heres a screenshot (probably something I've done):
Slide Menu - Gone Wrong
import UIKit
class SideInMenu: NSObject {
let blackView = UIView()
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
return cv
}()
#objc func showSlideMenu() {
//Show slide in menu Here
if let window = UIApplication.shared.keyWindow {
blackView.backgroundColor = UIColor(white: 0, alpha: 0.5)
blackView.addGestureRecognizer(UITapGestureRecognizer(target:
self, action: #selector(handleDismiss)))
window.addSubview(blackView)
window.addSubview(collectionView)
let width: CGFloat = 194
let x = window.frame.width - width
collectionView.frame = CGRectMake(x, 0, width, window.frame.height)
//collectionView.frame = CGRectMake(x, 0, width, window.frame.height)
blackView.frame = window.frame
blackView.alpha = 0
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping:
1, initialSpringVelocity: 1, options: .curveEaseOut,
animations: {
self.blackView.alpha = 1
self.collectionView.frame = self.CGRectMake(width, 0, self.collectionView.frame.width, self.collectionView.frame.height)
}, completion: nil)
}
}
#objc func handleDismiss() {
UIView.animate(withDuration: 0.5) {
self.blackView.alpha = 0
if let window = UIApplication.shared.keyWindow {
self.collectionView.frame = self.CGRectMake(window.frame.width, 0, self.collectionView.frame.width, self.collectionView.frame.height)
}
}
}
override init() {
super.init()
}
func CGRectMake(_ x: CGFloat, _ y: CGFloat, _ width: CGFloat, _ height: CGFloat) -> CGRect {return CGRect(x: x, y: y, width: width, height: height)
}
}
Always remember that 0, 0 for x and y is always the top left of the screen and if the width and height is 20, 20, it will go from the top left, right 20px across and down 20px. The first thing you have to do inside you viewDidLoad is to set the collectionViews initial frame, which is off screen like this. DON'T FORGET MINUS WIDTH:
self.collectionView.frame = self.CGRectMake(-width, 0, self.collectionView.frame.width, self.collectionView.frame.height)
Animate it in from the left like so:
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping:
1, initialSpringVelocity: 1, options: .curveEaseOut,
animations: {
self.blackView.alpha = 1
self.collectionView.frame = self.CGRectMake(0, 0, self.collectionView.frame.width, self.collectionView.frame.height)
}, completion: nil)
Dismiss with:
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping:
1, initialSpringVelocity: 1, options: .curveEaseOut,
animations: {
self.blackView.alpha = 1
self.collectionView.frame = self.CGRectMake(-width, 0, self.collectionView.frame.width, self.collectionView.frame.height)
}, completion: nil)