Black edges on return transition - swift

I have implemented the following animated transition:
class PopInAndOutAnimator: NSObject, UIViewControllerAnimatedTransitioning {
fileprivate let _operationType : UINavigationControllerOperation
fileprivate let _transitionDuration : TimeInterval
init(operation: UINavigationControllerOperation) {
_operationType = operation
_transitionDuration = 0.4
}
init(operation: UINavigationControllerOperation, andDuration duration: TimeInterval) {
_operationType = operation
_transitionDuration = duration
}
//MARK: Push and Pop animations performers
internal func performPushTransition(_ transitionContext: UIViewControllerContextTransitioning) {
guard let toView = transitionContext.view(forKey: UITransitionContextViewKey.to) else {
// Something really bad happend and it is not possible to perform the transition
print("ERROR: Transition impossible to perform since either the destination view or the conteiner view are missing!")
return
}
let container = transitionContext.containerView
guard let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) as? CollectionPushAndPoppable,
let fromView = fromViewController.collectionView,
let currentCell = fromViewController.sourceCell else {
// There are not enough info to perform the animation but it is still possible
// to perform the transition presenting the destination view
container.addSubview(toView)
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
return
}
// Add to container the destination view
container.addSubview(toView)
// Prepare the screenshot of the destination view for animation
let screenshotToView = UIImageView(image: toView.screenshot)
// set the frame of the screenshot equals to the cell's one
screenshotToView.frame = currentCell.frame
// Now I get the coordinates of screenshotToView inside the container
let containerCoord = fromView.convert(screenshotToView.frame.origin, to: container)
// set a new origin for the screenshotToView to overlap it to the cell
screenshotToView.frame.origin = containerCoord
// Prepare the screenshot of the source view for animation
let screenshotFromView = UIImageView(image: currentCell.screenshot)
screenshotFromView.frame = screenshotToView.frame
// Add screenshots to transition container to set-up the animation
container.addSubview(screenshotToView)
container.addSubview(screenshotFromView)
// Set views initial states
toView.isHidden = true
screenshotToView.isHidden = true
// Delay to guarantee smooth effects
let delayTime = DispatchTime.now() + Double(Int64(0.08 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
DispatchQueue.main.asyncAfter(deadline: delayTime) {
screenshotToView.isHidden = false
}
UIView.animate(withDuration: _transitionDuration, delay: 0, usingSpringWithDamping: 0, initialSpringVelocity: 0, options: [], animations: { () -> Void in
screenshotFromView.alpha = 0.0
screenshotToView.frame = UIScreen.main.bounds
screenshotToView.frame.origin = CGPoint(x: 0.0, y: 0.0)
screenshotFromView.frame = screenshotToView.frame
}) { _ in
screenshotToView.removeFromSuperview()
screenshotFromView.removeFromSuperview()
toView.isHidden = false
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
internal func performPopTransition(_ transitionContext: UIViewControllerContextTransitioning) {
guard let toView = transitionContext.view(forKey: UITransitionContextViewKey.to) else {
// Something really bad happend and it is not possible to perform the transition
print("ERROR: Transition impossible to perform since either the destination view or the conteiner view are missing!")
return
}
let container = transitionContext.containerView
guard let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) as? CollectionPushAndPoppable,
let toCollectionView = toViewController.collectionView,
let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
let fromView = fromViewController.view,
let currentCell = toViewController.sourceCell else {
// There are not enough info to perform the animation but it is still possible
// to perform the transition presenting the destination view
container.addSubview(toView)
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
return
}
// Add destination view to the container view
container.addSubview(toView)
// Prepare the screenshot of the source view for animation
let screenshotFromView = UIImageView(image: fromView.screenshot)
screenshotFromView.frame = fromView.frame
// Prepare the screenshot of the destination view for animation
let screenshotToView = UIImageView(image: currentCell.screenshot)
screenshotToView.frame = screenshotFromView.frame
// Add screenshots to transition container to set-up the animation
container.addSubview(screenshotToView)
container.insertSubview(screenshotFromView, belowSubview: screenshotToView)
// Set views initial states
screenshotToView.alpha = 0.0
fromView.isHidden = true
currentCell.isHidden = true
let containerCoord = toCollectionView.convert(currentCell.frame.origin, to: container)
UIView.animate(withDuration: _transitionDuration, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0, options: [], animations: { () -> Void in
screenshotToView.alpha = 1.0
screenshotFromView.frame = currentCell.frame
screenshotFromView.frame.origin = containerCoord
screenshotToView.frame = screenshotFromView.frame
}) { _ in
currentCell.isHidden = false
screenshotFromView.removeFromSuperview()
screenshotToView.removeFromSuperview()
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
//MARK: UIViewControllerAnimatedTransitioning
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return _transitionDuration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
if _operationType == .push {
performPushTransition(transitionContext)
} else if _operationType == .pop {
performPopTransition(transitionContext)
}
}
}
The issue that I'm having is when my collection view cell returns. There is a small delay where the bottom of the cell has a black line. I'm not sure how to remove that delay or make that line white so that it isn't noticeable.

A quick hack is to set the background view color to white.

Related

tabbar transition animation topbar color issue

I use this function to animate tabbar transition
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
guard let tabViewControllers = tabBarController.viewControllers, let toIndex = tabViewControllers.firstIndex(of: viewController) else {
return false
}
animateToTab(toIndex: toIndex)
return true
}
func animateToTab(toIndex: Int) {
guard let tabViewControllers = viewControllers,
let selectedVC = selectedViewController else { return }
guard let fromView = selectedVC.view,
let toView = tabViewControllers[toIndex].view,
let fromIndex = tabViewControllers.firstIndex(of: selectedVC),
fromIndex != toIndex else { return }
// Add the toView to the tab bar view
fromView.superview?.addSubview(toView)
// Position toView off screen (to the left/right of fromView)
let screenWidth = UIScreen.main.bounds.size.width
let scrollRight = toIndex > fromIndex
let offset = (scrollRight ? screenWidth : -screenWidth)
toView.center = CGPoint(x: fromView.center.x + offset, y: toView.center.y)
// Disable interaction during animation
view.isUserInteractionEnabled = false
UIView.animate(withDuration: 0.3,
delay: 0.0,
usingSpringWithDamping: 1,
initialSpringVelocity: 0,
options: .curveEaseOut,
animations: {
// Slide the views by -offset
fromView.center = CGPoint(x: fromView.center.x - offset, y: fromView.center.y)
toView.center = CGPoint(x: toView.center.x - offset, y: toView.center.y)
}, completion: { finished in
// Remove the old view from the tabbar view.
fromView.removeFromSuperview()
self.selectedIndex = toIndex
self.view.isUserInteractionEnabled = true
})
}
When the transition is happening, the topbar color of the controller changes and it causes an annoying flash effect.
check the video, please
https://drive.google.com/file/d/15mlR9NfV_1C8gwi-4vfpV4CxpXbG5BUm/view
How can I prevent this?
I fixed it by disabling translucent navigation bar

Navigation view transition full screen to a view with corner radius

I am trying to create an app home screen animation from splash, like after launch screen completed (full)screen color transforms into an app logo background color. Currently below code kind of archive what I expected. But, that transformation CAShapeLayer doesn't do with corner radius. Without corner radius it works as normal, when I try to use circle/oval/corner radius animation seems like below gif.
Tried few other StackOverflow answers which create circle animation those are not working. Here one of those.
weak var viewTransitionContext: UIViewControllerContextTransitioning!
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
viewTransitionContext = transitionContext
guard let fromVC = viewTransitionContext.viewController(forKey: .from) else { return }
guard let toVC = viewTransitionContext.viewController(forKey: .to) else { return }
if fromVC.isKind(of: SOGSplashViewController.self) && toVC.isKind(of: SOGHomeViewController.self) {
guard let toVCView = transitionContext.view(forKey: .to) else { return }
guard let fromVCView = transitionContext.view(forKey: .from) else { return }
let containerView = transitionContext.containerView
let labelWidth = UIDevice.width() * 0.75
let labelHeight = labelWidth * 0.7
let xAxis = (UIDevice.width() - labelWidth)/2.0
let yAxis = ((UIDevice.height()/2.0) - labelHeight)/2.0
let labelRect = CGRect(x: xAxis, y: yAxis, width: labelWidth, height: labelHeight)
let radius = (UIDevice.height()/2.0)*0.1
let fromFrame = fromVCView.bounds
let animationTime = transitionDuration(using: transitionContext)
let maskLayer = CAShapeLayer()
maskLayer.isOpaque = false
maskLayer.fillColor = fromVCView.backgroundColor?.cgColor
maskLayer.backgroundColor = UIColor.clear.cgColor
maskLayer.path = toPathValue.cgPath
let maskAnimationLayer = CABasicAnimation(keyPath: "path")
maskAnimationLayer.fromValue = (UIBezierPath(rect: fromFrame)).cgPath
maskAnimationLayer.toValue = toPathValue.cgPath
maskAnimationLayer.duration = animationTime
maskAnimationLayer.delegate = self as? CAAnimationDelegate
containerView.addSubview(fromVCView)
containerView.addSubview(toVCView)
fromVCView.layer.add(maskAnimationLayer, forKey: nil)
maskLayer.add(maskAnimationLayer, forKey: "path")
containerView.layer.addSublayer(maskLayer)
let deadLineTime = DispatchTime.now() + .seconds(1)
DispatchQueue.main.asyncAfter(deadline: deadLineTime) {
UIView.animate(withDuration: 0.2, animations: {
maskLayer.opacity = 0
}, completion: { (isSuccess) in
self.viewTransitionContext.completeTransition(true)
})
}
}
}
Transforming a rectangular path to a rounded rectangular path is a very complex operation if you do it through a generic way like Core Animation.. You should better use the cornerRadius property of CALayer which is animatable.
Here is a working example with a constraint based animation:
class ViewController: UIViewController {
#IBOutlet var constraints: [NSLayoutConstraint]!
#IBOutlet var contentView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
self.contentView.layer.cornerRadius = 10.0
self.animate(nil)
}
#IBAction func animate(_ sender: Any?) {
for c in constraints {
c.constant = 40.0
}
UIView.animate(withDuration: 4.0) {
self.view.layoutIfNeeded()
self.contentView.layer.cornerRadius = 40.0
}
}
}
contentView points to the inner view which should be animated, and constraints refer to the four layout constraints defining the distances from the view controller's view to the content view.
This is just a simple, rough example, which can certainly be improved.

Using UIPercentDrivenInteractiveTransition with CABasicAnimation has weird glitch

I'm implementing custom transition using CABasicAnimation and UIView.animate both. Also need to implement a custom interactive transition using UIPercentDrivenInteractiveTransition which exactly copies the behavior of the native iOS swipe back. Animation without a back swipe gesture (when I'm pushing and popping by the back arrow) works fine and smoothly. Moreover, swipe back also works smoothly, except when the gesture velocity is more than 900
Gesture Recognition function:
#objc func handleBackGesture(_ gesture: UIScreenEdgePanGestureRecognizer) {
guard animationTransition != nil else { return }
switch gesture.state {
case .began:
interactionController = TransparentNavigationControllerTransitionInteractor(duration: anumationDuration)
popViewController(animated: true)
case .changed:
guard let view = gesture.view?.superview else { return }
let translation = gesture.translation(in: view)
var percentage = translation.x / view.bounds.size.width
percentage = min(1.0, max(0.0, percentage))
shouldCompleteTransition = percentage > 0.5
interactionController?.update(percentage)
case .cancelled, .failed, .possible:
if let interactionController = self.interactionController {
isInteractiveStarted = false
interactionController.cancel()
}
case .ended:
interactionController?.completionSpeed = 0.999
let greaterThanMaxVelocity = gesture.velocity(in: view).x > 800
let canFinish = shouldCompleteTransition || greaterThanMaxVelocity
canFinish ? interactionController?.finish() : interactionController?.cancel()
interactionController = nil
#unknown default: assertionFailure()
}
}
UIPercentDrivenInteractiveTransition class. Here I'm synchronizing layer animation.
final class TransparentNavigationControllerTransitionInteractor: UIPercentDrivenInteractiveTransition {
// MARK: - Private Properties
private var context: UIViewControllerContextTransitioning?
private var pausedTime: CFTimeInterval = 0
private let animationDuration: TimeInterval
// MARK: - Initialization
init(duration: TimeInterval) {
self.animationDuration = duration * 0.4 // I dk why but layer duration should be less
super.init()
}
// MARK: - Public Methods
override func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) {
super.startInteractiveTransition(transitionContext)
context = transitionContext
pausedTime = transitionContext.containerView.layer.convertTime(CACurrentMediaTime(), from: nil)
transitionContext.containerView.layer.speed = 0
transitionContext.containerView.layer.timeOffset = pausedTime
}
override func finish() {
restart(isFinishing: true)
super.finish()
}
override func cancel() {
restart(isFinishing: false)
super.cancel()
}
override func update(_ percentComplete: CGFloat) {
super.update(percentComplete)
guard let transitionContext = context else { return }
let progress = CGFloat(animationDuration) * percentComplete
transitionContext.containerView.layer.timeOffset = pausedTime + Double(progress)
}
// MARK: - Private Methods
private func restart(isFinishing: Bool) {
guard let transitionLayer = context?.containerView.layer else { return }
transitionLayer.beginTime = transitionLayer.convertTime(CACurrentMediaTime(), from: nil)
transitionLayer.speed = isFinishing ? 1 : -1
}
}
And here is my Dismissal animation function in UIViewControllerAnimatedTransitioning class
private func runDismissAnimationFrom(
_ fromView: UIView,
to toView: UIView,
in transitionContext: UIViewControllerContextTransitioning) {
guard let toViewController = transitionContext.viewController(forKey: .to) else { return }
toView.frame = toView.frame.offsetBy(dx: -fromView.frame.width / 3, dy: 0)
let toViewFinalFrame = transitionContext.finalFrame(for: toViewController)
let fromViewFinalFrame = fromView.frame.offsetBy(dx: fromView.frame.width, dy: 0)
// Create mask to hide bottom view with sliding
let slidingMask = CAShapeLayer()
let initialMaskPath = UIBezierPath(rect: CGRect(
x: fromView.frame.width / 3,
y: 0,
width: 0,
height: toView.frame.height)
)
let finalMaskPath = UIBezierPath(rect: toViewFinalFrame)
slidingMask.path = initialMaskPath.cgPath
toView.layer.mask = slidingMask
toView.alpha = 0
let slidingAnimation = CABasicAnimation(keyPath: "path")
slidingAnimation.fromValue = initialMaskPath.cgPath
slidingAnimation.toValue = finalMaskPath.cgPath
slidingAnimation.timingFunction = .init(name: .linear)
slidingMask.path = finalMaskPath.cgPath
slidingMask.add(slidingAnimation, forKey: slidingAnimation.keyPath)
UIView.animate(
withDuration: duration,
delay: 0,
options: animationOptions,
animations: {
fromView.frame = fromViewFinalFrame
toView.frame = toViewFinalFrame
toView.alpha = 1
},
completion: { _ in
toView.layer.mask = nil
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
I note that glitch occurs only when a swipe has a grand velocity.
Here a video with the result of smooth animation at normal speed and not smooth at high speed - https://youtu.be/1d-kTPlhNvE
UPD:
I've already tried to use UIViewPropertyAnimator combine with
interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating
But the result is another type of glitching.
I've solved the issue, just change a part of restart function:
transitionLayer.beginTime =
transitionLayer.convertTime(CACurrentMediaTime(), from: nil) - transitionLayer.timeOffset
transitionLayer.speed = 1
I don't really understand why, but looks like timeOffset subtraction works!

Swift interactive transition - view from second view controller is flashing when cancelling the transition

i´m trying to make my own interactive transition in swift. In my sample project I have two view controllers. With my own transition I can swipe to each other. My problem is: when swiping from the first to the second view controller the second (where I want to swipe in) view controller is appears for a millisecond on the screen, when I did cancel the transition in my pan gesture.
I tried to solve the problem by taking a snapshot from the first view controller and lay it on the transition layer when the transition cancelled. That worked on my iPhone 8, but on the iPhone XS I get the same flash for a millisecond.
The flashing only appears when I swipe very fast over the screen.
Anyone with the same problem.
PangestureHandler:
#IBAction func HandlePan(_ sender: UIPanGestureRecognizer) {
let percentThreshold:CGFloat = 0.25
let translation = sender.translation(in: view)
var verticalMovement = (translation.y) / view.bounds.height
verticalMovement = verticalMovement - verticalMovement - verticalMovement
let downwardMovement = fmaxf(Float(verticalMovement), 0.0)
let downwardMovementPercent = fminf(downwardMovement, 1.0)
let progress = CGFloat(downwardMovementPercent)
let movementToStart: CGFloat = 0.0
let interactor = self.interactor
switch sender.state {
case .began:
print(".began")
case .changed:
print("progress: \(progress)")
if progress > movementToStart && !interactor.hasStarted {
interactor.hasStarted = true
performSegue(withIdentifier: "segueToSecondVC", sender: nil)
interactor.update(progress - movementToStart)
}
if interactor.hasStarted {
interactor.update(progress - movementToStart)
interactor.shouldFinish = progress > percentThreshold
}
case .cancelled:
print("cancel")
interactor.hasStarted = false
interactor.cancel()
case .ended:
interactor.hasStarted = false
if interactor.shouldFinish {
interactor.finish()
} else {
interactor.cancel()
print("interactor cancel")
}
default:
break
}
}
AnimatorClass:
import UIKit
class PresentAnimator: NSObject, UIViewControllerAnimatedTransitioning {
private var myView: UIView!
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.6
}
func animationEnded(_ transitionCompleted: Bool) {
if !transitionCompleted {
self.myView.alpha = 1.0
}
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromVC = transitionContext.viewController(forKey: .from), let toVC = transitionContext.viewController(forKey: .to) else { return }
let containerView = transitionContext.containerView
let screenBounds = UIScreen.main.bounds
let startPoint = CGPoint(x: 0, y: UIScreen.main.bounds.height )
toVC.view.frame = CGRect(origin: startPoint, size: screenBounds.size)
toVC.view.clipsToBounds = true
self.myView = fromVC.view.snapshotView(afterScreenUpdates: false)
containerView.addSubview(myView)
self.myView.alpha = 0.0
containerView.insertSubview(toVC.view, aboveSubview: fromVC.view)
let bottomLeftCorner = CGPoint(x: 0, y: 0)
let finalFrame = CGRect(origin: bottomLeftCorner, size: screenBounds.size)
let duration = transitionDuration(using: transitionContext)
UIView.animate(
withDuration: duration, delay: 10.0, options: .curveLinear,
animations: {
toVC.view.frame = finalFrame
self.myView.removeFromSuperview()
},
completion: { _ in
self.myView.removeFromSuperview()
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
}

Smooth object rotation in ARSCNView

I'm trying to make my pan gesture to be as smooth as the jigspace app when rotating 3d objects in AR. Here's what I have right now:
#objc func rotateObject(sender: UIPanGestureRecognizer) {
let sceneView = sender.view as! ARSCNView
var currentAngleY: Float = 0.0
let translation = sender.translation(in: sceneView)
var newAngleY = Float(translation.x)*Float(Double.pi)/180
sceneView.scene.rootNode.enumerateChildNodes { (node, stop) in
if sender.state == .changed {
newAngleY -= currentAngleY
node.eulerAngles.y = newAngleY
} else if sender.state == .ended {
currentAngleY = newAngleY
node.removeAllActions()
}
}
}
There seems to be a delay when I'm using it and I'm trying to figure out how to make the rotation as smooth as possible, again, kinda like jigspace or the Ikea app.
I've also noticed that when I try to rotate the object when it's in a certain angle, it could get quite awkward.
Looking at your rotate object function it seems like some of the logic is not quite right.
Firstly, I believe that the var currentAngleY: Float = 0 should be outside of your function body.
Secondly you should be adding the currentAngleY to the newAngleY variable e.g:
/// Rotates The Models On Their YAxis
///
/// - Parameter gesture: UIPanGestureRecognizer
#objc func rotateModels(_ gesture: UIPanGestureRecognizer) {
let translation = gesture.translation(in: gesture.view!)
var newAngleY = (Float)(translation.x)*(Float)(Double.pi)/180.0
newAngleY += currentAngleY
DispatchQueue.main.async {
self.sceneView.scene.rootNode.enumerateChildNodes { (node, _) in
node.eulerAngles.y = newAngleY
}
}
if(gesture.state == .ended) { currentAngleY = newAngleY }
}
An example therefore of this in a working context would be like so:
class ViewController: UIViewController {
#IBOutlet var augmentedRealityView: ARSCNView!
var currentAngleY: Float = 0
//-----------------------
// MARK: - View LifeCycle
//-----------------------
override func viewDidLoad() {
super.viewDidLoad()
//1. Generate Our Three Box Nodes
generateBoxNodes()
//2. Create Our Rotation Gesture
let rotateGesture = UIPanGestureRecognizer(target: self, action: #selector(rotateModels(_:)))
self.view.addGestureRecognizer(rotateGesture)
//3. Run The Session
let configuration = ARWorldTrackingConfiguration()
augmentedRealityView.session.run(configuration)
}
//------------------------
// MARK: - Node Generation
//------------------------
/// Generates Three SCNNodes With An SCNBox Geometry
func generateBoxNodes(){
//1. Create An Array Of Colours For Each Face
let colours: [UIColor] = [.red, .green, .blue, .purple, .cyan, .black]
//2. Create An SCNNode Wih An SCNBox Geometry
let boxNode = SCNNode()
let boxGeometry = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0.01)
boxNode.geometry = boxGeometry
//3. Create A Different Material For Each Face
var materials = [SCNMaterial]()
for i in 0..<5{
let faceMaterial = SCNMaterial()
faceMaterial.diffuse.contents = colours[i]
materials.append(faceMaterial)
}
//4. Set The Geometries Materials
boxNode.geometry?.materials = materials
//5. Create Two More Nodes By Cloning The First One
let secondBox = boxNode.flattenedClone()
let thirdBox = boxNode.flattenedClone()
//6. Position Them In A Line & Add To The Scene
boxNode.position = SCNVector3(-0.2, 0, -1.5)
secondBox.position = SCNVector3(0, 0, -1.5)
thirdBox.position = SCNVector3(0.2, 0, -1.5)
self.augmentedRealityView.scene.rootNode.addChildNode(boxNode)
self.augmentedRealityView.scene.rootNode.addChildNode(secondBox)
self.augmentedRealityView.scene.rootNode.addChildNode(thirdBox)
}
//----------------------
// MARK: - Node Rotation
//----------------------
/// Rotates The Models On Their YAxis
///
/// - Parameter gesture: UIPanGestureRecognizer
#objc func rotateModels(_ gesture: UIPanGestureRecognizer) {
let translation = gesture.translation(in: gesture.view!)
var newAngleY = (Float)(translation.x)*(Float)(Double.pi)/180.0
newAngleY += currentAngleY
DispatchQueue.main.async {
self.augmentedRealityView.scene.rootNode.enumerateChildNodes { (node, _) in
node.eulerAngles.y = newAngleY
}
}
if(gesture.state == .ended) { currentAngleY = newAngleY }
}
}
Hope it helps...