Once, the user taps a button, I want my modalViewController to appear as a small square in the middle of the screen (where you can still see the original view controller in the background).
Almost every answer on stackoverflow I find uses the storyboard to create a modal view controller, but I've gotten this far with everything I've found.
When you tap the button that is supposed to bring up the modal view, this function is called:
func didTapButton() {
let modalViewController = ModalViewController()
modalViewController.definesPresentationContext = true
modalViewController.modalPresentationStyle = .overCurrentContext
navigationController?.present(modalViewController, animated: true, completion: nil)
}
And the modalViewController contains:
import UIKit
class ModalViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .blue
view.isOpaque = false
self.preferredContentSize = CGSize(width: 100, height: 100)
}
}
Based on the answers I found, I was under the impression that if I set preferredContentSize = CGSize(width: 100, height: 100)
, then it would make the modal view controller 100px x 100px.
However, the view controller takes up the entire screen (except for the tab bar because I set modalViewController.modalPresentationStyle = .overCurrentContext
I'm obviously missing a step here, but I want to do everything programmatically as I'm not using the Storyboard at all in my project (except for setting the opening controller)
Thanks in advance for you help!!
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:
You can set view controller's background color to clear, and then create a view in the middle of the view controller, and set the modal presentation style to .overCurrentContext, and this way you will see the view controller from behind.
Here is the edited example:
func didTapButton() {
let modalViewController = storyboard?.instantiateViewController(withIdentifier: "ModalViewController") as! ModalViewController
modalViewController.modalPresentationStyle = .overCurrentContext
modalViewController.modalTransitionStyle = .crossDissolve // this will look more natural for this situation
navigationController?.present(modalViewController, animated: true, completion: nil)
}
Here is your presented view controller class:
import UIKit
class ModalViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .clear
createTheView()
}
private func createTheView() {
let xCoord = self.view.bounds.width / 2 - 50
let yCoord = self.view.bounds.height / 2 - 50
let centeredView = UIView(frame: CGRect(x: xCoord, y: yCoord, width: 100, height: 100))
centeredView.backgroundColor = .blue
self.view.addSubview(centeredView)
}
}
You can already build from here: add your desired look for the "smaller" view controller :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With