Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save mrugeshtank/4f017c28e7ba9ddee0fb73cfab699986 to your computer and use it in GitHub Desktop.
Save mrugeshtank/4f017c28e7ba9ddee0fb73cfab699986 to your computer and use it in GitHub Desktop.
Android bottom sheet like iOS present ViewController
class MTCustomPresentationController: UIPresentationController {
let cornerRadius: CGFloat = 16.0
var dimmingView: UIView?
var presentationWrappingView: UIView?
override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
presentedViewController.modalPresentationStyle = .custom
}
override var presentedView: UIView? {
return presentationWrappingView
}
override func presentationTransitionWillBegin() {
let presentedViewControllerView = super.presentedView;
func action1() {
let presentationWrapperView = UIView(frame: self.frameOfPresentedViewInContainerView)
presentationWrapperView.layer.shadowOpacity = 0.44
presentationWrapperView.layer.shadowRadius = 13.0
presentationWrapperView.layer.shadowOffset = CGSize(width: 0, height: -6.0)
presentationWrappingView = presentationWrapperView
let presentationRoundedCornerView = UIView(frame: presentationWrapperView.bounds.inset(by: UIEdgeInsets(top: 0, left: 0, bottom: -cornerRadius, right: 0)))
presentationRoundedCornerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
presentationRoundedCornerView.layer.cornerRadius = cornerRadius
presentationRoundedCornerView.layer.masksToBounds = true
let presentedViewControllerWrapperView = UIView(frame: presentationWrapperView.bounds.inset(by: UIEdgeInsets(top: 0, left: 0, bottom: cornerRadius, right: 0)))
presentedViewControllerWrapperView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
presentedViewControllerView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
presentedViewControllerView?.frame = presentedViewControllerWrapperView.bounds
presentedViewControllerWrapperView.addSubview(presentedViewControllerView!)
presentationRoundedCornerView.addSubview(presentedViewControllerWrapperView)
presentationWrapperView.addSubview(presentationRoundedCornerView)
}
func action2() {
let dimmingView = UIView(frame: self.containerView!.bounds)
dimmingView.backgroundColor = .black
dimmingView.isOpaque = false
dimmingView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
dimmingView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(dimmingViewTapped(_:))))
self.dimmingView = dimmingView
self.containerView?.addSubview(dimmingView)
let transitionCoordinator = presentingViewController.transitionCoordinator
self.dimmingView?.alpha = 0.0
transitionCoordinator?.animate(alongsideTransition: { (context) in
self.dimmingView?.alpha = 0.5
}, completion: nil)
}
action1()
action2()
}
override func presentationTransitionDidEnd(_ completed: Bool) {
if !completed {
self.presentationWrappingView = nil
self.dimmingView = nil
}
}
override func dismissalTransitionWillBegin() {
let transitionCoordinator = presentingViewController.transitionCoordinator
transitionCoordinator?.animate(alongsideTransition: { (context) in
self.dimmingView?.alpha = 0.0
}, completion: nil)
}
override func dismissalTransitionDidEnd(_ completed: Bool) {
if completed {
self.presentationWrappingView = nil
self.dimmingView = nil
}
}
//MARK: -
//MARK: Layout
override func preferredContentSizeDidChange(forChildContentContainer container: UIContentContainer) {
super.preferredContentSizeDidChange(forChildContentContainer: container)
if container as? NSObject == self.presentedViewController {
self.containerView?.setNeedsLayout()
}
}
override func size(forChildContentContainer container: UIContentContainer, withParentContainerSize parentSize: CGSize) -> CGSize {
if container as? NSObject == self.presentedViewController {
return (container as! UIViewController).preferredContentSize
}
else {
return super.size(forChildContentContainer: container, withParentContainerSize: parentSize)
}
}
override var frameOfPresentedViewInContainerView: CGRect {
let containerViewBounds = self.containerView!.bounds
let presentedViewContentSize = size(forChildContentContainer: self.presentedViewController, withParentContainerSize: containerViewBounds.size)
var presentedViewControllerFrame = containerViewBounds
presentedViewControllerFrame.size.height = presentedViewContentSize.height
presentedViewControllerFrame.origin.y = containerViewBounds.maxY - presentedViewContentSize.height
return presentedViewControllerFrame
}
override func containerViewWillLayoutSubviews() {
super.containerViewWillLayoutSubviews()
self.dimmingView?.frame = self.containerView!.bounds
self.presentationWrappingView?.frame = self.frameOfPresentedViewInContainerView
}
//MARK: -
//MARK: Tap Gesture Recognizer
@objc func dimmingViewTapped(_ sender: UIGestureRecognizer) {
self.presentingViewController.dismiss(animated: true, completion: nil)
}
}
//MARK: -
//MARK: UIViewControllerAnimatedTransitioning
extension MTCustomPresentationController: UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
transitionContext?.isAnimated ?? true ? 0.35 : 0
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let fromViewController = transitionContext.viewController(forKey: .from)
let toViewController = transitionContext.viewController(forKey: .to)
let containerView = transitionContext.containerView
let toView = transitionContext.view(forKey: .to)
let fromView = transitionContext.view(forKey: .from)
let isPresenting = fromViewController == presentingViewController
let _ /*fromViewInitialFrame*/ = transitionContext.initialFrame(for: fromViewController!)
var fromViewFinalFrame = transitionContext.finalFrame(for: fromViewController!)
var toViewInitialFrame = transitionContext.initialFrame(for: toViewController!)
let toViewFinalFrame = transitionContext.finalFrame(for: toViewController!)
if let toView = toView {
containerView.addSubview(toView)
}
if isPresenting {
toViewInitialFrame.origin = CGPoint(x: containerView.bounds.minX, y: containerView.bounds.maxY)
toViewInitialFrame.size = toViewFinalFrame.size
toView?.frame = toViewInitialFrame
}
else {
fromViewFinalFrame = fromView!.frame.offsetBy(dx: 0, dy: fromView!.frame.height)
}
let transitionDuration = self.transitionDuration(using: transitionContext)
UIView.animate(withDuration: transitionDuration) {
if (isPresenting) {
toView?.frame = toViewFinalFrame
}
else {
fromView?.frame = fromViewFinalFrame
}
} completion: { (finished) in
let wasCancelled = transitionContext.transitionWasCancelled
transitionContext.completeTransition(!wasCancelled)
}
}
}
extension MTCustomPresentationController: UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
assert(self.presentedViewController == presented, "You didn't initialize \(self) with the correct presentedViewController. Expected \(presented), got \(self.presentedViewController).")
return self
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
self
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
self
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
@IBAction func btnPresentSelected(_ sender: UIButton) {
if let vc = storyboard?.instantiateViewController(identifier: "VC2") as? ViewController2 {
let presentationController = MTCustomPresentationController(presentedViewController: vc, presenting: self)
withExtendedLifetime(presentationController) { ()
vc.transitioningDelegate = presentationController
self.present(vc, animated: true, completion: nil)
}
}
}
}
class ViewController2: UIViewController {
var initialTouchPoint: CGPoint!
override func viewDidLoad() {
self.preferredContentSize = CGSize(width: self.view.bounds.size.width, height: 220)
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
self.view.addGestureRecognizer(panGesture)
self.view.layer.cornerRadius = 16.0
}
override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
super.willTransition(to: newCollection, with: coordinator)
self.preferredContentSize = CGSize(width: self.view.bounds.size.width, height: 220)
}
@objc func handlePanGesture(_ sender: UIGestureRecognizer) {
let touchPoint = sender.location(in: self.view.window)
if sender.state == .began {
initialTouchPoint = touchPoint
}
if sender.state == .changed {
if touchPoint.y - initialTouchPoint.y > 0 {
self.view.frame = CGRect(x: 0, y: touchPoint.y - initialTouchPoint.y, width: self.view.bounds.size.width, height: self.view.frame.size.height)
}
}
if sender.state == .ended || sender.state == .cancelled {
if touchPoint.y - initialTouchPoint.y > 100 {
self.dismiss(animated: true, completion: nil)
}
else {
UIView.animate(withDuration: 0.3) {
self.view.frame = CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: self.view.bounds.size.height)
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment