Can somebody explain why my fromViewController is nil? I am trying to understand custom transition animations in iOS going back and forth between two different view controllers.
Removing the guard part of my code and modifying it a little bit, my present animation works, but I cannot figure out how to implement the dismiss animation. Any suggestions?
func animateTransition(using transitionContext: UIViewControllerContextTransitioning)
{
let containerView = transitionContext.containerView
guard
let fromViewController = transitionContext.view(forKey: .from),
let toViewController = transitionContext.view(forKey: .to)
else
{
print("Nope")
return
}
containerView.addSubview(toViewController)
if (presentationMode == .Present)
{
toViewController.alpha = 0.0
UIView.animate(withDuration: 1.0,
animations: {
toViewController.alpha = 1.0
},
completion: {
_ in transitionContext.completeTransition(true)
})
}
Edit: This is where I use the animation
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
let secondVC = segue.destination as! SignUpViewController
secondVC.transitioningDelegate = self
}
extension LoginViewController: UIViewControllerTransitioningDelegate
{
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning?
{
print("Presenting")
presentationTransition.presentationMode = .Present
return presentationTransition
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?
{
print("Dismissing")
presentationTransition.presentationMode = .Dismiss
return presentationTransition
}
}`
Related
I tried making transition animation for changing between two viewController. Is there any way to make that without using storyboard?
This code for calling new viewController (which I didn't use storyboard):
#objc func handleAccount() {
let navController = UINavigationController(rootViewController: userButtonLauncher())
navController.transitioningDelegate = self
navController.modalPresentationStyle = .custom
self.present(navController, animated: true, completion: nil)
}
And this codes for transition:
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.transitionMode = .present
transition.startingPoint = CGPoint(x: 0, y: 0)
return transition
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.transitionMode = .pop
transition.startingPoint = CGPoint(x: 0, y: 0)
return transition
}
Of course you can! It depends on what you would like to do, so if you need to use the defaults animation on code without using storyboard you can just:
instantiate your ViewController, set the animation you want and open the ViewController
let vc = self.storyboard?.instantiateViewController(withIdentifier: "vc_id")
vc.modalTransitionStyle = .flipHorizontal
self.present(vc, animated: true, completion: nil)
Otherwise you can create your custom transition, see this easy guide:
https://www.raywenderlich.com/322-custom-uiviewcontroller-transitions-getting-started
I solve my problem. I just add that code for calling animation transition in my homecontroller:
extension HomeController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return faceanim()
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return faceanim()
}
}
And to button action is:
#objc func handleAccount() {
let userConVC = UINavigationController(rootViewController: userButtonLauncher())
userConVC.transitioningDelegate = self
navigationController?.isNavigationBarHidden = false
self.present(userConVC, animated: true, completion: nil)
}
I'm relatively new in programming and currently try to build an app with three (if possible more) view controllers. I did a tutorial (https://www.youtube.com/watch?v=B9sH_VxPPo4&t=505s) in which I learned to animate between two view controllers after pressing a custom button. This all worked perfectly.
But now I try to implement another view controller (ThirdViewController) and a second button (showThirdVCButton).
The transitioning works perfectly for both buttons but the animation is set back to the standard animation.
This is my code in the initial view controller:
import UIKit
class ViewController: UIViewController, UIViewControllerTransitioningDelegate {
#IBOutlet weak var showSecondVCButton: UIButton!
#IBOutlet weak var showThirdVCButton: UIButton!
let transition = CircularTransition()
override func viewDidLoad() {
super.viewDidLoad()
//I customise my buttons here
showSecondVCButton.layer.cornerRadius = showSecondVCButton.frame.size.width / 2
showThirdVCButton.layer.cornerRadius = showSecondVCButton.frame.size.width / 2
}
//The destination for each button is declared here and the animation style
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "secondVCSegue" {
let secondVC = segue.destination as! SecondViewController
secondVC.transitioningDelegate = self
secondVC.modalPresentationStyle = .custom
}
if segue.identifier == "thirdVCSegue" {
let thirdVC = segue.destination as! ThirdViewController
thirdVC.transitioningDelegate = self
thirdVC.modalPresentationStyle = .custom
}
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.transitionMode = .present
if showSecondVCButton.isTouchInside == true {
transition.startingPoint = showSecondVCButton.center
transition.circleColor = showSecondVCButton.backgroundColor!
}
if showThirdVCButton.isTouchInside == true {
transition.startingPoint = showThirdVCButton.center
transition.circleColor = showThirdVCButton.backgroundColor!
}
return transition
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.transitionMode = .dismiss
if showSecondVCButton.isTouchInside == true {
transition.startingPoint = showSecondVCButton.center
transition.circleColor = showSecondVCButton.backgroundColor!
}
if showThirdVCButton.isTouchInside == true {
transition.startingPoint = showThirdVCButton.center
transition.circleColor = showThirdVCButton.backgroundColor!
}
return transition
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Thank you for your help!!
huberdo - I followed the same tutorial you were looking at, and I was able to replicate the error you're getting, when I copied the second button rather than creating one from scratch. Have a look and check that you only have one segue for each button.
I think you can simplify the code. I would always prefer to set up an action for the buttons, so that you can see in your code where things are being called - what you have just now is a combination of Storyboard setup and explicit code.
If you use actions for the buttons, and set a flag to keep track of which button is pressed, it all becomes a lot simpler
var transition = CircularTransition()
var startingView = UIView() // this will define the starting point for all transitions
//
//
// explicit actions for the buttons
#IBAction func cmdOneAction(_ sender: Any)
{
startingView = sender as! UIView
self.performSegue(withIdentifier: "segueShow1", sender: self)
}
#IBAction func cmdTwoAction(_ sender: Any)
{
startingView = sender as! UIView
self.performSegue(withIdentifier: "segueShow2", sender: self)
}
Simplified animation functions
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.transitionMode = .present
transition.startingPoint = startingView.center
transition.circleColor = startingView.backgroundColor!
return transition
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.transitionMode = .dismiss
transition.startingPoint = startingView.center
transition.circleColor = startingView.backgroundColor!
return transition
}
I have followed this tutorial and watched the WWDC video on this subject but I couldn't find my answer.
I have almost the same transition in my code. It is working pretty well when I am doing it as a presented view, but not as a pushed view.
It is supposed to animate a snapshot of the pushed view from a CGRect to the full screen and vice versa when popped.
Here is the code of my UIViewControllerAnimatedTransitioning class:
class ZoomingTransitionController: NSObject, UIViewControllerAnimatedTransitioning {
let originFrame: CGRect
let isDismissing: Bool
init(originFrame: CGRect, isDismissing: Bool) {
self.originFrame = originFrame
self.isDismissing = isDismissing
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return Constant.Animation.VeryShort
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) else {
return
}
let finalFrame = transitionContext.finalFrame(for: toVC)
toVC.view.frame = finalFrame
let snapshot = self.isDismissing ? fromVC.view.snapshotView(afterScreenUpdates: true) : toVC.view.snapshotView(afterScreenUpdates: true)
snapshot?.frame = self.isDismissing ? finalFrame : self.originFrame
snapshot?.layer.cornerRadius = Constant.FakeButton.CornerRadius
snapshot?.layer.masksToBounds = true
containerView.addSubview(toVC.view)
containerView.addSubview(snapshot!)
if self.isDismissing {
fromVC.view.isHidden = true
} else {
toVC.view.isHidden = true
}
let duration = transitionDuration(using: transitionContext)
UIView.animate(withDuration: duration,
animations: {
snapshot?.frame = self.isDismissing ? self.originFrame : finalFrame
},
completion: { _ in
if self.isDismissing {
fromVC.view.isHidden = false
} else {
toVC.view.isHidden = false
}
snapshot?.removeFromSuperview()
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
}
Then, I tried to show a new View Controller using 2 ways: by presenting it and by pushing it.
My FromViewController is subclassing both UINavigationControllerDelegate and UIViewControllerTransitioningDelegate.
FromViewController class presenting the ToViewController (which works fine):
func buttonAction(_ sender: AnyObject) {
self.tappedButtonFrame = sender.frame
let toVC = self.storyboard!.instantiateViewController(withIdentifier: "ToViewController")
toVC.transitioningDelegate = self
self.present(toVC, animated: true, completion: nil)
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let transitionController = ZoomingTransitionController(originFrame: self.tappedButtonFrame, isDismissing: false)
return transitionController
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let transitionController = ZoomingTransitionController(originFrame: self.tappedButtonFrame, isDismissing: true)
return transitionController
}
FromViewController class pushing the ToViewController (which doesn't work):
func buttonAction(_ sender: AnyObject) {
self.tappedButtonFrame = sender.frame
let toVC = self.storyboard!.instantiateViewController(withIdentifier: "ToViewController")
self.navigationController?.pushViewController(toVC, animated: true)
}
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
switch operation {
case .push:
return ZoomingTransitionController(originFrame: self.tappedButtonFrame, isDismissing: false)
case .pop:
return ZoomingTransitionController(originFrame: self.tappedButtonFrame, isDismissing: true)
default:
return nil
}
}
When pushing, the delegate method is called and the ZoomingTransitionController performs its code fine (going in animateTransition until the end without notable issue). But on the screen, the snapshot view isn't display at any moment. The ToVC appears after the transition duration, but without anything else meanwhile.
I am running out of idea on how to debug this... Do you have any idea?
Thanks!!
I found my answer by replacing the snapshot element (which was causing the problem) by a CGAffineTransform of the toVC.
Code is almost the same than here.
I encountered a strange bug. I am just using iOS's custom transitioning method for UIViewControllers using UIViewControllerTransitioningDelegate together with an implementation of UIViewControllerAnimatedTransitioning. It all seems to work fine, until I do exactly the following:
open the app
present another view controller with my custom transition
rotate to landscape
dismiss the just presented view controller
That's all! What happens now is the following: I see a large black bar on the right side of the initial view controller (as if that controller's view wasn't rotated to landscape).
The funny thing is this only goes wrong in iOS 9, in iOS 8 everything seems to work just fine. Did anything change with custom transition API I don't know of? Or is this simply a really nasty iOS 9 bug? If anyone can tell me what I did wrong or if anyone can provide me with a workaround I would really appreciate that!
These classes reproduce the problem:
import UIKit
class ViewController: UIViewController, UIViewControllerTransitioningDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: "tap")
view.addGestureRecognizer(tapGestureRecognizer)
}
func tap() {
let controller = ModalViewController()
controller.transitioningDelegate = self
presentViewController(controller, animated: true, completion: nil)
}
func animationControllerForPresentedController(presented: UIViewController,
presentingController presenting: UIViewController,
sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return Transitioning()
}
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return Transitioning()
}
}
The presented view controller:
import UIKit
class ModalViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.redColor()
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: "tap")
view.addGestureRecognizer(tapGestureRecognizer)
}
func tap() {
dismissViewControllerAnimated(true, completion: nil)
}
}
And finally the UIViewControllerAnimatedTransitioning implementation:
import UIKit
class Transitioning: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 0.5
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)
let toView = transitionContext.viewForKey(UITransitionContextToViewKey)
let containerView = transitionContext.containerView()
if let fromView = fromView, toView = toView {
containerView?.addSubview(fromView)
containerView?.addSubview(toView)
toView.alpha = 0
UIView.animateWithDuration(0.5, animations: {
toView.alpha = 1
}, completion: {
finished in
transitionContext.completeTransition(true)
})
}
}
}
I generally use the following in animateTransition:
toView.frame = fromView.frame
FYI, you don't have to add fromView to the hierarchy, as it's already there.
Update to correct version, since have got the answer.
I want to do fade in/fade out animation when navigation view controller pop up /push, then I implement a BaseViewController:
class BaseViewController: UIViewController, UINavigationControllerDelegate {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.delegate = self
}
func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
if (operation == UINavigationControllerOperation.Push) {
return FadeInAnimator()
}
if (operation == UINavigationControllerOperation.Pop) {
return FadeOutAnimator()
}
return nil;
}
}
and FadeInAnimator, FadeOutAnimator:
class FadeInAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 0.5
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
transitionContext.containerView()?.addSubview(toViewController!.view)
toViewController?.view.alpha = 0.0
UIView.animateWithDuration(self.transitionDuration(transitionContext), animations: { () -> Void in
toViewController?.view.alpha = 1.0
}) { (finished) -> Void in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
}
}
}
class FadeOutAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 0.5
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)
transitionContext.containerView()?.insertSubview((toViewController?.view)!, belowSubview: (fromViewController?.view)!)
UIView.animateWithDuration(self.transitionDuration(transitionContext), animations: { () -> Void in
fromViewController?.view.alpha = 0.0
}) { (finished) -> Void in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
}
}
Then, I add 2 view controllers and embed navigation view controller, ViewControllerA and ViewControllerB:
class ViewControllerA: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func pressButton(sender: UIButton) {
if let vc = create(ViewControllerB) {
self.navigationController?.pushViewController(vc, animated: true)
}
}
}
class ViewControllerB: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
press button on ViewControllerA will create and jump to ViewControllerB, then press navigation bar item "back" on ViewControllerB will back to ViewControllerA.
But it doesn't work, when press button, the ViewControllerA show ViewControllerB, then show ViewControllerA again.
any suggestion? my xcode is 7.1 and run on iPad Air 2 simulator.
in your FadeInAnimator at animation completion block
change this
transitionContext.completeTransition(transitionContext.transitionWasCancelled())
to
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())