Present modal view controller in half size parent controller
You can use a UIPresentationController
to achieve this.
For this you let the presenting ViewController
implement the UIViewControllerTransitioningDelegate
and return your PresentationController
for the half sized presentation:
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return HalfSizePresentationController(presentedViewController: presented, presenting: presentingViewController)
}
When presenting you set the presentation style to .Custom
and set your transitioning delegate:
pvc.modalPresentationStyle = .custom
pvc.transitioningDelegate = self
The presentation controller only returns the frame for your presented view controller:
class HalfSizePresentationController: UIPresentationController {
override var frameOfPresentedViewInContainerView: CGRect {
guard let bounds = containerView?.bounds else { return .zero }
return CGRect(x: 0, y: bounds.height / 2, width: bounds.width, height: bounds.height / 2)
}
}
Here is the working code in its entirety:
class ViewController: UIViewController, UIViewControllerTransitioningDelegate {
@IBAction func tap(sender: AnyObject) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let pvc = storyboard.instantiateViewController(withIdentifier: "CustomTableViewController") as! UITableViewController
pvc.modalPresentationStyle = .custom
pvc.transitioningDelegate = self
pvc.view.backgroundColor = .red
present(pvc, animated: true)
}
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return HalfSizePresentationController(presentedViewController: presented, presenting: presentingViewController)
}
}
class HalfSizePresentationController: UIPresentationController {
override var frameOfPresentedViewInContainerView: CGRect {
guard let bounds = containerView?.bounds else { return .zero }
return CGRect(x: 0, y: bounds.height / 2, width: bounds.width, height: bounds.height / 2)
}
}
How to present partial height modal view controller from bottom to top
Reading your comment it seems like you want to cover, say, the bottom half of the parent view with a modal view controller. If you're using storyboards, you can do this pretty easily using a contained view controller…
In the example below, the Show button is presenting the containing view controller modally, with a presentation type of over current context.
The view controller has a clear background, and a containing view set to half it's height. The contained (yellow) view controller has a dismiss button hooked up to an unwind segue in the green view controller.
@IBAction func unwind(_ segue: UIStoryboardSegue) { }
All that with one line of code!
Changing the size of a modal view controller
The modalPresentationStyle
documentation tells us
In a horizontally compact environment, modal view controllers are always presented full-screen.
So, if you want to do this in a iPhone in portrait mode, you have to specify a .custom
presentation style and have your transitioning delegate vend a custom presentation controller.
I’d personally let my second view controller manage its own presentation parameters, so my first view controller might only:
class FirstViewController: UIViewController {
@IBAction func didTapButton(_ sender: Any) {
let controller = storyboard!.instantiateViewController(withIdentifier: "SecondViewController")
present(controller, animated: true)
}
}
And then my second view controller would specify a custom transition and specify a custom transitioning delegate:
class SecondViewController: UIViewController {
private var customTransitioningDelegate = TransitioningDelegate()
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
}
private extension SecondViewController {
func configure() {
modalPresentationStyle = .custom
modalTransitionStyle = .crossDissolve // use whatever transition you want
transitioningDelegate = customTransitioningDelegate
}
}
Then that transitioning delegate would vend the custom presentation controller:
class TransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return PresentationController(presentedViewController: presented, presenting: presenting)
}
}
And that presentation controller would specify its size:
class PresentationController: UIPresentationController {
override var frameOfPresentedViewInContainerView: CGRect {
let bounds = presentingViewController.view.bounds
let size = CGSize(width: 200, height: 100)
let origin = CGPoint(x: bounds.midX - size.width / 2, y: bounds.midY - size.height / 2)
return CGRect(origin: origin, size: size)
}
override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
presentedView?.autoresizingMask = [
.flexibleTopMargin,
.flexibleBottomMargin,
.flexibleLeftMargin,
.flexibleRightMargin
]
presentedView?.translatesAutoresizingMaskIntoConstraints = true
}
}
This is just the tip of the iceberg with custom transitions. You can specify the animation controller (for custom animations), dim/blur the background, etc. See WWDC 2013 Custom Transitions Using View Controllers video for a primer on custom transitions, and WWDC 2014 videos View Controller Advancements in iOS 8 and A Look Inside Presentation Controllers dive into the details of presentation controllers.
For example, you might want to dim and blur the background when you present your modal view. So you might add presentationTransitionWillBegin
and dismissalTransitionWillBegin
to animate the presentation of this “dimming" view:
class PresentationController: UIPresentationController {
...
let dimmingView: UIView = {
let dimmingView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
dimmingView.translatesAutoresizingMaskIntoConstraints = false
return dimmingView
}()
override func presentationTransitionWillBegin() {
super.presentationTransitionWillBegin()
let superview = presentingViewController.view!
superview.addSubview(dimmingView)
NSLayoutConstraint.activate([
dimmingView.leadingAnchor.constraint(equalTo: superview.leadingAnchor),
dimmingView.trailingAnchor.constraint(equalTo: superview.trailingAnchor),
dimmingView.bottomAnchor.constraint(equalTo: superview.bottomAnchor),
dimmingView.topAnchor.constraint(equalTo: superview.topAnchor)
])
dimmingView.alpha = 0
presentingViewController.transitionCoordinator?.animate(alongsideTransition: { _ in
self.dimmingView.alpha = 1
}, completion: nil)
}
override func dismissalTransitionWillBegin() {
super.dismissalTransitionWillBegin()
presentingViewController.transitionCoordinator?.animate(alongsideTransition: { _ in
self.dimmingView.alpha = 0
}, completion: { _ in
self.dimmingView.removeFromSuperview()
})
}
}
That yields:
How to present a modal view controller with custom size in center?
I didn't find a way to do it from the modal controller itself so I created a class and an extension method:
public class ModalViewController : UIViewController
{
public SizeF OriginalViewSize { get; private set; }
void Initialize ()
{
ModalPresentationStyle = UIModalPresentationStyle.FormSheet;
}
public override void ViewDidLoad ()
{
OriginalViewSize = View.Bounds.Size;
base.ViewDidLoad ();
}
public ModalViewController (IntPtr handle) : base (handle)
{
Initialize ();
}
public ModalViewController (string nibName, NSBundle bundle) : base (nibName, bundle)
{
Initialize ();
}
public ModalViewController () : base ()
{
Initialize ();
}
}
public static class ModalViewControllerExtensions
{
public static void PresentModalViewController (this UIViewController parent, ModalViewController target)
{
parent.PresentViewController (target, true, null);
target.View.Superview.AutoresizingMask = UIViewAutoresizing.FlexibleMargins;
target.View.Superview.Frame = new RectangleF (PointF.Empty, target.OriginalViewSize);
target.View.Superview.Center = UIScreen.MainScreen.Bounds.Center ().Rotate ();
}
}
This is roughly how I use it:
this.PresentModalViewController (
new PublishModalViewController (Item, HandlePublishAction)
);
It is convenient that I don't need to specify the size explicitly because it uses root View's bounds from the interface builder. I'm not sure how this reacts to autorotate, it may need some tuning. I'm also using two extension methods here:
public static PointF Rotate (this PointF pt)
{
return new PointF (pt.Y, pt.X);
}
public static PointF Center (this RectangleF rect)
{
return new PointF (
(rect.Right - rect.Left) / 2.0f,
(rect.Bottom - rect.Top) / 2.0f
);
}
And this is it.
Related Topics
How to Calculate Uilabel Width Based on Text Length
Dyld'_Abort_With_Payload: Without an Error Message
How to Log a Method's Execution Time Exactly in Milliseconds
Using Iskindofclass with Swift
One Step Affine Transform for Rotation Around a Point
Failed to Obtain a Cell from Its Datasource
iOS Perform Action After Period of Inactivity (No User Interaction)
Phonegap Cdvviewcontroller.H File Not Found When Archiving for iOS
Silent Push Notification in iOS 7 Does Not Work
Framework Not Found Googletoolboxformac
Uitableview Load More When Scrolling to Bottom Like Facebook Application
Module Compiled with Swift 5.1 Cannot Be Imported by the Swift 5.1.2 Compiler
Xcode 4.5 and iOS 4.2.1 Incompatibility
Cannot Change Search Bar Background Color