Animate UIView out scaled behaves strangely - ios

I've had this problem before, but couldn't figure out what it is.
If I'm animating a view "standalone" in the Playground, this sort of thing looks fine, but now it just looks like in the provided GIF.
Animate it in look fine, but when I want to animate it out (scale) it just gets maxed-out size then disappears.
Here's the code that animates it:
self.circleView.transform = CGAffineTransformIdentity
UIView.animateWithDuration(0.5, delay: 0, options: [], animations: {
self.circleView.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.001, 0.001);
}, completion: {
finished in
if finished {
self.resetStyle()
self.circleView.hidden = true
self.circleView.transform = CGAffineTransformIdentity
}
})
func resetStyle() {
self.circleView.transform = CGAffineTransformIdentity
self.circleView.backgroundColor = nil
self.textLabel.textColor = UIColor.blackColor()
}

Try this:
#IBOutlet weak var circleView: UIView!
#IBAction func leftAction(sender: AnyObject) {
self.circleView.layer.transform = CATransform3DMakeScale(0.001, 0.001, 1)
UIView.animateWithDuration(0.5) {
self.circleView.layer.transform = CATransform3DIdentity
}
}
#IBAction func rightAction(sender: AnyObject) {
circleView.layer.transform = CATransform3DIdentity
UIView.animateWithDuration(0.5) {
self.circleView.layer.transform = CATransform3DMakeScale(0.001, 0.001, 1)
}
}
override func viewDidLoad() {
super.viewDidLoad()
circleView.backgroundColor = .blueColor()
circleView.layer.cornerRadius = CGRectGetMidY(circleView.bounds)
}

Related

Animating UIButton configuration change

Modern UIButton configuration API allows for new way of setting button appearance. However the code below skips the animation entirely.
#IBAction func buttonTouched(_ sender: UIButton) {
sender.configuration?.background.backgroundColor = .green
UIView.animate(withDuration: 0.4, delay: 0.5) {
sender.configuration?.background.backgroundColor = .systemMint
}
}
Similar thing can be done like this:
#IBAction func buttonTouched(_ sender: UIButton) {
sender.backgroundColor = .red
UIView.animate(withDuration: 0.4, delay: 0.5) {
sender.backgroundColor = .systemMint
}
}
And this works. The question is how to animate UIButton's configuration changes.
After some quick searching, it appears configuration.background.backgroundColor is not animatable.
Depending on your needs, you can use a .customView for the button's background and then animate the color change for that view.
Quick example (assuming you've added the button as an #IBOutlet):
class TestVC: UIViewController {
#IBOutlet var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
let v = UIView()
v.backgroundColor = .red
button.configuration?.background.customView = v
}
#IBAction func buttonTouched(_ sender: UIButton) {
if let v = sender.configuration?.background.customView {
UIView.animate(withDuration: 0.4, delay: 0.5) {
v.backgroundColor = v.backgroundColor == .red ? .systemMint : .red
}
}
}
}
The way to achieve animated configuration change by using transitions is the only workaround I could find so far. Somewhat like this:
#IBAction func buttonTouched(_ sender: UIButton) {
UIView.transition(with: sender, duration: 0.3, options: .transitionCrossDissolve) {
sender.configuration?.background.backgroundColor = .red
} completion: { _ in
UIView.transition(with: sender, duration: 1.0, options: .transitionCrossDissolve) {
sender.configuration?.background.backgroundColor = .cyan
}
}
}
When I tried to optimize and reuse code, I ended with something like:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Another option is subclass `UIButton` and override its `configurationUpdateHandler`
view.subviews.compactMap({ $0 as? UIButton }).forEach {
$0.configurationUpdateHandler = { button in
guard let config = button.configuration else { return }
button.setConfiguration(config,duration: 2)
}
}
}
#IBAction func buttonTouched(_ sender: UIButton) {
// sender.configuration?.background.backgroundColor = .red
// sender.configuration?.baseForegroundColor = .white
// The above would work, but we should rather aggregate all changes at once.
if var config = sender.configuration {
config.background.backgroundColor = .red
config.baseForegroundColor = .white
sender.configuration = config
}
}
}
extension UIButton {
func setConfiguration(_ configuration: UIButton.Configuration, duration: Double = 0.25, completion: ((Bool) -> Void)? = nil) {
UIView.transition(with: self, duration: duration, options: .transitionCrossDissolve) {
self.configuration? = configuration
} completion: { completion?($0) }
}
}
I

Change ViewController after animation

I have created this animation for the first screen of the app(I'm not using Launchscreen) but when the animation ends I don't know how to pass to the next view controller. I've tired by segue but nothing :/
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var logoLSMini: UIImageView!
#IBOutlet weak var logoLSMini2: UIImageView!
#IBOutlet weak var logoLS: UIImageView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.logoLSMini.alpha = 0.0
self.logoLSMini2.alpha = 0.0
self.logoLS.alpha = 0.0
self.logoLS.frame.origin.y = +100
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 5, delay: 0.0, options: .curveEaseOut , animations: {
//FIRST EFFECT
self.logoLSMini.alpha = 1.0
self.logoLSMini.transform = CGAffineTransform(rotationAngle: 360)
self.logoLSMini.transform = CGAffineTransform(scaleX: 100, y: 100)
self.logoLS.alpha = 1.0
}, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
You simply need to perform the segue in the completion of UIView.animate. Just make sure you substitute whatever identifier you added to your segue in Storyboard for yourSegue.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 5, delay: 0.0, options: .curveEaseOut , animations: {
//FIRST EFFECT
self.logoLSMini.alpha = 1.0
self.logoLSMini.transform = CGAffineTransform(rotationAngle: 360)
self.logoLSMini.transform = CGAffineTransform(scaleX: 100, y: 100)
self.logoLS.alpha = 1.0
}, completion: { _ in
self.performSegue(withIdentifier: "yourSegue", sender: nil)
})
}
Try to put the code of moving viewController in the completionHandler
UIView.animate(withDuration: 5, delay: 0.0, options: .curveEaseOut, animations: {
.....
}, completion: { (finished) in
if finished {
//Move to your next controller
DispatchQueue.main.async {
//Your code
}
}
})

How to solve this problem with custom transition in iOS?

I built a class to implement a circular transition between view controllers. When I hit the button to navigate to the other view controller a circle starts growing from the button until it fills the screen with the new controller. When I dismiss the view controller I expected this circle to shrink down back to the original position. It's also working. The only problem is that when the dismiss is underway the back of the screen while the circle is shrinking is completely black and after the animation is completed the new viewController appears abruptly.
Here are some photos of the effect:
Here's the code of the custom class:
class customTransition: NSObject, UIViewControllerAnimatedTransitioning{
var duration: TimeInterval = 0.5
var startPoint = CGPoint.zero
var circle = UIView()
var circleColor = UIColor.white
enum transitMode: Int {
case presenting, dismissing
}
var transitionMode: transitMode = .presenting
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let container = transitionContext.containerView
guard let to = transitionContext.view(forKey: UITransitionContextViewKey.to) else {return}
guard let from = transitionContext.view(forKey: UITransitionContextViewKey.from) else {return}
circleColor = to.backgroundColor ?? UIColor.white
if transitionMode == .presenting {
to.translatesAutoresizingMaskIntoConstraints = false
to.center = startPoint
circle = UIView()
circle.backgroundColor = circleColor
circle.frame = getFrameForCircle(rect: to.frame)
circle.layer.cornerRadius = circle.frame.width / 2
circle.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
circle.alpha = 0
circle.addSubview(to)
to.centerXAnchor.constraint(equalTo: circle.centerXAnchor).isActive = true
to.centerYAnchor.constraint(equalTo: circle.centerYAnchor).isActive = true
to.widthAnchor.constraint(equalToConstant: to.frame.width).isActive = true
to.heightAnchor.constraint(equalToConstant: to.frame.height).isActive = true
container.addSubview(circle)
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: UIView.AnimationOptions.curveLinear, animations: {
self.circle.center = from.center
self.circle.transform = CGAffineTransform.identity
self.circle.alpha = 1
}) { (sucess) in
transitionContext.completeTransition(sucess)
}
} else if transitionMode == .dismissing {
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: UIView.AnimationOptions.curveLinear, animations: {
self.circle.center = self.startPoint
self.circle.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
self.circle.alpha = 0
}) { (sucess) in
transitionContext.completeTransition(sucess)
}
}
}
func getFrameForCircle(rect: CGRect) -> CGRect{
let width = Float(rect.width)
let height = Float(rect.height)
let diameter = CGFloat(sqrtf(width * width + height * height))
let x: CGFloat = rect.midX - (diameter / 2)
let y: CGFloat = rect.midY - (diameter / 2)
return CGRect(x: x, y: y, width: diameter, height: diameter)
}
}
and the implementation...
let circularTransition = customTransition()
the call for the present view controller... I tried to set secondVC.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext but when I set this line it ignores completely the animation transition I don't know why...
`
#objc func handlePresent(sender: UIButton){
let secondVC = nextVC()
secondVC.transitioningDelegate = self
present(secondVC, animated: true, completion: nil)
}
delegate methods:
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
circularTransition.startPoint = presentButton.center
circularTransition.transitionMode = .presenting
return circularTransition
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
circularTransition.transitionMode = .dismissing
circularTransition.startPoint = presentButton.center
return circularTransition
}
What am I missing here? Any suggestions?
No storyboard being used, just code.
If you don't use navigationController, it's necessary to use the .custom mode in the presentedviewController.
import UIKit
class TransViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
let circularTransition = customTransition()
#IBOutlet var presentButton : UIButton!
#IBAction func handlePresent(sender: UIButton){
if let secondVC = storyboard?.instantiateViewController(withIdentifier: "next"){
secondVC.modalPresentationStyle = .custom
secondVC.transitioningDelegate = self
present(secondVC, animated: true, completion: nil)
}
}
}
class BackViewController: UIViewController {
#IBAction func dismissMe(sender: UIButton){
self.dismiss(animated: true, completion: nil)
}
}
extension TransViewController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
circularTransition.startPoint = presentButton.center
circularTransition.transitionMode = .presenting
return circularTransition
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
circularTransition.transitionMode = .dismissing
circularTransition.startPoint = presentButton.center
return circularTransition
}
}
If there is no from or to view, we have use the from and to view from containView.
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let container = transitionContext.containerView
var to : UIView!
var from : UIView!
to = transitionContext.view(forKey: UITransitionContextViewKey.to)
if to == nil {to = container}
from = transitionContext.view(forKey: UITransitionContextViewKey.from)
if from == nil {from = container}
The rest is same:
circleColor = to.backgroundColor ?? UIColor.white
if transitionMode == .presenting {
to.translatesAutoresizingMaskIntoConstraints = false
to.center = startPoint
circle = UIView()
circle.backgroundColor = circleColor
circle.frame = getFrameForCircle(rect: to.frame)
circle.layer.cornerRadius = circle.frame.width / 2
circle.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
circle.alpha = 0
circle.addSubview(to)
to.centerXAnchor.constraint(equalTo: circle.centerXAnchor).isActive = true
to.centerYAnchor.constraint(equalTo: circle.centerYAnchor).isActive = true
to.widthAnchor.constraint(equalToConstant: to.frame.width).isActive = true
to.heightAnchor.constraint(equalToConstant: to.frame.height).isActive = true
container.addSubview(circle)
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: UIView.AnimationOptions.curveLinear, animations: {
self.circle.center = from.center
self.circle.transform = CGAffineTransform.identity
self.circle.alpha = 1
}) { (sucess) in
transitionContext.completeTransition(sucess)
}
} else if transitionMode == .dismissing {
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: UIView.AnimationOptions.curveLinear, animations: {
self.circle.center = self.startPoint
self.circle.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
self.circle.alpha = 0
}) { (sucess) in
transitionContext.completeTransition(sucess)
}
}
}

Animation not working when home button is pressed and the app is relaunch again

My animation stopped running when I press the home button and then relaunch the app. The settings button just stop spinning and the blink label just faded away. Here is my code for both animation:
Blink animation:
extension UILabel {
func startBlink() {
UIView.animate(withDuration: 0.8,
delay:0.0,
options:[.autoreverse, .repeat],
animations: {
self.alpha = 0
}, completion: nil)
}
}
Rotating animation:
extension UIButton {
func startRotating() {
UIView.animate(withDuration: 4.0, delay: 0.0, options:[.autoreverse, .repeat,UIViewAnimationOptions.allowUserInteraction], animations: {
self.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
}, completion: nil)
}
}
Where I run it:
override func viewDidLoad() {
super.viewDidLoad()
settingsButton.layer.cornerRadius = 0.5 * settingsButton.bounds.size.width
settingsButton.clipsToBounds = true
settingsButton.imageView?.contentMode = .scaleAspectFit
NotificationCenter.default.addObserver(self, selector: #selector(appMovedToForeground), name: Notification.Name.UIApplicationWillEnterForeground, object: nil)
}
func appMovedToForeground() {
tapToPlayLabel.startBlink()
settingsButton.startRotating()
print("DID")
}
To restart your animation you have to do below thing, please check below code.
Check extension
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var tapToPlayLabel: UILabel!
#IBOutlet weak var settingsButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
settingsButton.layer.cornerRadius = settingsButton.frame.size.width/2
settingsButton.clipsToBounds = true
//settingsButton.imageView?.contentMode = .scaleAspectFit
settingsButton.startRotating()
tapToPlayLabel.startBlink()
NotificationCenter.default.addObserver(self, selector: #selector(appMovedToForeground), name: Notification.Name.UIApplicationWillEnterForeground, object: nil)
}
func appMovedToForeground() {
self.tapToPlayLabel.startBlink()
self.settingsButton.startRotating()
}
}
extension UILabel {
func startBlink() {
self.alpha = 1
UIView.animate(withDuration: 0.8,
delay:0.0,
options:[.autoreverse, .repeat],
animations: {
self.alpha = 0
}, completion: nil)
}
}
extension UIButton {
func startRotating() {
self.transform = CGAffineTransform(rotationAngle: CGFloat.pi/2)
UIView.animate(withDuration: 4.0, delay: 0.0, options:[.autoreverse, .repeat,UIViewAnimationOptions.allowUserInteraction], animations: {
self.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
}, completion: nil)
}
}
Output
I think you need to run your animations with a little delay as there is already the delegate of app in execution while app is moving to foreground.
Or you can add CALayerAnimation on UILabel
extension UILabel {
func startBlink() {
let scaleAnimation = CAKeyframeAnimation(keyPath: "transform")
scaleAnimation.delegate = self as? CAAnimationDelegate
let transform: CATransform3D = CATransform3DMakeScale(1.5, 1.5, 1)
scaleAnimation.values = [NSValue(caTransform3D: CATransform3DIdentity), NSValue(caTransform3D: transform), NSValue(caTransform3D: CATransform3DIdentity)]
scaleAnimation.duration = 0.5
scaleAnimation.repeatCount = 100000000
self.layer.add(scaleAnimation as? CAAnimation ?? CAAnimation(), forKey: "scaleText")
}
func startRotating() {
let rotation : CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotation.fromValue = 0
rotation.toValue = NSNumber(value: Double.pi * 2)
rotation.duration = 2
rotation.isCumulative = true
rotation.repeatCount = .greatestFiniteMagnitude
self.layer.add(rotation, forKey: "rotationAnimation")
}
}

.xib Called Inside ViewController Does not Recognize Touch Events

Inside a UIViewController, I call a .xib file and present it over the current UIView.
// initiate the pop up ad view and display it
let popUpAdView = PopUpAdViewController(nibName: "PopUpAdView", bundle: nil)
popUpAdView.displayIntoSuperView(view)
There is a button inside that .xib file that should remove itself from the screen when it's touched. However it doesn't perform so.
What exactly am I missing?
class PopUpAdViewController: UIViewController {
#IBOutlet weak var popUpAdView: UIView!
#IBOutlet weak var closeButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// adjust the view size from the device's screen size
let screenSize = UIScreen.mainScreen().bounds
view.frame = CGRectMake(0, 64, screenSize.width, screenSize.height-64)
// cosmetically adjust the view itself
view.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.6)
view.userInteractionEnabled = true
closeButton.userInteractionEnabled = true
// style the pop up ad view
popUpAdView.layer.cornerRadius = 5
popUpAdView.layer.shadowOpacity = 0.8
popUpAdView.layer.shadowOffset = CGSizeMake(0.0, 0.0)
closeButton.addTarget(self, action: #selector(removeOutOfSuperView), forControlEvents: .TouchUpInside)
}
func displayIntoSuperView(superView: UIView!) {
superView.addSubview(self.view)
// define the initial cosmetical values of the items
popUpAdView.transform = CGAffineTransformMakeScale(0.3, 0.3)
popUpAdView.alpha = 0.0
closeButton.alpha = 0.0
// animate...
UIView.animateWithDuration(0.8, delay: 0.0, options: .CurveEaseIn, animations: {
self.popUpAdView.alpha = 1.0
self.popUpAdView.transform = CGAffineTransformMakeScale(1.0, 1.0)
}) { (Bool) in
UIView.animateWithDuration(0.6, delay: 1.5, options: .CurveEaseIn, animations: {
self.closeButton.alpha = 1.0
}, completion: { (Bool) in
})
}
}
func removeOutOfSuperView() {
UIView.animateWithDuration(0.5, delay: 0.0, options: .CurveEaseIn, animations: {
self.closeButton.alpha = 0.0
self.popUpAdView.transform = CGAffineTransformMakeScale(0.1, 0.1)
}) { (finished) in
UIView.animateWithDuration(0.8, delay: 0.0, options: .CurveEaseIn, animations: {
self.view.alpha = 0.0
self.view.removeFromSuperview()
}, completion: nil)
}
}
#IBAction func closePopUpAdView(sender: AnyObject) {
print("Closing the pop up ad...")
removeOutOfSuperView()
}
}
Update
.xib structureL:

Resources