'How to present a ViewController on Half screen
I have a UIViewController which have only a UIView which covers 1/3 of the viewController from bottom. Like this
I want to present this viewController on an other ViewController. It should appear from bottom animated and it should dismiss to the bottom animated.
But I do not want it to cover the whole Screen. The viewController on which it is presented should be visible in the back.
It seems like a basic question But I am unable to get it done. Can someone please point me to the direction ?
Edit:
This is what I have tried so Far. I have created these classes
// MARK: -
class MyFadeInFadeOutTransitioning: NSObject, UIViewControllerTransitioningDelegate {
var backgroundColorAlpha: CGFloat = 0.5
var shoulDismiss = false
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let fadeInPresentAnimationController = MyFadeInPresentAnimationController()
fadeInPresentAnimationController.backgroundColorAlpha = backgroundColorAlpha
return fadeInPresentAnimationController
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let fadeOutDismissAnimationController = MyFadeOutDismissAnimationController()
return fadeOutDismissAnimationController
}
}
// MARK: -
class MYFadeInPresentAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
let kPresentationDuration = 0.5
var backgroundColorAlpha: CGFloat?
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return kPresentationDuration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
toViewController.view.backgroundColor = UIColor.clear
let toViewFrame = transitionContext.finalFrame(for: toViewController)
let containerView = transitionContext.containerView
if let pickerContainerView = toViewController.view.viewWithTag(kContainerViewTag) {
let transform = CGAffineTransform(translationX: 0.0, y: pickerContainerView.frame.size.height)
pickerContainerView.transform = transform
}
toViewController.view.frame = toViewFrame
containerView.addSubview(toViewController.view)
UIView.animate(withDuration: 0.3, delay: 0.0, options: .curveLinear , animations: {
toViewController.view.backgroundColor = UIColor(white: 0.0, alpha: self.backgroundColorAlpha!)
if let pickerContainerView = toViewController.view.viewWithTag(kContainerViewTag) {
pickerContainerView.transform = CGAffineTransform.identity
}
}) { (finished) in
transitionContext.completeTransition(true)
}
}
}
// MARK: -
class MYFadeOutDismissAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
let kDismissalDuration = 0.15
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return kDismissalDuration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
let containerView = transitionContext.containerView
containerView.addSubview(toViewController.view)
containerView.sendSubview(toBack: toViewController.view)
UIView.animate(withDuration: kDismissalDuration, delay: 0.0, options: .curveLinear, animations: {
// fromViewController.view.backgroundColor = UIColor.clearColor()
// if let pickerContainerView = toViewController.view.viewWithTag(kContainerViewTag) {
// let transform = CGAffineTransformMakeTranslation(0.0, pickerContainerView.frame.size.height)
// pickerContainerView.transform = transform
// }
fromViewController.view.alpha = 0.0
}) { (finished) in
let canceled: Bool = transitionContext.transitionWasCancelled
transitionContext.completeTransition(true)
if !canceled {
UIApplication.shared.keyWindow?.addSubview(toViewController.view)
}
}
}
}
And in the viewController which is being presented, I am doing as follows
var customTransitioningDelegate: MYFadeInFadeOutTransitioning? = MYFadeInFadeOutTransitioning()
init() {
super.init(nibName: "SomeNibName", bundle: Bundle.main)
transitioningDelegate = customTransitioningDelegate
modalPresentationStyle = .custom
customTransitioningDelegate?.backgroundColorAlpha = 0.0
}
It do present the viewController and I can see the background viewController as well. But I want it to be presented from bottom with animation. And dismiss to bottom with animation. How can I do that ?
Solution 1:[1]
If you want to present a view controller over half a screen I suggest using the UIPresentationController class it will allow you to set the frame of the view controller when it is presented. A word of advice, this method will stop the user interaction of the presentingViewController until you dismiss the presentedViewController, so if you want to show the view controller over half the screen while retaining user interaction with the presentingViewController you should use container views like the other answers suggested.
This is an example of a UIPresentationController class that does what you want
import UIKit
class ForgotPasswordPresentationController: UIPresentationController{
let blurEffectView: UIVisualEffectView!
var tapGestureRecognizer: UITapGestureRecognizer = UITapGestureRecognizer()
func dismiss(){
self.presentedViewController.dismiss(animated: true, completion: nil)
}
override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
let blurEffect = UIBlurEffect(style: UIBlurEffectStyle.dark)
blurEffectView = UIVisualEffectView(effect: blurEffect)
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.dismiss))
blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.blurEffectView.isUserInteractionEnabled = true
self.blurEffectView.addGestureRecognizer(tapGestureRecognizer)
}
override var frameOfPresentedViewInContainerView: CGRect{
return CGRect(origin: CGPoint(x: 0, y: self.containerView!.frame.height/2), size: CGSize(width: self.containerView!.frame.width, height: self.containerView!.frame.height/2))
}
override func dismissalTransitionWillBegin() {
self.presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (UIViewControllerTransitionCoordinatorContext) in
self.blurEffectView.alpha = 0
}, completion: { (UIViewControllerTransitionCoordinatorContext) in
self.blurEffectView.removeFromSuperview()
})
}
override func presentationTransitionWillBegin() {
self.blurEffectView.alpha = 0
self.containerView?.addSubview(blurEffectView)
self.presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (UIViewControllerTransitionCoordinatorContext) in
self.blurEffectView.alpha = 1
}, completion: { (UIViewControllerTransitionCoordinatorContext) in
})
}
override func containerViewWillLayoutSubviews() {
super.containerViewWillLayoutSubviews()
presentedView!.layer.masksToBounds = true
presentedView!.layer.cornerRadius = 10
}
override func containerViewDidLayoutSubviews() {
super.containerViewDidLayoutSubviews()
self.presentedView?.frame = frameOfPresentedViewInContainerView
blurEffectView.frame = containerView!.bounds
}
}
This also adds a blur view and a tap to dismiss when you tap outside the presentedViewController frame. You need to set the transitioningDelegate of the presentedViewController and implement the
presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController?
method in there. Don't forget to also set
modalPresentationStyle = .custom
of the presentedViewController
I find the usage of the UIPresentationController to be a much cleaner approach. Good luck
Solution 2:[2]
iOS 15: There's a new class, UISheetPresentationController, which contains a property called detents. This lets you specify what type of sizing behavior you want.
class ViewController: UIViewController {
@IBAction func nextButtonPressed(_ sender: Any) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let viewController = storyboard.instantiateViewController(withIdentifier: "NextViewController")
if let presentationController = viewController.presentationController as? UISheetPresentationController {
presentationController.detents = [.medium()] /// change to [.medium(), .large()] for a half *and* full screen sheet
}
self.present(viewController, animated: true)
}
}
| Half-screen sheet | Half and full-screen sheet |
|---|---|
![]() |
![]() |
Solution 3:[3]
iOS 15 *
Use UISheetPresentationController
How to use
let yourVC = YourViewController()
if let sheet = yourVC.sheetPresentationController {
sheet.detents = [.medium()]
}
self.present(yourVC, animated: true, completion: nil)
Read more Here
Below iOS 15
Use UIPresentationController, It will also work on ios 15 as well. But if your app support only iOS 15 use the code above.
import UIKit
class PresentationController: UIPresentationController {
let blurEffectView: UIVisualEffectView!
var tapGestureRecognizer: UITapGestureRecognizer = UITapGestureRecognizer()
override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
let blurEffect = UIBlurEffect(style: .dark)
blurEffectView = UIVisualEffectView(effect: blurEffect)
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissController))
blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.blurEffectView.isUserInteractionEnabled = true
self.blurEffectView.addGestureRecognizer(tapGestureRecognizer)
}
override var frameOfPresentedViewInContainerView: CGRect {
CGRect(origin: CGPoint(x: 0, y: self.containerView!.frame.height * 0.4),
size: CGSize(width: self.containerView!.frame.width, height: self.containerView!.frame.height *
0.6))
}
override func presentationTransitionWillBegin() {
self.blurEffectView.alpha = 0
self.containerView?.addSubview(blurEffectView)
self.presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (UIViewControllerTransitionCoordinatorContext) in
self.blurEffectView.alpha = 0.7
}, completion: { (UIViewControllerTransitionCoordinatorContext) in })
}
override func dismissalTransitionWillBegin() {
self.presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (UIViewControllerTransitionCoordinatorContext) in
self.blurEffectView.alpha = 0
}, completion: { (UIViewControllerTransitionCoordinatorContext) in
self.blurEffectView.removeFromSuperview()
})
}
override func containerViewWillLayoutSubviews() {
super.containerViewWillLayoutSubviews()
presentedView!.roundCorners([.topLeft, .topRight], radius: 22)
}
override func containerViewDidLayoutSubviews() {
super.containerViewDidLayoutSubviews()
presentedView?.frame = frameOfPresentedViewInContainerView
blurEffectView.frame = containerView!.bounds
}
@objc func dismissController(){
self.presentedViewController.dismiss(animated: true, completion: nil)
}
}
extension UIView {
func roundCorners(_ corners: UIRectCorner, radius: CGFloat) {
let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners,
cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.cgPath
layer.mask = mask
}
}
How to use
Add UIViewControllerTransitioningDelegate to your presenting ViewController
// MARK: - UIViewControllerTransitioningDelegate
extension PresentingViewController: UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
PresentationController(presentedViewController: presented, presenting: presenting)
}
}
Present yourVC in PresentingViewController
let yourVC = YourViewController()
yourVC.modalPresentationStyle = .custom
yourVC.transitioningDelegate = self
self.present(yourVC, animated: true, completion: nil)
Code reference
Solution 4:[4]
I'd recommend to implement this feature by using Container Views. Take a look here for reference.
This means you can show a UIViewController (and its subclasses) embedded in a UIView within another view controller. Then you can animate the fade-in or whatever you want.
Solution 5:[5]
There is the updated code to achieve this functionality. On action where you want to present ViewController
@IBAction func btnShow(_ sender: Any) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let pvc = storyboard.instantiateViewController(withIdentifier: "SubViewController") as! SubViewController
pvc.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
self.present(pvc, animated: true, completion: nil)
}
Go to StoryBoard select subViewController and add a UIView in it.
For blur effect set its constraint to
(top:0,Bottom:0,Leading:0,Trailing:0)
for all sides and change its color to black with the alpha you want.
And after that add a other UIView for options, set its constraints to
(top:-,Bottom:0,Leading:0,Trailing:0)
Set its height constraint to equal height with superview(self.View) and change its multipler to 0.33 or 0.34.
Solution 6:[6]
You can use a UIPresentationController to achieve this. Implement the UIViewControllerTransitioningDelegate method on presenting ViewController and return your PresentationController from delegate method
func presentationController(forPresented presented: UIViewController,
presenting: UIViewController?,
source: UIViewController) -> UIPresentationController?
You can refer this answer which has similar requirement. Alternatively you can use UIView animation or embedded view controller as suggested in the other answers.
Edit:
sample project found in Github
https://github.com/martinnormark/HalfModalPresentationController
Solution 7:[7]
Thanks to @aheze and @Sreekuttan Provides iOS15 solutions
I provide iOS13-15 solutions
iOS15:
Use UISheetPresentationController
let vc = HalfScreenVC()
if #available(iOS 15.0, *) {
if let sheet = vc.sheetPresentationController {
sheet.detents = [.medium()]
sheet.preferredCornerRadius = 20
}
}
present(vc, animated: true, completion: nil)
iOS13-iOS14
class HalfScreenVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if #available(iOS 15.0, *) {
} else {
// Below iOS 15, change frame here
self.view.frame = CGRect(x: 0, y: UIScreen.main.bounds.height / 5 * 2, width: self.view.bounds.width, height: UIScreen.main.bounds.height / 5 * 3)
self.view.layer.cornerRadius = 20
self.view.layer.masksToBounds = true
}
}
}
If we use textfield, we want to type in full screen
class HalfPresentVC: UIViewController {
let textfield = UITextField(frame: CGRect(x: 20, y: 30, width: 200, height: 40))
var initialBounds: CGRect?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
textfield.borderStyle = .roundedRect
textfield.placeholder = "enter text"
view.addSubview(textfield)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if #available(iOS 15.0, *) {
} else if initialBounds == nil {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
initialBounds = view.bounds
view.layer.cornerRadius = 20
view.layer.masksToBounds = true
view.frame = CGRect(x: 0, y: view.bounds.height / 5 * 2, width: view.bounds.width, height: view.bounds.height / 5 * 3)
}
}
@objc func keyboardWillShow() {
guard let initialBounds = initialBounds else {
return
}
view.frame = initialBounds
}
@objc func keyboardWillHide() {
guard let initialBounds = initialBounds else {
return
}
view.frame = CGRect(x: 0, y: initialBounds.height / 5 * 2, width: initialBounds.width, height: initialBounds.height / 5 * 3)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
Solution 8:[8]
If you want the functionality from the @DatForis answer but don't want to mess with overriding the presentation controller you can create a sort of obvious hack when using a full screen modal as shown here:
What you can do is present a full screen modal just like you normally would, however when you design the modal create two top views, one for the modal and a second view that would be placed over the background. The view that you place over the background should be transparent so that you can see the view from your presenting view controller.
Then to dismiss it you'd do something like this:
@IBOutlet weak var transparentView: UIView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(false)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(backgroundTapped(gesture:)))
transparentView.addGestureRecognizer(tapGesture)
}
@objc func backgroundTapped(gesture: UIGestureRecognizer) {
self.dismiss(animated: true, completion: nil)
}
Hope that makes sense / helps someone!
Solution 9:[9]
please write below code on presented view controller .
override var popupPresentDuration: Double { return 0.30 }
override var popupDismissDuration: Double { return 0.30 }
override var popupHeight: CGFloat { return CGFloat(UIScreen.main.bounds.height - 100) }
It will work on all ios versions.
Solution 10:[10]
simply you can do the following first you will design your screen to be 2 parts as in my case I want to hide the topView and I made it 0.3 of my whole screen and this code will be inside the viewDidLoad function and then you should present your viewController and set the modalPresentationStyle to be equal .formSheet
topView.backgroundColor = UIColor(white: 1, alpha: 0.000001)
self.view.backgroundColor = UIColor(white: 1, alpha: 0.000001)
Solution 11:[11]
You can also achieve the effect by presenting view controller modally and setting the presentation style property to over full screen, transition style property to cover vertical and setting the alpha component of view's background color to 0.1.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow







