This Swift 4 ModalService
class below is a pre-packaged flexible solution that can be dropped into a project and called from anywhere it is required. This class brings the modal view in on top of the current view from the specified direction, and then moves it out to reveal the original view behind it when it exits. Given a presentingViewController
that is currently being displayed and a modalViewController
that you have created and wish to display, you simply call:
ModalService.present(modalViewController, presenter: presentingViewController)
Then, to dismiss, call:
ModalService.dismiss(modalViewController)
This can of course be called from the modalViewController itself as ModalService.dismiss(self)
. Note that dismissing does not have to be called from the presentingViewController, and does not require knowledge of or a reference to the original presentingViewController.
The class provides sensible defaults, including transitioning the modal view in from the right and out to the left. This transition direction can be customised by passing a direction, which can be customised for both entry and exit:
ModalService.present(modalViewController, presenter: presentingViewController, enterFrom: .left)
and
ModalService.dismiss(self, exitTo: .left)
This can be set as .left
, .right
, .top
and .bottom
. You can likewise pass a custom duration in seconds if you wish:
ModalService.present(modalViewController, presenter: presentingViewController, enterFrom: .left, duration: 0.5)
and
ModalService.dismiss(self, exitTo: .left, duration: 2.0)
Head nod to @jcdmb for the Objective C answer on this question which formed the kernel of the solution in this class.
Here's the full class. The values returned by the private transitionSubtype
look odd but are set this way deliberately. You should test the observed behaviour before assuming these need 'correcting'. :)
import UIKit
class ModalService {
enum presentationDirection {
case left
case right
case top
case bottom
}
class func present(_ modalViewController: UIViewController,
presenter fromViewController: UIViewController,
enterFrom direction: presentationDirection = .right,
duration: CFTimeInterval = 0.3) {
let transition = CATransition()
transition.duration = duration
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionMoveIn
transition.subtype = ModalService.transitionSubtype(for: direction)
let containerView: UIView? = fromViewController.view.window
containerView?.layer.add(transition, forKey: nil)
fromViewController.present(modalViewController, animated: false)
}
class func dismiss(_ modalViewController: UIViewController,
exitTo direction: presentationDirection = .right,
duration: CFTimeInterval = 0.3) {
let transition = CATransition()
transition.duration = duration
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionReveal
transition.subtype = ModalService.transitionSubtype(for: direction, forExit: true)
if let layer = modalViewController.view?.window?.layer {
layer.add(transition, forKey: nil)
}
modalViewController.dismiss(animated: false)
}
private class func transitionSubtype(for direction: presentationDirection, forExit: Bool = false) -> String {
if (forExit == false) {
switch direction {
case .left:
return kCATransitionFromLeft
case .right:
return kCATransitionFromRight
case .top:
return kCATransitionFromBottom
case .bottom:
return kCATransitionFromTop
}
} else {
switch direction {
case .left:
return kCATransitionFromRight
case .right:
return kCATransitionFromLeft
case .top:
return kCATransitionFromTop
case .bottom:
return kCATransitionFromBottom
}
}
}
}