I have a view controller (VCA) that modally presents another view controller VCB:
presentViewController(VCB, animated: true, completion: nil)
If VCB has a modalPresentationStyle = .OverFullScreen (same applies for .OverCurrentContext), why when it is dismissed:
dismissViewControllerAnimated(true, completion: nil)
does the following code when the transition is finished result in VCA turning black?
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewContrllerKey)!
let fromVc = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
var containerView = transitionContext.containerView()!
if presenting {
toVC.view.alpha = 0
containerView.insertSubview(toVC.view, aboveSubview: fromVC.view)
} else {
containerView.insertSubview(toVC.view, belowSubview: fromVC.view)
}
UIView.animateWithDuration(transitionDuration(transitionContext), delay: 0.0, options: [.CurveEaseInOut], animations: {
if self.presenting {
self.toVC.view.alpha = 1
} else {
self.fromVC.view.alpha = 0
}
} , completion: { finished in
self.transitionContext.completeTransition(true)
})
}
Is there a way to fix this without removing the line containerView.insertSubview(toVC.view, belowSubview: fromVC.view) or checking the value of fromVC.modalPresentationStyle before executing the above line? i.e. is there some parameter I can set in VCA's or VCB's initialiser?
Many thanks for any help.
Related
When you present (popover) a view controller the previous view controller kind of backs out of the screen. Is there any way to stop that from happenening.
Here is an example of what is happening: https://imgur.com/a/DSKC6vF
And here is an example of what I want to happen: https://imgur.com/a/vyjA1Jv
The only code I'm using for presenting it
let viewController = UIStoryboard(name: "StoryboardName", bundle: nil).instantiateViewController(identifier: "IdentifierName")
self.present(viewController, animated: true, completion: nil)
If you want something like this:
First ViewController:
let sb = UIStoryboard.init(name: "Appointment", bundle: nil)
let popVC = sb.instantiateViewController(withIdentifier: "AppointConfirmationPopUpVC") as! AppointConfirmationPopUpVC
self.addChild(popVC)
popVC.view.frame = self.view.frame
self.view.addSubview(popVC.view)
popVC.didMove(toParent: self)
Second ViewController:
#IBOutlet weak var bottomView: UIView!
viewdidLoad()
{
self.view.backgroundColor = UIColor.black.withAlphaComponent(0.6)
showAnimate()
}
func showAnimate(){
UIView.animate(withDuration: 0.2, delay: 0, options: [.curveEaseIn],
animations: {
self.bottomView.center.y -= self.bottomView.bounds.height
self.bottomView.layoutIfNeeded()
}, completion: nil)
}
func hideAnimate()
{
self.tabBarController?.tabBar.isHidden = false
UIView.animate(withDuration: 0.2, delay: 0, options: [.curveLinear],
animations: {
self.bottomView.center.y += self.bottomView.bounds.height
self.bottomView.layoutIfNeeded()
self.view.center.y += self.view.bounds.height
self.view.layoutIfNeeded()
}, completion: {(_ completed: Bool) -> Void in
})
}
I'm trying to present a view controller with custom animation, so I came up with this code. When I tried to present the view controller, I got no error but the app crashes. Can someone tell me why my app crashes?
My code looks like this
func presentLeftToRight<T>(viewName: String, class: T.Type) where T: DefaultView {
let presentView = self.storyboard?.instantiateViewController(withIdentifier: viewName) as! T
presentView.loadViewIfNeeded()
presentView.topContainer.alpha = 0
presentView.bgMenuView.alpha = 0
presentView.mainButton.alpha = 0
self.view.insertSubview(presentView.view, belowSubview: scrollView)
self.view.layoutIfNeeded()
UIView.animate(withDuration: 0.5, animations: {
self.scrollView.frame.origin.x = self.view.frame.width
}, completion: { _ in
self.present(presentView, animated: false, completion: nil)
})
}
I have a tableViewController in a containerView of another viewController.
When an image is tapped, a custom transition occurs to another viewController to view the image. The transition works well.
When the custom transition takes place though viewWillDisappear is called on the containerView's tableViewController and then viewWillAppear again when the presented viewController is dismissed (again with a transition).
This is causing some unusual scrolling occasionally in the tableView.
What I would like is to present the imageViewer with a transition but overCurrentContext.
I have tried what people suggest which is the following:
self.definesPresentationContext = true
vc.modalPresentationStyle = .overCurrentContext
vc.transitioningDelegate = self
self.present(vc, animated: true, completion: nil)
Unfortunately this is not working and when the imageViewController is dismissed, initially the transition back is correct and the tableViewController can be seen. But once the transition is complete the view goes black.
I think this may have to do with the fact that I'm using a containerView. But not sure and not sure how to resolve this.
This is the animator method I'm using:
private var image: UIImage?
private var fromDelegate: ImageTransitionProtocol?
private var toDelegate: ImageTransitionProtocol?
// MARK: Setup Methods
func setupImageTransition(image: UIImage, fromDelegate: ImageTransitionProtocol, toDelegate: ImageTransitionProtocol) {
self.image = image
self.fromDelegate = fromDelegate
self.toDelegate = toDelegate
}
// MARK: UIViewControllerAnimatedTransitioning
// 1: Set animation speed
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.4
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
// 2: Get view controllers involved
let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
// 3: Set the destination view controllers frame
toVC.view.frame = fromVC.view.frame
// 4: Create transition image view
let imageView = UIImageView(image: image)
imageView.contentMode = .scaleAspectFill
imageView.frame = (fromDelegate == nil) ? CGRect() : fromDelegate!.imageWindowFrame()
imageView.clipsToBounds = true
containerView.addSubview(imageView)
// 5: Create from screen snapshot
fromDelegate!.transitionSetup()
toDelegate!.transitionSetup()
let fromSnapshot = fromVC.view.snapshotView(afterScreenUpdates: true)!
fromSnapshot.frame = fromVC.view.frame
containerView.addSubview(fromSnapshot)
// 6: Create to screen snapshot
let toSnapshot = toVC.view.snapshotView(afterScreenUpdates: true)!
toSnapshot.frame = fromVC.view.frame
containerView.addSubview(toSnapshot)
toSnapshot.alpha = 0
// 7: Bring the image view to the front and get the final frame
containerView.bringSubview(toFront: imageView)
let toFrame = (self.toDelegate == nil) ? CGRect() : self.toDelegate!.imageWindowFrame()
// 8: Animate change
UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.8, options: .curveEaseOut, animations: {
toSnapshot.alpha = 1
imageView.frame = toFrame
}, completion:{ [weak self] (finished) in
self?.toDelegate!.transitionCleanup()
self?.fromDelegate!.transitionCleanup()
// 9: Remove transition views
imageView.removeFromSuperview()
fromSnapshot.removeFromSuperview()
toSnapshot.removeFromSuperview()
// 10: Complete transition
if !transitionContext.transitionWasCancelled {
containerView.addSubview(toVC.view)
}
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
And this is in the presenting viewController:
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let imagePresentationViewController = (presented as! UINavigationController).topViewController as! ImagePresentationViewController
self.transition.setupImageTransition( image: pickedImageForTransition!, imageType: nil,
fromDelegate: self,
toDelegate: imagePresentationViewController)
return transition
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let imagePresentationViewController = (dismissed as! UINavigationController).topViewController as! ImagePresentationViewController
transition.setupImageTransition( image: pickedImageForTransition!, imageType: .listsImage,
fromDelegate: imagePresentationViewController,
toDelegate: self)
return transition
}
Had the same problem, you just need to remove container view from func animateTransition(using transitionContext: UIViewControllerContextTransitioning)
I have run into an issue when using a custom segue. I have two tableviews that I'm an trying to switch back and forth from. When I click on a cell in tableview1 it should take me to tableview2. I have a button on tableview2 that connects to the exit of the storyboard. From there it should take me back to tableview1 but whenever I press the button, the application crashes with a BAD_ACCESS error.
Here is my custom segue class:
class TableViewSegue: UIStoryboardSegue {
override func perform() {
scale()
}
func scale () {
let toViewcontroller = self.destination
let fromViewcontroller = self.source
let containerView = fromViewcontroller.view.superview
let originalCenter = fromViewcontroller.view.center
toViewcontroller.view.transform = CGAffineTransform(scaleX: 0.05, y: 0.05)
toViewcontroller.view.center = originalCenter
containerView?.addSubview(toViewcontroller.view)
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseInOut, animations: {
toViewcontroller.view.transform = CGAffineTransform.identity
}, completion: { success in
fromViewcontroller.present(toViewcontroller, animated: false, completion: nil) //The application crashes and highlights this line as the error.
})
}
}
I have implemented this method in my tableViewController1:
#IBAction func prepareForUnwind(segue: UIStoryboardSegue) {
}
Not sure why the tableview2 does not dismiss.
EDIT: The issue had to do with needing a navigation controller.
The problem is that you are presenting the toViewcontroller each time a segue is performed. So the app presents table2 over table1, and then tries again to present table1 over table2 on the unwind.
Modify your custom segue to check - essentially - which direction you're going:
class TableViewSegue: UIStoryboardSegue {
override func perform() {
scale()
}
func scale () {
let toViewcontroller = self.destination
let fromViewcontroller = self.source
let containerView = fromViewcontroller.view.superview
let originalCenter = fromViewcontroller.view.center
toViewcontroller.view.transform = CGAffineTransform(scaleX: 0.05, y: 0.05)
toViewcontroller.view.center = originalCenter
containerView?.addSubview(toViewcontroller.view)
let fromP = fromViewcontroller.presentingViewController
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseInOut, animations: {
toViewcontroller.view.transform = CGAffineTransform.identity
}, completion: { success in
// if nil, we are presenting a new VC
if fromP == nil {
fromViewcontroller.present(toViewcontroller, animated: false, completion: nil)
} else {
fromViewcontroller.dismiss(animated: false, completion: nil)
}
})
}
}
Note: This is assuming:
you are not trying to push/pop within a UINavigationController ... you'd need to add some other checks to handle that.
you are only going one-level-in, that is, you are not presenting, presenting, presenting, etc. and then trying to unwind.
So I've been following a couple tutorials on how to perform a custom animation when presenting a view controller. I currently have the following,
A class called TransitionManager which will be instantiated in ViewController A that will present the view
ViewController A that will present ViewController B in a navigation controller
According to all the tutorials i've read after setting the delegate in the presentation of the view, I should see my custom transition. However, the default animation is still used instead. I've tried moving the setting of the delegate before and after presenting with no avail
Transition Manager Class
class TransitionManager: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate {
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: .from)!
let toView = transitionContext.view(forKey: .to)!
// set up from 2D transforms that we'll use in the 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
if self.presenting {
toView.transform = offScreenRight
} else {
toView.transform = offScreenLeft
}
// add the both views to our view controller
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, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.8, options: [], animations: {
if self.presenting {
toView.transform = offScreenLeft
} else {
toView.transform = offScreenRight
}
toView.transform = .identity
}, completion: { finished in
// tell our transitionContext object that we've finished animating
transitionContext.completeTransition(true)
})
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.5
}
// MARK: UIViewControllerTransitioningDelegate protocol methods
// return the animataor when presenting a viewcontroller
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
self.presenting = true
return self
}
// return the animator used when dismissing from a viewcontroller
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
self.presenting = false
return self
}
}
Class ViewController A
class ViewControllerA: UIViewController {
let transitionManager = TransitionManager()
func addButtonSelected() {
let vc = ViewControllerB()
let nav = UINavigationController(rootViewController: vc)
present(nav, animated: true, completion: nil)
nav.transitioningDelegate = self.transitionManager
}
}
Update your methods to swift3-:
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return Transition()
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return Transition()
}
You need to set the modalPresentationStyle to .custom, mentioned in the docs here and here. I'm not sure if it matters, but I've also always set the transitioning delegate before calling present:
nav.modalPresentationStyle = .custom
nav.transitioningDelegate = self.transitionManager
present(nav, animated: true, completion: nil)