Created
March 1, 2021 00:40
-
-
Save mrugeshtank/4f017c28e7ba9ddee0fb73cfab699986 to your computer and use it in GitHub Desktop.
Android bottom sheet like iOS present ViewController
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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