Custom VC Transition not Dismissing Properly - ios

I am trying to recreate the stock UIAlertAction in a VC with a custom transition. I currently have the presenting working perfectly(backgroundView fades in and the 'notification' VC slides from the bottom). The problem I am facing is when I dismiss the VC the backgroundView doesn't fade out. It's as if it's completely bypassing my animation block. Once completeTransition is called the backgroundView disappears completely. What is happening?
class AnimationController: NSObject {
let backgroundView = UIView()
}
extension AnimationController: UIViewControllerAnimatedTransitioning {
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard let toViewController = transitionContext.viewController(forKey: .to),
let fromViewController = transitionContext.viewController(forKey: .from) else {
transitionContext.completeTransition(false)
return
}
switch animationType {
case .present:
backgroundView.frame = CGRect(x: 0, y: 0, width: toViewController.view.frame.width, height: fromViewController.view.frame.height)
backgroundView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.4)
backgroundView.alpha = 0
containerView.addSubview(backgroundView)
let viewToAnimate = toViewController.view!
containerView.addSubview(viewToAnimate)
toViewController.view.clipsToBounds = true
viewToAnimate.frame = CGRect(x: 0, y: viewToAnimate.frame.maxY, width: viewToAnimate.frame.width, height: viewToAnimate.frame.height)
let duration = transitionDuration(using: transitionContext)
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.80, initialSpringVelocity: 0.1, options: .curveEaseInOut, animations: {
self.backgroundView.alpha = 1.0
viewToAnimate.transform = CGAffineTransform(translationX: 0, y: -viewToAnimate.frame.height)
}) { _ in
transitionContext.completeTransition(true)
}
case .dismiss:
containerView.addSubview(fromViewController.view!)
let testView = fromViewController.view!
backgroundView.frame = CGRect(x: 0, y: 0, width: testView.frame.width, height: testView.frame.height)
backgroundView.backgroundColor = UIColor(red: 1, green: 0, blue: 0, alpha: 1)
let duration = transitionDuration(using: transitionContext)
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.80, initialSpringVelocity: 0.1, options: .curveEaseInOut, animations: {
self.backgroundView.alpha = 0.0
testView.transform = CGAffineTransform(translationX: 0, y: testView.frame.height)
print("backgroundView Doesn't fade out")
}) { _ in
print("backgroundView disappears here")
transitionContext.completeTransition(true)
}
}
}

Your problem is that you're dealing with two separate instances of your AnimationController and hence two separate instances of backgroundView. One instance is instantiated for presentation and the other is instantiated for dismissal. Notice in your case .present: block you are calling containerView.addSubview(backgroundView) but you don't do the same in the case .dismiss: block. As a result, the backgroundView instance in the case .dismiss: block isn't even part of the view hierarchy.
What you need to do is call self.backgroundView.removeFromSuperview() in your first animation completion block. Then in your case .dismiss: you need to call containerView.addSubview(backgroundView) once again.
Try this...
switch animationType {
case .present:
// ...
containerView.addSubview(backgroundView)
// ...
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.80, initialSpringVelocity: 0.1, options: .curveEaseInOut, animations: {
self.backgroundView.alpha = 1.0
viewToAnimate.transform = CGAffineTransform(translationX: 0, y: -viewToAnimate.frame.height)
}) { _ in
self.backgroundView.removeFromSuperview()
transitionContext.completeTransition(true)
}
case .dismiss:
// ...
containerView.addSubview(backgroundView)
containerView.addSubview(fromViewController.view!)
// ...
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.80, initialSpringVelocity: 0.1, options: .curveEaseInOut, animations: {
self.backgroundView.alpha = 0.0
testView.transform = CGAffineTransform(translationX: 0, y: testView.frame.height)
print("backgroundView Doesn't fade out")
}) { _ in
print("backgroundView disappears here")
transitionContext.completeTransition(true)
}
}

you could try to present like this
//where DevolucionVC is you ViewController
if let controller = DevolucionVC() as? DevolucionVC {
if let window = self.window, let rootViewController = window.rootViewController {
var currentController = rootViewController
while let presentedController = currentController.presentedViewController {
currentController = presentedController
}
currentController.present(controller, animated: true, completion: nil)
}
}
you can dismiss it later by simply use
dismiss(animated: true, completion: nil)

Try This
for present replace this code
backgroundView.frame = CGRect(x: 0, y: 0, width: toViewController.view.frame.width, height: fromViewController.view.frame.height)
backgroundView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.4)
backgroundView.alpha = 0
containerView.addSubview(backgroundView)
let transition = CATransition()
transition.duration = 0.5
transition.type = CATransitionType.moveIn
transition.subtype = CATransitionSubtype.fromTop
containerView.layer.add(transition, forKey: kCATransition)
For Dismiss Replace this code
containerView.addSubview(fromViewController.view!)
let transition = CATransition()
transition.duration = 0.5
transition.type = CATransitionType.fade
containerView.layer.add(transition, forKey: kCATransition)
backgroundView.frame = CGRect(x: 0, y: 0, width: testView.frame.width, height: testView.frame.height)
backgroundView.backgroundColor = UIColor(red: 1, green: 0, blue: 0, alpha: 1)
self.backgroundView.alpha = 0.0

Related

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)
}
}

UIView.animate Ignoring duration on custom pop I created. Swift 5

let bigView = UIViewController()
let pView = UIView()
extension UIViewController {
func showPopUp() {
bigView.modalPresentationStyle = .overFullScreen //or .overFullScreen for transparency
self.present(bigView, animated: false, completion: nil)
let bColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1).withAlphaComponent(0.4)
blurView.frame = bigView.view.bounds
blurView.backgroundColor = bColor
bigView.view.addSubview(blurView)
pView.frame = CGRect(x: 0, y: 0, width: 300, height: 180)
if self.traitCollection.userInterfaceStyle == .light {
pView.backgroundColor = #colorLiteral(red: 0.9778117069, green: 0.9778117069, blue: 0.9778117069, alpha: 1) } else {pView.backgroundColor = #colorLiteral(red: 0.2506543464, green: 0.2506543464, blue: 0.2506543464, alpha: 1)}
pView.layer.cornerRadius = 15
pView.isOpaque = false
pView.center = blurView.center
blurView.addSubview(pView)
pView.transform = CGAffineTransform(scaleX: 1, y: 1)
pView.alpha = 0
UIView.animate(withDuration: 0.8, delay: 0.5, options: [], animations: {
pView.alpha = 1
pView.transform = CGAffineTransform(scaleX: 1.1, y: 1.1)
self.view.layoutIfNeeded()
}, completion: nil)
}
}
Hey guys here is a shortened version of my code, not sure why its not animating with a duration... when I click the button I have assigned it just jumps the scale to (scaleX: 1.1, y: 1.1).. instead of gradually animating. Any idea why ?

Run two animations simultaneously

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.

How do I set the status bar in iOS to show a message?

I'm looking to set a message just below the status bar, and possibly changing the color of the message (see photo - am looking to set a message where "Sleep Cycle" is at).
However, I've looked at iOS' Human Design Guidelines, but am unable to ascertain what this control is called.
https://developer.apple.com/ios/human-interface-guidelines/ui-bars/navigation-bars/
Just a point in the right direction will be of great help.
Try this code: Code tested in Xcode 8.
/Update your plist with below code
View controller-based status bar appearance = NO
//In your VC:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
title = "Some Title"
navigationController?.navigationBar.tintColor = UIColor.white
navigationController?.navigationBar.barTintColor = UIColor.red
navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName:UIColor.white]
UIApplication.shared.statusBarStyle = .lightContent // To change your status bar to display light content (In white colour)
}
func sleepCycleNotify() {
// To set BannerView
let barView = UIView(frame: CGRect(x:0, y:0, width:view.frame.width, height:(UINavigationController().navigationBar.frame.height)))
barView.backgroundColor=UIColor.red // set to any colour you want..
navigationController?.navigationBar.addSubview(barView)
let notifyLabel = UILabel()
notifyLabel.frame = CGRect(x:0, y:0, width:view.frame.width, height:(UINavigationController().navigationBar.frame.height))
notifyLabel.backgroundColor=UIColor.clear
notifyLabel.text = "Sleep Cycle"
notifyLabel.textAlignment = .center
notifyLabel.textColor = UIColor.white
notifyLabel.alpha = 0.8
barView.addSubview(notifyLabel)
// Animation 1:
// To achive animation
barView.center.y -= (navigationController?.navigationBar.bounds.height)!
UIView.animate(withDuration: 0.7, delay: 0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0.6, options: UIViewAnimationOptions.curveEaseIn, animations:{
barView.center.y += (self.navigationController?.navigationBar.frame.height)!
}, completion:{ finished in
UIView.animate(withDuration: 1, delay: 1.5, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.9, options: UIViewAnimationOptions.curveEaseOut, animations:{
barView.center.y -= ((self.navigationController?.navigationBar.frame.height)! + UIApplication.shared.statusBarFrame.height)
}, completion: nil)
})
}
Output from above code:
//Animation 2:
func sleepCycleNotify() {
//
let barView = UIView(frame: CGRect(x:0, y:0, width:view.frame.width, height:(UINavigationController().navigationBar.frame.height)))
barView.backgroundColor=UIColor.red // set any colour you want..
navigationController?.navigationBar.addSubview(barView)
let notifyLabel = UILabel()
notifyLabel.frame = CGRect(x:0, y:0, width:view.frame.width, height:(UINavigationController().navigationBar.frame.height))
notifyLabel.backgroundColor=UIColor.clear
notifyLabel.text = "Sleep Cycle"
notifyLabel.textAlignment = .center
notifyLabel.textColor = UIColor.white
notifyLabel.alpha = 0.8
barView.addSubview(notifyLabel)
// To achive animation
barView.center.y -= (navigationController?.navigationBar.bounds.height)!
UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0.6, options: UIViewAnimationOptions.curveEaseIn, animations:{
UIApplication.shared.isStatusBarHidden = true
UINavigationController().navigationBar.isHidden = true
barView.center.y += (self.navigationController?.navigationBar.frame.height)!
}, completion:{ finished in
UIView.animate(withDuration: 1.5, delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.9, options: UIViewAnimationOptions.curveEaseOut, animations:{
// notifyLabel.alpha = 0...1
UIApplication.shared.isStatusBarHidden = false
UINavigationController().navigationBar.isHidden = false
barView.center.y -= ((self.navigationController?.navigationBar.frame.height)! + UIApplication.shared.statusBarFrame.height)
}, completion: nil)
})
}
Output from animation 2:
Improved Answer:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
title = "Some Title"
navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName:UIColor.white]
navigationController?.navigationBar.barTintColor = UIColor.purple
UIApplication.shared.statusBarStyle = .lightContent
}
func sleepCycleNotify() {
// To set BannerView
let barView = UIView(frame: CGRect(x:0, y:-UIApplication.shared.statusBarFrame.height, width:view.frame.width, height:(UINavigationController().navigationBar.frame.height) + UIApplication.shared.statusBarFrame.height ))
barView.backgroundColor=UIColor.red // set to any colour you want..
navigationController?.navigationBar.addSubview(barView)
let notifyLabel = UILabel()
notifyLabel.frame = CGRect(x:0, y:UIApplication.shared.statusBarFrame.height, width:view.frame.width, height:(UINavigationController().navigationBar.frame.height))
notifyLabel.backgroundColor=UIColor.clear
notifyLabel.numberOfLines = 0
notifyLabel.text = "Sleep Cycle"
notifyLabel.textAlignment = .center
notifyLabel.textColor = UIColor.white
notifyLabel.alpha = 0.8
barView.addSubview(notifyLabel)
// Animation 1:
// To achive animation
barView.center.y -= (navigationController?.navigationBar.bounds.height)!
UIView.animate(withDuration: 0.7, delay: 0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0.6, options: UIViewAnimationOptions.curveEaseIn, animations:{
barView.center.y += (self.navigationController?.navigationBar.frame.height)!
}, completion:{ finished in
UIView.animate(withDuration: 1, delay: 1.5, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.9, options: UIViewAnimationOptions.curveEaseOut, animations:{
barView.center.y -= ((self.navigationController?.navigationBar.frame.height)! + UIApplication.shared.statusBarFrame.height)
}, completion: nil)
})
}
Output from Animation 3:

Swift: Is it possible to remove View Controller from stack?

I found a Github repository where the user has created a launch animation like in twitters app: This
This works best if the icon is a solid shape and being centered in the screen.
My problem is that I can center my icon because how its designed. So when my animation is finished the app will still have some masking left on the screen from the previously View Controller that made the animation.
So is it possible to remove that View Controller completely once the animation is done and the app hits the table view View Controller?
Here is my code:
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window!.backgroundColor = UIColor(red: 241/255, green: 196/255, blue: 15/255, alpha: 1)
self.window!.makeKeyAndVisible()
// rootViewController from StoryBoard
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
var navigationController = mainStoryboard.instantiateViewControllerWithIdentifier("navigationController") as! UIViewController
self.window!.rootViewController = navigationController
// logo mask
navigationController.view.layer.mask = CALayer()
navigationController.view.layer.mask.contents = UIImage(named: "logo.png")!.CGImage
navigationController.view.layer.mask.bounds = CGRect(x: 0, y: 0, width: 10, height: 10)
navigationController.view.layer.mask.anchorPoint = CGPoint(x: 0.5, y: 0.5)
navigationController.view.layer.mask.position = CGPoint(x: navigationController.view.frame.width / 2, y: navigationController.view.frame.height / 2)
// logo mask background view
var maskBgView = UIView(frame: navigationController.view.frame)
maskBgView.backgroundColor = UIColor.whiteColor()
navigationController.view.addSubview(maskBgView)
navigationController.view.bringSubviewToFront(maskBgView)
// logo mask animation
let transformAnimation = CAKeyframeAnimation(keyPath: "bounds")
transformAnimation.delegate = self
transformAnimation.duration = 1
transformAnimation.beginTime = CACurrentMediaTime() + 1 //add delay of 1 second
let initalBounds = NSValue(CGRect: navigationController.view.layer.mask.bounds)
let secondBounds = NSValue(CGRect: CGRect(x: 0, y: 0, width: 50, height: 50))
let finalBounds = NSValue(CGRect: CGRect(x: 0, y: 0, width: 2000, height: 2000))
transformAnimation.values = [initalBounds, secondBounds, finalBounds]
transformAnimation.keyTimes = [0, 0.5, 1]
transformAnimation.timingFunctions = [CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut), CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)]
transformAnimation.removedOnCompletion = false
transformAnimation.fillMode = kCAFillModeForwards
navigationController.view.layer.mask.addAnimation(transformAnimation, forKey: "maskAnimation")
// logo mask background view animation
UIView.animateWithDuration(0.1,
delay: 1.35,
options: UIViewAnimationOptions.CurveEaseIn,
animations: {
maskBgView.alpha = 0.0
},
completion: { finished in
maskBgView.removeFromSuperview()
})
// root view animation
UIView.animateWithDuration(0.25,
delay: 1.3,
options: UIViewAnimationOptions.TransitionNone,
animations: {
self.window!.rootViewController!.view.transform = CGAffineTransformMakeScale(1.05, 1.05)
},
completion: { finished in
UIView.animateWithDuration(0.3,
delay: 0.0,
options: UIViewAnimationOptions.CurveEaseInOut,
animations: {
self.window!.rootViewController!.view.transform = CGAffineTransformIdentity
},
completion: nil
)
})
And the problem is worse on iPad where the screen is bigger = more masking left on the screen.
You need to remove the mask after the animation is done.
UIView.animateWithDuration(0.25,
delay: 1.3,
options: UIViewAnimationOptions.TransitionNone,
animations: {
self.window!.rootViewController!.view.transform = CGAffineTransformMakeScale(1.05, 1.05)
},
completion: { finished in
UIView.animateWithDuration(0.3,
delay: 0.0,
options: UIViewAnimationOptions.CurveEaseInOut,
animations: {
self.window!.rootViewController!.view.transform = CGAffineTransformIdentity
},
completion: {
// ADDED THIS LINE
self.window!.rootViewController!.view.layer.mask = nil
}
)
})

Resources