I am trying modify the width of my popover, which is a UITableViewController, so that it only takes up half of the width of the parent view. The popover is called programmatically when a button in another UITableView (the parent view) is tapped. I tried setting the preferredContentSize of the popover and setting the sourceRect but the popover still takes over the entire screen.
class MyTableViewController: UITableViewController, UICollectionViewDataSource, UICollectionViewDelegate, UIDynamicAnimatorDelegate, UIGestureRecognizerDelegate, CLLocationManagerDelegate, UIPopoverPresentationControllerDelegate, UIAdaptivePresentationControllerDelegate {
...
func goToPlaces(button: UIButton) {
let fromRect = CGRectMake(50.0, 50.0, self.view.bounds.width / 2.0, self.view.bounds.height)
let popoverVC = storyboard?.instantiateViewControllerWithIdentifier("otherPlaces")
popoverVC?.modalPresentationStyle = .OverFullScreen
presentViewController(popoverVC!, animated: true, completion: nil)
popoverVC?.view.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.6)
popoverVC?.preferredContentSize = CGSizeMake(self.view.bounds.width / 2.0, self.view.bounds.height)
let popoverController = popoverVC?.popoverPresentationController
popoverPresentationController?.sourceView = self.view
popoverPresentationController?.sourceRect = fromRect
popoverController?.permittedArrowDirections = .Any
popoverController?.delegate = self
}
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return .None
}
EDIT:
When I do a print of
popoverPresentationController?.sourceView and
popoverPresentationController?.sourceRect
they both return nil for some reason
You are asking for
popoverVC?.modalPresentationStyle = .OverFullScreen
so you get it covering the whole screen. Try using:
popoverVC?.modalPresentationStyle = .Popover
The
presentViewController(popoverVC!, animated: true, completion: nil)
should also be last so that the delegate can get the calls for which it wants to respond. (I think -- it might not matter if UIKit is actually delaying the presentation.)
Try using popoverVC.modalPresentationStyle = UIModalPresentationStyle.PageSheet,
Related
I want all of the functionality of a pageSheet UIModalPresentationStyle segue but I only want the presented ViewController to show half the screen (see the example in the image below).
I am presenting it modally using the pageSheet modalPresentationStyle but it always presents it at 100% height.
I haven't been able to figure out how to limit or modify a ViewController's height. I tried the following in my SecondViewController but it didn't work:
import UIKit
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.preferredContentSize = CGSize(width: self.view.frame.width, height: 400)
}
}
I'm initiating the segue with Storyboard Segues, and a button that presents it modally:
I figured out a way to do it, which I find to be pretty simple:
import UIKit
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let newView = UIView(frame: CGRect(x: 0, y: 500, width: self.view.frame.width, height: 400))
newView.backgroundColor = .yellow
newView.layer.cornerRadius = 20
self.view = UIView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height))
// self.view is now a transparent view, so now I add newView to it and can size it however, I like.
self.view.addSubview(newView)
// works without the tap gesture just fine (only dragging), but I also wanted to be able to tap anywhere and dismiss it, so I added the gesture below
let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
self.view.addGestureRecognizer(tap)
}
#objc func handleTap(_ sender: UITapGestureRecognizer? = nil) {
dismiss(animated: true, completion: nil)
}
}
In order to achieve you will need to subclass UIPresentationController and implement the protocol UIViewControllerTransitioningDelegate in the presenting controller and set transitioningDelegate and modalPresentationStyle of presented view controller as self(presenting view controller) and .custom respectively. Implement an optional function of UIViewControllerTransitioningDelegate:
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source _: UIViewController) -> UIPresentationController?
and return the custom presentationController which sets the height of presented controller as per your requirement.
Basic code that might help:
class CustomPresentationController: UIPresentationController {
var presentedViewHeight: CGFloat
init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?, presentedViewHeight: CGFloat) {
self.presentedViewHeight = presentedViewHeight
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
}
override var frameOfPresentedViewInContainerView: CGRect {
var frame: CGRect = .zero
frame.size = CGSize(width: containerView!.bounds.width, height: presentedViewHeight)
frame.origin.y = containerView!.frame.height - presentedViewHeight
return frame
}
}
Implementation of optional function:
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source _: UIViewController) -> UIPresentationController? {
let presentationController = CustomPresentationController(presentedViewController: presented, presenting: presenting, presentedViewHeight: 100)
return presentationController
}
You can also play with other optional functions and adding some other functionalities to CustomPresentationController like adding blur background, adding tap functionality and swipe gesture.
We can add our view in UIActivityController and remove UIActivityController's default view and if you add navigation controller so you will get navigation also, so you can do half your controller by this:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func actionPresent(_ sender: UIBarButtonItem) {
let vc1 = storyboard?.instantiateViewController(withIdentifier: "ViewControllerCopy")
let vc = ActivityViewController(controller: vc1!)
self.present(vc, animated: true, completion: nil)
}
}
class ActivityViewController: UIActivityViewController {
private let controller: UIViewController!
required init(controller: UIViewController) {
self.controller = controller
super.init(activityItems: [], applicationActivities: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
let subViews = self.view.subviews
for view in subViews {
view.removeFromSuperview()
}
self.addChild(controller)
self.view.addSubview(controller.view)
}
}
for example you can check this repo:
https://github.com/SomuYadav/HalfViewControllerTransition
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 :)
I am presenting a modal popover view like this:
let popover = StatueSelectionController(collectionViewLayout: UICollectionViewFlowLayout())
popover.delegate = self
popover.modalPresentationStyle = .popover
popover.popoverPresentationController?.barButtonItem = navigationItem.rightBarButtonItem
popover.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.up;
popover.preferredContentSize = CGSize(width: 200, height: 300)
present(popover, animated: true, completion: nil)
The popover.delegate = self has nothing to do with UIPopoverControllerDelegate although it does implement that protocol.
The problem is that the popover takes up the whole view. StatueSelectionController, which is a UICollectionViewController, fills up the whole screen and does not change for preferredContentSize.
What am I doing wrong here?
If you want to force the popOver to not cover the full screen on an iPhone, you would have to add a UIPopoverPresentationControllerDelegate method in your code
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
return .none
}
This is my UI trying to make that Popup full length:
This is how it looks when Keyboard appears:
I want to show this in full length.
func present(_ viewController: UINavigationController,
contentSize: CGSize,
sourceView: UIView,
delegate: UIPopoverPresentationControllerDelegate?) {
viewController.navigationBar.isTranslucent = false
viewController.preferredContentSize = contentSize
viewController.modalPresentationStyle = .popover
viewController.popoverPresentationController?.permittedArrowDirections = .left
viewController.popoverPresentationController?.sourceView = sourceView
viewController.popoverPresentationController?.sourceRect = sourceView.bounds
viewController.popoverPresentationController?.delegate = delegate
viewController.view.superview?.layer.cornerRadius = 0
viewController.view.superview?.layer.masksToBounds = true
present(viewController, animated: true, completion: {
viewController.interactivePopGestureRecognizer?.isEnabled = false
})
}
I wish to create a small popover about 50x50px from a UIButton. I have seen methods using adaptive segue's but I have my size classes turn of thus meaning I can not use this features!
How else can I create this popover? Can I create it with code inside my button IBACtion? Or is there still a way I can do this with storyboards?
You can do one of the following two options :
Create an action for the UIButton in your UIViewController and inside present the ViewController you want like a Popover and your UIViewController has to implement the protocol UIPopoverPresentationControllerDelegate, take a look in the following code :
#IBAction func showPopover(sender: AnyObject) {
var popoverContent = self.storyboard?.instantiateViewControllerWithIdentifier("StoryboardIdentifier") as! UIViewController
popoverContent.modalPresentationStyle = .Popover
var popover = popoverContent.popoverPresentationController
if let popover = popoverContent.popoverPresentationController {
let viewForSource = sender as! UIView
popover.sourceView = viewForSource
// the position of the popover where it's showed
popover.sourceRect = viewForSource.bounds
// the size you want to display
popoverContent.preferredContentSize = CGSizeMake(200,500)
popover.delegate = self
}
self.presentViewController(popoverContent, animated: true, completion: nil)
}
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return .None
}
According to the book of #matt Programming iOS 8:
A popover presentation controller, in iOS 8, is a presentation controller (UIPresentationController), and presentation controllers are adaptive. This means that, by default, in a horizontally compact environment (i.e. on an iPhone), the .Popover modal presentation style will be treated as .FullScreen. What appears as a popover on the iPad will appear as a fullscreen presented view on the iPhone, completely replacing the interface.
To avoid this behavior in the iPhone you need to implement the delegate method adaptivePresentationStyleForPresentationController inside your UIViewController to display the Popover correctly.
The other way in my opinion is more easy to do, and is using Interface Builder, just arrange from the UIButton to create a segue to the ViewController you want and in the segue select the Popover segue.
I hope this help you.
Swift 4 Here is fully working code. So here you will see popup window with size of 250x250:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// in case if you don't want to make it via IBAction
button.addTarget(self, action: #selector(tapped), for: .touchUpInside)
}
#objc
private func tapped() {
guard let popVC = storyboard?.instantiateViewController(withIdentifier: "popVC") else { return }
popVC.modalPresentationStyle = .popover
let popOverVC = popVC.popoverPresentationController
popOverVC?.delegate = self
popOverVC?.sourceView = self.button
popOverVC?.sourceRect = CGRect(x: self.button.bounds.midX, y: self.button.bounds.minY, width: 0, height: 0)
popVC.preferredContentSize = CGSize(width: 250, height: 250)
self.present(popVC, animated: true)
}
}
// This is we need to make it looks as a popup window on iPhone
extension ViewController: UIPopoverPresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
}
Take into attention that you have to provide popVC identifier to one viewController you want to present as a popup.
Hope that helps!
Here you can present a popover on button click.
func addCategory( _ sender : UIButton) {
var popoverContent = self.storyboard?.instantiateViewControllerWithIdentifier("NewCategory") as UIViewController
var nav = UINavigationController(rootViewController: popoverContent)
nav.modalPresentationStyle = UIModalPresentationStyle.Popover
var popover = nav.popoverPresentationController
popoverContent.preferredContentSize = CGSizeMake(50,50)
popover.delegate = self
popover.sourceView = sender
popover.sourceRect = sender.bounds
self.presentViewController(nav, animated: true, completion: nil)
}
Swift 4 Version
Doing most work from the storyboard
I added a ViewController, went to it's attribute inspector and ticked the "Use Preferred Explicit size". After that I changed the Width and Height values to 50 each.
Once this was done I ctrl clicked and dragged from the Button to the ViewController I added choosing "Present as Popover" and naming the segue Identifier as "pop"
Went to the ViewController where I had my Button and added the following code:
class FirstViewController: UIViewController, UIPopoverPresentationControllerDelegate {
#IBOutlet weak var popoverButton: UIButton! // the button
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "pop" {
let popoverViewController = segue.destination
popoverViewController.modalPresentationStyle = .popover
popoverViewController.presentationController?.delegate = self
popoverViewController.popoverPresentationController?.sourceView = popoverButton
popoverViewController.popoverPresentationController?.sourceRect = CGRect(x: 0, y: 0, width: popoverButton.frame.size.width, height: popoverButton.frame.size.height)
}
}
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.none
}
override func viewDidLoad() {
super.viewDidLoad()
}
}