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)
Related
I am trying to achieve 3. Material design - container transform effect with custom transitioning in iOS. Below is the code for the presentation part.
class CustomTransition: NSObject {
var duration: TimeInterval = 5
}
extension CustomTransition:UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let toViewController = transitionContext.viewController(forKey: .to),
let fromViewController = transitionContext.viewController(forKey: .from) as? ViewController else {
return
}
let finalFrame = transitionContext.finalFrame(for: toViewController)
toViewController.view.frame = fromViewController.menuButton.frame
toViewController.view.layer.cornerRadius = fromViewController.menuButton.frame.size.width / 2
toViewController.view.clipsToBounds = true
toViewController.view.alpha = 0
let archive = NSKeyedArchiver.archivedData(withRootObject: fromViewController.menuButton!)
let menuButtonCopy = NSKeyedUnarchiver.unarchiveObject(with: archive) as! UIButton
menuButtonCopy.layer.cornerRadius = menuButtonCopy.frame.size.width / 2
transitionContext.containerView.addSubview(menuButtonCopy)
transitionContext.containerView.addSubview(toViewController.view)
UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
toViewController.view.frame = finalFrame
toViewController.view.alpha = 1
menuButtonCopy.alpha = 0
menuButtonCopy.frame = finalFrame
}, completion: { _ in
transitionContext.completeTransition(true)
menuButtonCopy.removeFromSuperview()
})
}
}
Here is the result
Actually, I want to align the '+' button in the centre of the container as long as it animates to the full screen as seen in the design. What is the I am missing here? Why is the '+' seen arising from bottom centre?
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!
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)
I am trying to make a simple animated transitioning when pushing a UIViewController, but it seems I am missing something.
I animate a snapshot of a subview from the fromViewController to the toViewController. I am animating snapshot’s frame, but the snapshot is invisible for the whole duration of the animation.
Here is a simple code example. I am trying to animate a single UILabel from the first controller to the second. I specifically want to animate a snapshot taken from the toViewConroller and not from the fromViewController.
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let fromVC = transitionContext.viewController(forKey: .from) as! ViewController
let toVC = transitionContext.viewController(forKey: .to) as! SecondViewController
let container = transitionContext.containerView
toVC.view.frame = fromVC.view.frame
container.addSubview(toVC.view)
toVC.view.layoutIfNeeded()
let animatedFromView = fromVC.label!
let animatedToView = toVC.label!
let initialFrame = container.convert(animatedFromView.frame,
from: animatedFromView.superview)
let finalFame = container.convert(animatedToView.frame,
to: animatedToView.superview)
let snapshot = animatedToView.snapshotView(afterScreenUpdates: true)!
snapshot.frame = initialFrame
container.addSubview(snapshot)
animatedFromView.alpha = 0
animatedToView.alpha = 0
UIView.animate(withDuration: 2,
animations: {
snapshot.frame = finalFame
}) { (_) in
snapshot.removeFromSuperview()
fromVC.label.alpha = 1
toVC.label.alpha = 1
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
I guess that the snapshot is hidden, because setting the animatedToView’s alpha to 0, however I am not sure how to achieve that animation without setting that.
I tried your code its working fine.I changed a few things like initial frame hardcoded it so i can see the effect and also from viewController alpha.
::::::for presenting view controller
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let fromVC = transitionContext.viewController(forKey: .from) as! ViewController
let toVC = transitionContext.viewController(forKey: .to) as! SecondViewController
let container = transitionContext.containerView
toVC.view.frame = fromVC.view.frame
container.addSubview(toVC.view)
toVC.view.layoutIfNeeded()
let animatedFromView = fromVC.view!
let animatedToView = toVC.view!
let initialFrame = container.convert(CGRect(x: 0, y: 200, width: 100, height: 100),
from: animatedFromView.superview)
let finalFame = container.convert(animatedToView.frame,
to: animatedToView.superview)
let snapshot = animatedToView.snapshotView(afterScreenUpdates: true)!
snapshot.frame = initialFrame
container.addSubview(snapshot)
animatedFromView.alpha = 1
animatedToView.alpha = 0
UIView.animate(withDuration: 2,
animations: {
snapshot.frame = finalFame
}) { (_) in
snapshot.removeFromSuperview()
fromVC.view.alpha = 1
toVC.view.alpha = 1
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
:::::::::::::::::
Use while you are pushing view controller
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from) else { return }
guard let toView = transitionContext.view(forKey: UITransitionContextViewKey.to) else { return }
let container = transitionContext.containerView
container.insertSubview(toView, belowSubview: fromView)
let animatedFromView = fromView
let animatedToView = toView
let initialFrame = container.convert(CGRect(x: 0, y: 200, width: 100, height: 100),
from: animatedFromView.superview)
let finalFame = container.convert(animatedToView.frame,
to: animatedToView.superview)
let snapshot = animatedToView.snapshotView(afterScreenUpdates: true)!
snapshot.frame = initialFrame
container.addSubview(snapshot)
animatedFromView.alpha = 1
animatedToView.alpha = 1
UIView.animate(withDuration: 2,
animations: {
snapshot.frame = finalFame
}) { (_) in
snapshot.removeFromSuperview()
//fromVC.view.alpha = 1
//toVC.view.alpha = 1
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
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.