I am trying to present modally a view when tapping a button, that would have at first the same frame than the button, and then expanding to end up full screen, all this using UIViewControllerTransitioningDelegate.
Here is my code:
Expandable Base
class ExpandableBase: UIViewController {
var senderFrame: CGRect = .zero
#IBOutlet weak var fullScreenPopupView: UIView?
#IBAction func dismiss() {
self.dismiss(animated: true, completion: nil)
}
}
Transitioning Delegate extension
extension ExpandableBase: UIViewControllerTransitioningDelegate {
public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return ExpandableBasePresenter()
}
public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return ExpandableBaseDismisser()
}
}
private final class ExpandableBasePresenter: NSObject, UIViewControllerAnimatedTransitioning {
public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.8
}
public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let toViewController: ExpandableBase = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) as! ExpandableBase
let duration = self.transitionDuration(using: transitionContext)
let containerView = transitionContext.containerView
toViewController.view.frame = containerView.frame
containerView.addSubview(toViewController.view)
let finishFrame = toViewController.fullScreenPopupView?.frame
toViewController.fullScreenPopupView?.frame = toViewController.senderFrame
UIView.animate(withDuration: duration, delay: 0.3, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: .layoutSubviews, animations: {
toViewController.fullScreenPopupView?.frame = finishFrame!
}) { result in
transitionContext.completeTransition(result)
}
}
}
private final class ExpandableBaseDismisser: NSObject, UIViewControllerAnimatedTransitioning {
public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.4
}
public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let fromViewController: ExpandableBase = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) as! ExpandableBase
let duration = self.transitionDuration(using: transitionContext)
UIView.animate(withDuration: duration, delay: 0.1, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: .layoutSubviews, animations: {
fromViewController.fullScreenPopupView?.frame = fromViewController.senderFrame
}) { result in
transitionContext.completeTransition(result)
}
}
}
A simple view using this, presenting a label and a dismiss button:
final class ExpandableSimpleView: ExpandableBase {
init(from initialFrame: CGRect) {
super.init(nibName: "ExpandableSimpleView", bundle: .main)
self.senderFrame = initialFrame
self.transitioningDelegate = self
}
required init?(coder aDecoder: NSCoder) {
fatalError("NOPE")
}
static func present(fromInitialFrame initialFrame: CGRect) {
let expandableSimpleView = ExpandableSimpleView(from: initialFrame)
expandableSimpleView.modalPresentationStyle = .overCurrentContext
AppDelegateTopMostViewController.present(expandableSimpleView, animated: true, completion: nil)
//AppDelegateTopMostViewController is a global reference to the top-most view controller of the app
}
}
Here is the corresponding XIB:
And how I present this from the parent view controller:
#IBAction func openSimpleView(_ sender: UIButton) {
ExpandableSimpleView.present(fromInitialFrame: sender.frame)
}
And here are some screenshots showing how this view expands:
Although the view expands fine, the label is not centered as it should be. I don't understand why.
Thank you for your help.
EDIT: following matt's answer, I have made the following changes in animateTransition's presenter.
toViewController.fullScreenPopupView!.transform =
toViewController.fullScreenPopupView!.transform.scaledBy(x: toViewController.senderFrame.width / toViewController.view.bounds.width, y: toViewController.senderFrame.height / toViewController.view.bounds.height)
UIView.animate(withDuration: duration, delay: 0, options: [.allowUserInteraction], animations: {
toViewController.fullScreenPopupView!.transform = .identity
}, completion: nil)
Now the animation is fine, but I'm facing another issue: the button in the view is not clickable any longer.
Your animation is wrong. Do not start with a small frame and animate an expansion of the frame. Start with a small transform (e.g. x and y scale values of 0.1) and expand the transform (to .identity).
There is nothing wrong with shrinking or growing the parent view.
You don't have to animate the actual views. You can create and remove temporary views. Hide the actual view and reveal it during takedown.
I suggest that you animate the UILabel separately. It doesn't have to shrink or grow. It merely has to remain stationary. Place a temporary UILabel over the original, hide the original, and perform the animation. Reverse the process during takedown.
Related
I have found many related topics on SO (and elsewhere) but still couldn't find a solution to my problem. I want to display a custom alert on my view using UIViewControllerTransitioningDelegate. So first, in my initial view controller, here is the call:
#IBAction func tappedButton(_ sender: Any) {
MyAlertViewController.presentIn(viewController: self)
}
And here the code of MyAlertViewController:
import UIKit
open class MyAlertViewController: UIViewController, UIViewControllerTransitioningDelegate {
#IBOutlet weak var overlayView: UIView?
#IBOutlet weak var alertView: UIView?
#IBOutlet weak var alertCenterYConstraint: NSLayoutConstraint?
public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: "MyAlertView", bundle: nil)
self.transitioningDelegate = self
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
static func presentIn(viewController: UIViewController) {
let alertViewController = MyAlertViewController()
if Thread.isMainThread {
viewController.present(alertViewController, animated: true, completion: nil)
} else {
DispatchQueue.main.async {
viewController.present(alertViewController, animated: true, completion: nil)
}
}
}
public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return MyDismissAlertViewAnimationController()
}
public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return MyPresentAlertViewAnimationController()
}
}
class MyPresentAlertViewAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3
}
public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let toViewController: MyAlertViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) as! MyAlertViewController
let duration = self.transitionDuration(using: transitionContext)
let containerView = transitionContext.containerView
toViewController.view.frame = containerView.frame
containerView.addSubview(toViewController.view)
toViewController.overlayView?.alpha = 0.0
UIView.animate(withDuration: duration, animations: {
toViewController.overlayView?.alpha = 0.6
})
let finishFrame = toViewController.alertView?.frame
var startingFrame = finishFrame
startingFrame?.origin.y = -((finishFrame?.height)!)
toViewController.alertView?.frame = startingFrame!
UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 0.7, initialSpringVelocity: 1.0, options: .layoutSubviews, animations: {
toViewController.alertView?.frame = finishFrame!
}, completion: { result in
transitionContext.completeTransition(result)
})
}
}
class MyDismissAlertViewAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3
}
public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let fromViewController: MyAlertViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) as! MyAlertViewController
let duration = self.transitionDuration(using: transitionContext)
UIView.animate(withDuration: duration, animations: {
fromViewController.overlayView?.alpha = 0.0
})
var finishFrame = fromViewController.alertView?.frame
finishFrame?.origin.y = -(finishFrame?.height)!
finishFrame?.origin.y = fromViewController.isDismissingByBottom ? fromViewController.view.frame.size.height : -(finishFrame?.height)!
UIView.animate(withDuration: duration, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 1.0, options: .layoutSubviews, animations: {
fromViewController.alertView?.frame = finishFrame!
}, completion: { result in
transitionContext.completeTransition(true)
})
}
}
The animation works fine, the black screen only appears after the call to completeTransition() as you can see below:
Thanks for your help...
I think you need to either set your background color like so:
self.view.backgroundColor = .clear
This will make sure the background you see is not just the background color of your modal.
Or prevent the presenting view controller to be removed from the screen by making the modal presentation style overCurrentContext
self.modalPresentationStyle = .overCurrentContext
I'm trying to create a transition effect on a UITabBarController somewhat similar to the Facebook app. I managed to get a "scrolling effect" working on tab switch, but I can't seem to figure out how to cross dissolve (or it doesn't work at least).
Here's my current code:
import UIKit
class ScrollingTabBarControllerDelegate: NSObject, UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, animationControllerForTransitionFrom fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return ScrollingTransitionAnimator(tabBarController: tabBarController, lastIndex: tabBarController.selectedIndex)
}
}
class ScrollingTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
weak var transitionContext: UIViewControllerContextTransitioning?
var tabBarController: UITabBarController!
var lastIndex = 0
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.2
}
init(tabBarController: UITabBarController, lastIndex: Int) {
self.tabBarController = tabBarController
self.lastIndex = lastIndex
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
self.transitionContext = transitionContext
let containerView = transitionContext.containerView
let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)
let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
containerView.addSubview(toViewController!.view)
var viewWidth = toViewController!.view.bounds.width
if tabBarController.selectedIndex < lastIndex {
viewWidth = -viewWidth
}
toViewController!.view.transform = CGAffineTransform(translationX: viewWidth, y: 0)
UIView.animate(withDuration: self.transitionDuration(using: (self.transitionContext)), delay: 0.0, usingSpringWithDamping: 1.2, initialSpringVelocity: 2.5, options: .transitionCrossDissolve, animations: {
toViewController!.view.transform = CGAffineTransform.identity
fromViewController!.view.transform = CGAffineTransform(translationX: -viewWidth, y: 0)
}, completion: { _ in
self.transitionContext?.completeTransition(!self.transitionContext!.transitionWasCancelled)
fromViewController!.view.transform = CGAffineTransform.identity
})
}
}
Would be great if anyone know how to get this to work, been trying for days now without progress... :/
edit: I got a cross dissolve working by replacing the UIView.animate block with:
UIView.transition(with: containerView, duration: 0.2, options: .transitionCrossDissolve, animations: {
toViewController!.view.transform = CGAffineTransform.identity
fromViewController!.view.transform = CGAffineTransform(translationX: -viewWidth, y: 0)
}, completion: { _ in
self.transitionContext?.completeTransition(!self.transitionContext!.transitionWasCancelled)
fromViewController!.view.transform = CGAffineTransform.identity
})
However, the animation is really laggy and not usable :(
edit 2: For people trying to use these snippets, don't forget to hook up the delegate for the UITabBarController, otherwise nothing will happen.
edit 3: I've found a Swift library that does exactly what I was looking for:
https://github.com/Interactive-Studio/TransitionableTab
There is a simpler way to doing this. Add the following code in the tabbar delegate:
Working on Swift 2, 3 and 4
class MySubclassedTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
}
}
extension MySubclassedTabBarController: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
guard let fromView = selectedViewController?.view, let toView = viewController.view else {
return false // Make sure you want this as false
}
if fromView != toView {
UIView.transition(from: fromView, to: toView, duration: 0.3, options: [.transitionCrossDissolve], completion: nil)
}
return true
}
}
EDIT (4/23/18)
Since this answer is getting popular, I updated the code to remove the force unwraps, which is a bad practice, and added the guard statement.
EDIT (7/11/18)
#AlbertoGarcía is right. If you tap the tabbar icon twice you get a blank screen. So I added an extra check
If you want to use UIViewControllerAnimatedTransitioning to do something more custom than UIView.transition, take a look at this gist.
// MyTabController.swift
import UIKit
class MyTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
}
}
extension MyTabBarController: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, animationControllerForTransitionFrom fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return MyTransition(viewControllers: tabBarController.viewControllers)
}
}
class MyTransition: NSObject, UIViewControllerAnimatedTransitioning {
let viewControllers: [UIViewController]?
let transitionDuration: Double = 1
init(viewControllers: [UIViewController]?) {
self.viewControllers = viewControllers
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return TimeInterval(transitionDuration)
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard
let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
let fromView = fromVC.view,
let fromIndex = getIndex(forViewController: fromVC),
let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to),
let toView = toVC.view,
let toIndex = getIndex(forViewController: toVC)
else {
transitionContext.completeTransition(false)
return
}
let frame = transitionContext.initialFrame(for: fromVC)
var fromFrameEnd = frame
var toFrameStart = frame
fromFrameEnd.origin.x = toIndex > fromIndex ? frame.origin.x - frame.width : frame.origin.x + frame.width
toFrameStart.origin.x = toIndex > fromIndex ? frame.origin.x + frame.width : frame.origin.x - frame.width
toView.frame = toFrameStart
DispatchQueue.main.async {
transitionContext.containerView.addSubview(toView)
UIView.animate(withDuration: self.transitionDuration, animations: {
fromView.frame = fromFrameEnd
toView.frame = frame
}, completion: {success in
fromView.removeFromSuperview()
transitionContext.completeTransition(success)
})
}
}
func getIndex(forViewController vc: UIViewController) -> Int? {
guard let vcs = self.viewControllers else { return nil }
for (index, thisVC) in vcs.enumerated() {
if thisVC == vc { return index }
}
return nil
}
}
I was struggling with the tab bar animation both from a user tap and programmatically calling selectedIndex = X since the accepted solution didn't work for me when setting the selected tab programatically.
In the end I managed to solve it by a UITabBarControllerDelegate and a custom UIViewControllerAnimatedTransitioning as follows:
extension MainController: UITabBarControllerDelegate {
public func tabBarController(
_ tabBarController: UITabBarController,
animationControllerForTransitionFrom fromVC: UIViewController,
to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return FadePushAnimator()
}
}
Where the FadePushAnimator looks like this:
class FadePushAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard
let toViewController = transitionContext.viewController(forKey: .to)
else {
return
}
transitionContext.containerView.addSubview(toViewController.view)
toViewController.view.alpha = 0
let duration = self.transitionDuration(using: transitionContext)
UIView.animate(withDuration: duration, animations: {
toViewController.view.alpha = 1
}, completion: { _ in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
}
This approach supports any sort of custom animation and works both on user tap and setting the selected tab programatically. Tested on Swift 5.
To expand on #gmogames answer: https://stackoverflow.com/a/45362914/1993937
I couldn't get this to animate when selecting the tab bar index via code, as calling:
tabBarController.setSeletedIndex(0)
Doesn't seem to go through the same call heirarchy, and it skips the method:
tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController)
entirely, resulting in no animation.
In my code I wanted to have an animation transition for a user tapping a tab bar item in addition to me setting the tab bar item in-code manually under certain circumstances.
Here is my addition to the solution above which adds a different method to set the selected index via code that will animate the transition:
import Foundation
import UIKit
#objc class CustomTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
}
#objc func set(selectedIndex index : Int) {
_ = self.tabBarController(self, shouldSelect: self.viewControllers![index])
}
}
#objc extension CustomTabBarController: UITabBarControllerDelegate {
#objc func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
guard let fromView = selectedViewController?.view, let toView = viewController.view else {
return false // Make sure you want this as false
}
if fromView != toView {
UIView.transition(from: fromView, to: toView, duration: 0.3, options: [.transitionCrossDissolve], completion: { (true) in
})
self.selectedViewController = viewController
}
return true
}
}
Now just call
tabBarController.setSelectedWithIndex(1)
for an in-code animated transition!
I still think it is unfortunate that to get this done we have to override a method that isn't a setter and manipulate data within it. It doesn't make the tab bar controller as extensible as it should be if this is the method that we need to override to get this done.
So, a few years later and more experienced, after revisiting my own question for the same behaviour, I improved a little bit upon Derek's answer.
I inherited most of his code (as it seems like the best solution).
What I changed
I added a crossDissolve animation (as I originally wanted) to the slide animation by adding a toCoverView and fromCoverView, these are snapshotviews of the other view which will be used to fade in/out at the same time.
Changed the frame width to already start at 75% instead of having to translate the full 100% width, it's only translating 25% now which makes it feel snappier.
Added SpringWithDamping and initialSpringVelocity settings.
These changes made it feel just about as close as I could get it to Facebook's implementation and I'm personally quite happy with it.
Here's the adapted answer (most of the credit goes to Derek so be sure to upvote him):
class MyTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
}
}
extension MyTabBarController: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, animationControllerForTransitionFrom fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return MyTransition(viewControllers: tabBarController.viewControllers)
}
}
class MyTransition: NSObject, UIViewControllerAnimatedTransitioning {
let viewControllers: [UIViewController]?
let transitionDuration: Double = 0.2
init(viewControllers: [UIViewController]?) {
self.viewControllers = viewControllers
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return TimeInterval(transitionDuration)
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard
let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
let fromView = fromVC.view,
let fromIndex = getIndex(forViewController: fromVC),
let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to),
let toView = toVC.view,
let toIndex = getIndex(forViewController: toVC)
else {
transitionContext.completeTransition(false)
return
}
let frame = transitionContext.initialFrame(for: fromVC)
var fromFrameEnd = frame
var toFrameStart = frame
let quarterFrame = frame.width * 0.25
fromFrameEnd.origin.x = toIndex > fromIndex ? frame.origin.x - quarterFrame : frame.origin.x + quarterFrame
toFrameStart.origin.x = toIndex > fromIndex ? frame.origin.x + quarterFrame : frame.origin.x - quarterFrame
toView.frame = toFrameStart
let toCoverView = fromView.snapshotView(afterScreenUpdates: false)
if let toCoverView = toCoverView {
toView.addSubview(toCoverView)
}
let fromCoverView = toView.snapshotView(afterScreenUpdates: false)
if let fromCoverView = fromCoverView {
fromView.addSubview(fromCoverView)
}
DispatchQueue.main.async {
transitionContext.containerView.addSubview(toView)
UIView.animate(withDuration: self.transitionDuration, delay: 0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0.8, options: [.curveEaseOut], animations: {
fromView.frame = fromFrameEnd
toView.frame = frame
toCoverView?.alpha = 0
fromCoverView?.alpha = 1
}) { (success) in
fromCoverView?.removeFromSuperview()
toCoverView?.removeFromSuperview()
fromView.removeFromSuperview()
transitionContext.completeTransition(success)
}
}
}
func getIndex(forViewController vc: UIViewController) -> Int? {
guard let vcs = self.viewControllers else { return nil }
for (index, thisVC) in vcs.enumerated() {
if thisVC == vc { return index }
}
return nil
}
}
The only thing I've yet to figure out is how to make it "interruptible" like Facebook does. I know there's a interruptibleAnimator function for this but I haven't been able to make it work yet.
When swipe , i want navigate between pages with smoothly ( change according to finger moves ) not to navigate with a given time
class FirstCustomSegue: UIStoryboardSegue {
override func perform() {
// Assign the source and destination views to local variables.
var firstVCView = self.sourceViewController.view as UIView!
var secondVCView = self.destinationViewController.view as UIView!
// Get the screen width and height.
let screenWidth = UIScreen.mainScreen().bounds.size.width
let screenHeight = UIScreen.mainScreen().bounds.size.height
// Specify the initial position of the destination view.
secondVCView.frame = CGRectMake(screenWidth, 0.0, screenWidth, screenHeight)
// Access the app's key window and insert the destination view above the current (source) one.
let window = UIApplication.sharedApplication().keyWindow
window?.insertSubview(secondVCView, aboveSubview: firstVCView)
// Animate the transition.
UIView.animateWithDuration(0.4, animations: { () -> Void in
firstVCView.frame = CGRectOffset(firstVCView.frame, -screenWidth, 0.0)
secondVCView.frame = CGRectOffset(secondVCView.frame, -screenWidth, 0.0)
}) { (Finished) -> Void in
self.sourceViewController.presentViewController(self.destinationViewController as! UIViewController,
animated: false,
completion: nil)
}
} }
I had to deal with it recently. look at my github project, maybe it will hepl you.
If a nutshell. You should create class adopts
UIViewControllerAnimatedTransitioning
and implement 2 methods. One for animation's duration, another for your custom animation (moving, fade, and so on).
For example:
class ModalPresentAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
return 0.5
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
// ...
let duration = transitionDuration(transitionContext)
UIView.animateWithDuration(duration, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.2, options: UIViewAnimationOptions.CurveLinear, animations: {
toVC.view.frame = // new postion
}) { finished in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
}
}
Then in your ViewController to specify a new ViewControllerTransitioning
extension ViewController: UIViewControllerTransitioningDelegate {
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
swipeInteractionController.wireToViewController(presented)
return modalPresentAnimationController
}
But, if you want to add "change according to finger moves" you should create class adopts
UIPercentDrivenInteractiveTransition
And in it to use gestureRecognizer.
When using a UINavigationController and pushViewController:animated: to push another view controller onto the stack, there is always a drop shadow shown during the transition:
I have an app where I have a video background on the navigation controller, so I want to remove that drop shadow since it looks strange during the transition.
Is there any way to completely remove it? (I am NOT talking about UINavigationBar shadow)
Solution:
// set one of viewcontrollers (usual first) as navigationController delegate
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.delegate = self
}
}
// make the view controller conform to `UINavigationControllerDelegate`
extension ViewController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return PushPopAnimator(operation: operation)
}
}
// The animation controller
class PushPopAnimator: NSObject, UIViewControllerAnimatedTransitioning {
let operation: UINavigationControllerOperation
init(operation: UINavigationControllerOperation) {
self.operation = operation
super.init()
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.25
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let from = transitionContext.viewController(forKey: .from)!
let to = transitionContext.viewController(forKey: .to)!
let rightTransform = CGAffineTransform(translationX: transitionContext.containerView.bounds.size.width, y: 0)
let leftTransform = CGAffineTransform(translationX: -transitionContext.containerView.bounds.size.width, y: 0)
if operation == .push {
to.view.transform = rightTransform
transitionContext.containerView.addSubview(to.view)
UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
from.view.transform = leftTransform
to.view.transform = .identity
}, completion: { finished in
from.view.transform = .identity
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
} else if operation == .pop {
to.view.transform = leftTransform
transitionContext.containerView.insertSubview(to.view, belowSubview: from.view)
UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
to.view.transform = .identity
from.view.transform = rightTransform
}, completion: { finished in
from.view.transform = .identity
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
}
}
I followed This tutorial and achieve that animation but now I want to add some functionality into it like when user click in the minimised viewController I want to popup that minimised viewController back I tried to Implement TapGesture on that view and this is my code:
import Foundation
import UIKit
class TransitionOperator: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate{
var snapshot : UIView!
var isPresenting : Bool = true
func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval {
return 0.5
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
if isPresenting{
presentNavigation(transitionContext)
}else{
dismissNavigation(transitionContext)
}
}
func presentNavigation(transitionContext: UIViewControllerContextTransitioning) {
let container = transitionContext.containerView()
let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)
let fromView = fromViewController!.view
let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
let toView = toViewController!.view
let size = toView.frame.size
var offSetTransform = CGAffineTransformMakeTranslation(size.width - 120, 0)
offSetTransform = CGAffineTransformScale(offSetTransform, 0.6, 0.6)
snapshot = fromView.snapshotViewAfterScreenUpdates(true)
//TapGesture for detect touch
let aSelector : Selector = "singleTap"
let tapGesture = UITapGestureRecognizer(target: self, action: aSelector)
tapGesture.numberOfTapsRequired = 1
self.snapshot.addGestureRecognizer(tapGesture)
container.addSubview(toView)
container.addSubview(snapshot)
let duration = self.transitionDuration(transitionContext)
UIView.animateWithDuration(duration, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.8, options: nil, animations: {
self.snapshot.transform = offSetTransform
}, completion: { finished in
transitionContext.completeTransition(true)
})
}
func singleTap(){
NavigationViewController().dismissViewControllerAnimated(true, completion: nil)
println("Touched")
}
func dismissNavigation(transitionContext: UIViewControllerContextTransitioning) {
let container = transitionContext.containerView()
let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)
let fromView = fromViewController!.view
let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
let toView = toViewController!.view
let duration = self.transitionDuration(transitionContext)
UIView.animateWithDuration(duration, delay: 0.0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.8, options: nil, animations: {
self.snapshot.transform = CGAffineTransformIdentity
}, completion: { finished in
transitionContext.completeTransition(true)
self.snapshot.removeFromSuperview()
})
}
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
self.isPresenting = true
return self
}
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
self.isPresenting = false
return self
}
}
when I click on that minimised view Touched is print as you can see into image:
But view is not dismissed.I want to popup TimelineViewController back.
Thanks in advance.
Maybe what you need to do is call self.dismissNavigation() from your singleTap method. Though I'm not sure what context to pass to that method...
I know it's been a few months but i figured it out. Look at the function animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) which has the parameter presented, the UIViewController of the snapshot. That is your reference that you need instead of calling NavigationViewController(). which is just making new instances. So create a UIView variable var foo : UIView! and in animationControllerForPresentedController() set foo = presented.
Now in your function singleTap() you can set foo.dismissViewControllerAnimated().
Hope this helps.