UIView snapshots on custom UIViewController transitions - ios

I am creating a custom transition for my app but when I try to create a snapshot of the destination view it always appears as a blank white rectangle. It is important I note that this is a custom push transition and not a modal presentation of the view. When presenting modally the snapshot appears to work. Is this the normal behaviour for custom push/pop transitions?
The code I've written is as follows:
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let toViewController = transitionContext.viewController(forKey: .to) as? CultureViewController else {
return
}
let containerView = transitionContext.containerView
let finalFrame = transitionContext.finalFrame(for: toViewController)
let snapshot = toViewController.view.snapshotView(afterScreenUpdates: true)
snapshot?.frame = UIScreen.main.bounds
containerView.addSubview(snapshot!)
toViewController.view.transform = CGAffineTransform(translationX: 0, y: toViewController.view.bounds.height)
//containerView.addSubview(toViewController.view)
let duration = transitionDuration(using: transitionContext)
UIView.animateKeyframes(withDuration: duration, delay: 0, options: .calculationModeCubic, animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1, animations: {
toViewController.view.frame = finalFrame
})
}, completion: { _ in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}

Taking a snapshot of a view before it is rendered, such as is the case with a transition before you call containerView.addSubview(toViewController.view), returns a blank view, per UIView's documentation.
If the current view is not yet rendered, perhaps because it is not yet onscreen, the snapshot view has no visible content.

Related

Programmatically letting my transition manager know where im coming from or where im going? Swift

So i have this code in my animateTransition func:
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
// Getting the reference to container, toView & fromView
let container = transitionContext.containerView
let fromView = transitionContext.view(forKey: .from)! // Force unpacked
let toView = transitionContext.view(forKey: .to)! // Force unpacked
// Setup for the 2d animation
let offScreenRight = CGAffineTransform(translationX: container.frame.width, y: 0)
let offScreenLeft = CGAffineTransform(translationX: -container.frame.width, y: 0)
// Start the toView to the right of the screen
container.addSubview(toView)
container.addSubview(fromView)
// Get the duration of the animation
let duration = self.transitionDuration(using: transitionContext)
// Perform the animation
UIView.animate(withDuration: duration, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.8, animations: {
fromView.transform = offScreenLeft
toView.transform = .identity
}, completion: { finished in
// Tell our transitionContext object that we've finished animating
transitionContext.completeTransition(true)
})
}
And if im trying to present a viewcontroller with the help of the transitionManager, which is containing this func, it can't find the from view. If i'm trying to dismiss, it can't find the to view. How can I tell it where it comes from in the present, and how can i tell it where it has to go when dismissing?
Okay, so I found the solution. The reason i wasn't able to reach the fromView or the toView depending on dismiss or present was because i called:
let fromView = transitionContext.view(forKey: .from)!
let toView = transitionContext.view(forKey: .to)!
Instead i should call:
let fromView = transitionContext.viewController(forkey: .from)!.view!
let toView = transitionContext.viewController(forkey: .to)!.view!

UIViewController interactive transition - Presented view disappears when interactive dismiss is canceled

I have a demo app where I'm trying to mimic the Mail app "new message" interactive transition - you can drag down to dismiss, but if you don't drag far enough the view pops back up and the transition is cancelled. I was able to duplicate the transition and interactivity in my demo app, but I noticed that when the dismiss transition is cancelled, the presented view controller is animated back up into place, then vanishes. Here's what it looks like:
My best guess is that the transition context's container view is being removed for some reason, since I added the presented view controller's view to it. Here is the presentation and dismiss code inside the UIViewControllerAnimatedTransitioning objects:
Show Transition
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 finalFrame = transitionContext.finalFrame(for: toVC)
let duration = transitionDuration(using: transitionContext)
let topSafeAreaSpace = fromVC.view.safeAreaInsets.top // using fromVC safe area since it's on screen and has correct insets
let topGap: CGFloat = topSafeAreaSpace + 20
containerView.addSubview(toVC.view)
toVC.view.frame = CGRect(x: 0,
y: containerView.frame.height,
width: toVC.view.frame.width,
height: toVC.view.frame.height - 30)
UIView.animate(withDuration: duration, animations: {
toVC.view.frame = CGRect(x: finalFrame.minX,
y: finalFrame.minY + topGap,
width: finalFrame.width,
height: finalFrame.height - topGap)
let sideGap: CGFloat = 20
fromVC.view.frame = CGRect(x: sideGap,
y: topSafeAreaSpace,
width: fromVC.view.frame.width - 2 * sideGap,
height: fromVC.view.frame.height - 2 * topSafeAreaSpace)
fromVC.view.layer.cornerRadius = 10
fromVC.view.layoutIfNeeded()
}) { _ in
transitionContext.completeTransition(true)
}
}
Dismiss Transition
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromVC = transitionContext.viewController(forKey: .from),
let toVC = transitionContext.viewController(forKey: .to)
else {
return
}
let finalFrame = transitionContext.finalFrame(for: toVC)
let duration = transitionDuration(using: transitionContext)
UIView.animate(withDuration: duration, animations: {
toVC.view.frame = finalFrame
fromVC.view.frame = CGRect(x: fromVC.view.frame.minX,
y: finalFrame.height,
width: fromVC.view.frame.width,
height: fromVC.view.frame.height)
toVC.view.layer.cornerRadius = 0
toVC.view.layoutIfNeeded()
}) { _ in
transitionContext.completeTransition(true)
}
}
And here is the code in the UIPercentDrivenInteractiveTransition object:
func handleGesture(_ gesture: UIPanGestureRecognizer) {
#warning("need to use superview?")
let translation = gesture.translation(in: gesture.view)
var progress = translation.y / 400
progress = min(1, max(0, progress)) // constraining value between 1 and 0
switch gesture.state {
case .began:
interactionInProgress = true
viewController.dismiss(animated: true, completion: nil)
case .changed:
shouldCompleteTransition = progress > 0.5
update(progress)
case .cancelled:
interactionInProgress = false
cancel()
case .ended:
interactionInProgress = false
if shouldCompleteTransition {
finish()
} else {
cancel()
}
default:
break
}
}
Any help would be greatly appreciated. It's worth noting I used this Ray Wenderlich tutorial as a reference -
https://www.raywenderlich.com/322-custom-uiviewcontroller-transitions-getting-started
However where they used image snapshots to animate the transition I'm using the view controller's views.
Thanks to a comment by #Dare, I realized all that was needed was a small update to the dismiss animation completion block:
// before - broken
transitionContext.completeTransition(true)
// after - WORKING!
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)

Unable to present a Controller on Formsheet style Controller

I am working on Custom presentation style and having one issue. what i have using is https://github.com/ergunemr/BottomPopup library but unable to present in visible context!!
i already change the code in below method at BottomPopupPresentAnimator class
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let toVC = transitionContext.viewController(forKey: .to)! // sub
let fromVC = transitionContext.viewController(forKey: .from)! // plan
transitionContext.containerView.addSubview(toVC.view)
let presentFrame = transitionContext.finalFrame(for: fromVC)
let initialFrame = CGRect(origin: CGPoint(x: 0, y: fromVC.view.frame.height), size: presentFrame.size)
toVC.view.frame = initialFrame
UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
toVC.view.frame = presentFrame
}) { (_) in
transitionContext.completeTransition(true)
}
}
what i achieve is
i already tried with different type of combination with presentation style
can anyone suggest what is wrong here? i just want to present over middle controller (Popup Setting)

Swift - UIViewControllerAnimatedTransitioning not transform as expected with Swift4

I just upgraded to XCode9 with Swift4 today.
And I found that my UIViewControllerAnimatedTransitioning doesn't work as expected anymore.
The effect of this animation is that the fromView will scale down to 0.95 and the toView will slide in from the right side. The pop operation will do it reversely.
But now when I hit the back button of the NavigationBar, the start position of the toView isn't right. It displayed a original size toView and then scale up to 1.05.
Here's how I implement the transition animator.
// animate a change from one viewcontroller to another
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
// get reference to our fromView, toView and the container view that we should perform the transition in
let container = transitionContext.containerView
let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)!
let toView = transitionContext.view(forKey: UITransitionContextViewKey.to)!
// set up from 2D transforms that we'll use in the animation
let offScreenRight = CGAffineTransform(translationX: container.frame.width, y: 0)
let offScreenDepth = CGAffineTransform(scaleX: 0.95, y: 0.95)
// start the toView to the right of the screen
if( presenting ){
toView.transform = offScreenRight
container.addSubview(fromView)
container.addSubview(toView)
}
else{
toView.transform = offScreenDepth
container.addSubview(toView)
container.addSubview(fromView)
}
// get the duration of the animation
// DON'T just type '0.5s' -- the reason why won't make sense until the next post
// but for now it's important to just follow this approach
let duration = self.transitionDuration(using: transitionContext)
// perform the animation!
// for this example, just slid both fromView and toView to the left at the same time
// meaning fromView is pushed off the screen and toView slides into view
// we also use the block animation usingSpringWithDamping for a little bounce
UIView.animate(withDuration: duration, delay: 0.0, options: .curveEaseOut, animations: {
if( self.presenting ){
fromView.transform = offScreenDepth
}
else{
fromView.transform = offScreenRight
}
toView.transform = .identity
}, completion: { finished in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
I didn't found anything special in this migration guide page.
https://swift.org/migration-guide-swift4/
What should I do to make the transition works again?
Before setting anything else, try to reset transformations of the fromView to identity:
// animate a change from one viewcontroller to another
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
...
UIView.animate(withDuration: duration, delay: 0.0, options: .curveEaseOut, animations: {
if( self.presenting ){
fromView.transform = offScreenDepth
}
else{
fromView.transform = offScreenRight
}
toView.transform = .identity
}, completion: { finished in
fromView.transform = .identity
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
....
}

Keyboard view gets messed up with Modally presented view using UIViewControllerTransitioningDelegate

I'm presenting a view from another using a UIViewControllerTransitioningDelegate instance as transitioning delegate and modalPresentationStyle = Custom.
I'm using a TableViewController using static table view cells with UITextfields inside. Now when tapping over a text field whose borders are close to the the keyboard's frame, part of the tableView beneath the keyboard shows up. I also removed a UITapGestureRecognizer that I added to the semi-transparent background to make sure it's not part of the problem but the issue it's still there. Any ideas? Below is the animateTransition() method
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
dimmingView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
let containerView = transitionContext.containerView()
if let presentedViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) {
let presentedView = presentedViewController.view
let fromView = transitionContext.viewForKey(UITransitionContextFromViewControllerKey)
let centre = presentedView.center
if isPresenting {
presentedView.center = containerView.center
presentedView.frame = presentedView.bounds.rectByInsetting(dx: 30, dy: 150)
transitionContext.containerView().addSubview(presentedView)
dimmingView.frame = containerView.bounds
dimmingView.alpha = 0.0
containerView.insertSubview(dimmingView, atIndex: 0)
presentedViewController.transitionCoordinator()?.animateAlongsideTransition({
context in
self.dimmingView.alpha = 1.0
}, completion: nil)
}
else {
presentedViewController.transitionCoordinator()?.animateAlongsideTransition({
context in
self.dimmingView.alpha = 0.0
}, completion: {
context in
self.dimmingView.removeFromSuperview()
})
}
UIView.animateWithDuration(self.transitionDuration(transitionContext),
delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: 10.0, options: nil,
animations: {
presentedView.center = centre
}, completion: {
_ in
transitionContext.completeTransition(true)
})
}
}

Resources