Why is 'present as popover' segue covering the whole screen? - ios

In my project I have a button on the bottom right side of the screen and i added another uiviewcontroller to the storyboard, did control-drag to the uiviewcontroller I wanted as the popover, then set that viewcontroller size to (300, 300) and checked 'use preferred explicit size'. When I load the app and click the button, the entire screen gets covered by the "popover". I also tried to go into the popoverViewController's .m file and set the size but that didn't work either.
Any ideas?
Edit: Since it looks like I have to have it be full screen that is fine however I am still running into some other problems I was having earlier. My popup screen will come up and I make the background black and alpha as .5 to make it see through however it'll do the animation, then once the animation is finished the screen will go from .5 opacity to completely black and the only thing I can see is the battery icon thing.

The OP uses Objective-C. This answer presents code in swift. Converting swift to Objective-C should be easy.
In the newly added ViewController, under “Simulated Metrics” change “Size” to “Freeform” and “Status Bar” to “None.”
Under “Simulated Size” change your view’s height and width to the actual size you want your popover’s content to be.
Create a segue to the newly added VC. Use segue type as "Present As Popover" and give a name for the segue, for example "popoverSegue".
In the ViewConroller from which this segue is to be triggered, add the UIPopoverPresentationControllerDelegate protocol.
class ViewController: UIViewController, UIPopoverPresentationControllerDelegate {
}
Override the prepareForSegue function to catch your popover segue. Set the modalPresentationStyle to .Popover to explicitly state that you want a popover and then assign the delegate property of the view’s popoverPresentationController to self:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "popoverSegue" {
let popoverViewController = segue.destinationViewController as! UIViewController
popoverViewController.modalPresentationStyle = UIModalPresentationStyle.Popover
popoverViewController.popoverPresentationController!.delegate = self
}
}
Implement the adaptivePresentationStyleForPresentationController function to tell your app that you really want that popover presentation and will accept no substitutions:
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.None
}
Following these, I could get a popup on iPhone which is not full screen but the size set for the ViewController.
Source: iPad Style Popovers on the iPhone with Swift

Thanks to Bharat for the great answer, I personally use a UIStoryboardSegue that does pretty much the same thing. That way, I can change the class of the segue in the storyboard, have what I want, and not pollute my controllers:
class AlwaysPopupSegue : UIStoryboardSegue, UIPopoverPresentationControllerDelegate
{
override init(identifier: String?, source: UIViewController, destination: UIViewController)
{
super.init(identifier: identifier, source: source, destination: destination)
destination.modalPresentationStyle = UIModalPresentationStyle.popover
destination.popoverPresentationController!.delegate = self
}
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.none
}
}

Swift 3-5 version
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "SEGUE_IDENTIFIER" {
let popoverViewController = segue.destination as! YourViewController
popoverViewController.modalPresentationStyle = UIModalPresentationStyle.popover
popoverViewController.popoverPresentationController!.delegate = self
}
}
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.none
}

Swift 4 Version
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "SegueIdentifier" {
let popoverViewController = segue.destination
popoverViewController.modalPresentationStyle = .popover
popoverViewController.presentationController?.delegate = self
}
}
Don't forget to add
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.none
}

On iPhone you can creat a custom view controller that can manage all the popovers. Since each view controller has its own navigation controller, you can add a new view controller to the app.window.rootviewcontroller as a du view and bring all to front.
If you didn't want to write your own, you can use something like this for instance: http://cocoapods.org/pods/FPPopover

This is Swift 5 code, some/most of the above mentioned solutions are all valid. This is an effort to present whole solution. This example supposes you are using a xib for popover view controller but this would work otherwise as well, say, in prepare for segue. Here's a complete code:
Presenting ViewController:
let popoverVC = PopoverVC(nibName: "popoverVC", bundle: nil)
popoverVC.completionHandler = { [unowned self] (itemIndex : Int?) in
if let itemIndex = itemIndex
{
// Do completion handling
}
}
popoverVC.preferredContentSize = CGSize(width: 200, height: 60)
popoverVC.modalPresentationStyle = .popover
if let pvc = popoverVC.popoverPresentationController {
pvc.permittedArrowDirections = [.down]
pvc.delegate = self
pvc.sourceRect = button.frame
pvc.sourceView = button // Button popover is presented from
present(popoverVC, animated: true, completion: nil)
}
This is important:
extension ViewController: UIPopoverPresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
}

Related

Popover covers entire screen iOS

I am trying to implement a popover whose anchor is a bar button item for my iphone app. I have connected the bar button item to the view controller via a segue pathway configured as show as "present as popover." I have also set my own size for the view controller and selected "use preferred explicit size." Based on previous posts about this similar topic, I have implemented the following code for my popover. However, the popover is still covering the entire screen, probably because my adaptivePresentationStyle method is not being called ("hello" does not print to the screen). Note that I have also implemented the UIPopoverPresentationControllerDelegate. Where did I go wrong?
override func prepare (for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "popoverLogin" {
let popoverViewController = segue.destination
popoverViewController.modalPresentationStyle = UIModalPresentationStyle.popover
popoverViewController.popoverPresentationController!.delegate = self }
}
// MARK: - UIPopoverPresentationControllerDelegate method
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
// Force popover style
print ("hello")
return UIModalPresentationStyle.none
}
Thanks!
In my case your code works and popover is presented normally!
Make sure your segue has the right "Kind": "Present As Popover".

Perform Function when Popover is Dismissed

I have a popover that is using the same view controller that calls it.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "groupSelect" {
let popVC = segue.destination
popVC.popoverPresentationController?.delegate = self
}
}
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.none
}
However, because I used the storyboard to make everything, I'm not really sure how or where I can take over or add code when the popover gets dismissed. In Xcode 8.2 with Swift 3, adding a popover with the storyboard editor automatically makes the popover dismiss when the user touches outside the popover. Everything is working great, the only problem is that when I return from the popover, the table under it won't have the reload function called so any changes made in the popover don't take effect for the user.
Implement UIPopoverPresentationControllerDelegate method popoverPresentationControllerDidDismissPopover which tells the delegate that the popover was dismissed.

Segue to popover for iPhone not working in iOS 10?

I wanted to segue to a popover in iOS 10, this piece of code used to work fine on iPhone but not now (it shows full screen), what have I done wrong? The segue is set to "Present As Popover".
override func prepare(for segue:UIStoryboardSegue, sender:AnyObject!) {
if segue.identifier == "about" {
let aboutController = segue.destination as! AboutController
aboutController.preferredContentSize = CGSize(width:300, height:440)
let popoverController = aboutController.popoverPresentationController
if popoverController != nil {
popoverController!.delegate = self
popoverController!.backgroundColor = UIColor.black
}
}
}
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
Many function have been renamed in Swift 3, including adaptivePresentationStyleForPresentationController - this is now adaptivePresentationStyle(for:)
Change your code to
func adaptivePresentationStyle(for controller:UIPresentationController) -> UIModalPresentationStyle {
return .none
}
Since your function name didn't match it wasn't being called and because it is an optional function in the protocol, you didn't get a warning.
Popover is an iPad only feature.
UIKit is smart enough to figure out it should present it modally on iPhone/iPod.

Skip/Add a View Controller in Navigation Stack

I have three View Controllers embedded in Navigation Controller. I want to go from VC1 to VC3 so that in VC3 the Navigation Item's back button would direct the user to VC2 instead of VC1. I think this should be done either by adding the VC2 to the Navigation Stack between VC1 and VC3 when VC3 is created or by skipping the second View Controller.
I tried this:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
switch identifier {
case "JumpToThirdVCSegue":
if let secondvc = segue.destinationViewController as? SecondViewController {
secondvc.performSegueWithIdentifier("ThirdVCSegue", sender: self)
}
default: break
}
}
}
but I couldn't get it working. Perhaps performing a segue isn't possible when the View Controller isn't open yet?
What is the best way to skip a View Controller / add a View Controller in the middle of Navigation Stack? I hope this helps you to understand what I'm trying to do:
Something like this should work:
self.navigationController?.pushViewController(viewController2, animated: true)
self.navigationController?.pushViewController(viewController3, animated: true)
EDIT:
If you want to push the second view controller without being noticed by the user you need to add it to the navigation controller after the third view controller is pushed. This can be done by implementing UINavigationControllerDelegate. You can store your second view controller in a variable and insert it to the navigation controllers hierarchy in delegate method. Your main view controller will look like following:
class MyViewController: UIViewController, UINavigationControllerDelegate {
var viewControllerToInsertBelow : UIViewController?
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.delegate = self
}
func pushTwoViewControllers() {
if let viewController2 = self.storyboard?.instantiateViewControllerWithIdentifier("id1"),
let viewController3 = self.storyboard?.instantiateViewControllerWithIdentifier("id2") { //change this to your identifiers
self.viewControllerToInsertBelow = viewController2
self.navigationController?.pushViewController(viewController3, animated: true)
}
}
//MARK: - UINavigationControllerDelegate
func navigationController(navigationController: UINavigationController, didShowViewController viewController: UIViewController, animated: Bool) {
if let vc = viewControllerToInsertBelow {
viewControllerToInsertBelow = nil
let index = navigationController.viewControllers.indexOf(viewController)!
navigationController.viewControllers.insert(vc, atIndex: index)
}
}
}
if var navstack = navigationController?.viewControllers{
navstack.append(contentsOf: [vc1,vc2])
navigationController?.setViewControllers(navstack, animated: true)
}
Works well
Edit: using swift and segues, this should work:
override func performSegueWithIdentifier(identifier: String?, sender: AnyObject?) {
super.performSegueWithIdentifier(identifier, sender: sender);
if identifier == "JumpToThirdVCSegue" {
// now the vc3 was already pushed on the navigationStack
var navStackArray : [AnyObject]! = self.navigationController!.viewControllers
// insert vc2 at second last position
navStackArray.insert(viewController2, atIndex: navStackArray.count - 2)
// update navigationController viewControllers
self.navigationController!.setViewControllers(navStackArray, animated:false)
}
}
So you override the performSegueWithIdentifier in the VC1 in order to change the navigation stack when the segue to VC3 has actually been performed (and is not only in preparation).
This assumes, that you want to handle this special navigation logic in VC1.
You can use method
func setViewControllers(_ viewControllers: [UIViewController],
animated: Bool)
Here's the documentation (link) that solves the UIViewController skipping problem.
Simply pass all the needed UIViewControllers to it (in navigational order) and the last one will be made as the current active UIViewController (if it already isn't the active one).
My solution would be to keep a BOOL property of when you should skip to third and when not skip, like declaring ’shouldSkip’ in VC2 so that if u set it in prepare segue like below you can act according to that in VC2
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
switch identifier {
case "JumpToThirdVCSegue":
secondvc.shouldSkip=true
}
default: break
}
}
}
and then in viewDidload VC2 you should check this BOOL and if it is true and perform segue and if not just move on
you could also pass pass whenever that kind of skip is not necessary
Building off of MarkHim's answer, I was getting a Black screen when navigating back to the inserted view controller so I came up with the following solution.
BTW - I wanted to insert the new view controller directly under the view controller I just segued to hence the navStack.count - 1 instead of navStack - 2.
Here is my solution:
override func performSegue(withIdentifier identifier: String, sender: Any?) {
super.performSegue(withIdentifier: identifier, sender: sender)
if identifier == "JumpToThirdViewControllerSegue",
let navController = navigationController { // unwrap optional
// initialize the view controller you want to insert
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let viewControllerToInsert = storyboard.instantiateViewController(
withIdentifier: "SecondVC") as! SecondViewController
// set any passed properties
viewControllerToInsert.passedProperty = propertyToPass
// create an object using the current navigation stack
var navStackArray: [UIViewController]! = navController.viewControllers
// insert the newly initialized view controller into the navStackArray
navStackArray.insert(viewControllerToInsert, at: navStackArray.count - 1)
// replace the current navigation stack with the one you just
// inserted your view controller in to
navController.setViewControllers(navStackArray, animated: false)
}
}

Swift: How to create a popup menu in iOS

I'm doing some drawing on a custom UIView canvas, and rather than having a set of buttons at the bottom of the view to allow the user to select shapes, I'd like to have the user do a long press gesture, then have a popup-type menu appear with different shapes they can choose. I don't see anything like this in xCode, though I'd assume there's something like that in iOS. I don't want the alert popup that shows up when you have low battery and notifications.
I've looked into using a UIPopoverController but I'm a bit confused about some of the other Stack Overflow questions I've read about it, and also about the documentation given by Apple.
I described the steps to achieve floating menu as shown in above image:
Create segue from the barButtonItem to the MenuViewCobtroller of type 'Present as Popover'
In the MenuViewController override the preferredContentSize as:
override var preferredContentSize : CGSize
{
get
{
return CGSize(width: 88 , height: 176)
}
set
{
super.preferredContentSize = newValue
}
}
In my case I am returning CGSize with width 100 and size 200. You can set these values so as to fit your floating menu content properly.
4. In the initial/source view controller, in the prepare(for segue: sender) method set self as popoverPresentationController delegate:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowMenuSegue" {
if let tvc = segue.destination as? MenuViewController
{
tvc.delegate = self
if let ppc = tvc.popoverPresentationController
{
ppc.delegate = self
}
}
}
}
The source view controller must comply to UIPopoverPresentationControllerDelegate and implement following method:
extension ViewController: UIPopoverPresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.none
}
}
That's it. You got the floating menu. Hopefully this will be useful.
I used Masture's method above and it worked for me (thank you!), but a couple of notes for other newbies like myself:
Make sure you put "ShowMenuSegue" (or whatever you choose) as the identifier for your segue in the Storyboard, and
I had to add
var delegate: MainViewController!
in the MenuViewController (with MainViewController being your source view controller) in order to get tvc.delegate = self to work
After you make a connection of that button with the viewController and popover as a segue you will need to prepare. Here is the following code in order to prepare for the popover segue.
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
if let identifier = segue.identifier
{
switch identifier
{
case History.SegueIdentifier:
if let tvc = segue.destinationViewController as? TextViewController
{
if let ppc = tvc.popoverPresentationController
{
ppc.delegate = self
}
tvc.text = "\(diagnosticHistory)"
}
default: break
}
}
}
Do keep in mind that if you have an iPhone the popover will take full screen, so you can fix that using this for let's say a text that takes some particular elements.
This will fix the popover to be exactly the size of the elements you have in your text.
#IBOutlet weak var textView: UITextView!
{
didSet
{
textView.text = text
}
}
var text : String = ""
{
didSet
{
textView?.text = text
}
}
override var preferredContentSize : CGSize
{
get
{
if textView != nil && presentingViewController != nil
{
return textView.sizeThatFits(presentingViewController!.view.bounds.size)
}
else
{
return super.preferredContentSize
}
}
set {super.preferredContentSize = newValue}
}
}
I have those 2 in different view controllers but I guess it will work. You will also need to implement UIPopoverPresentationControllerDelegate and
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.None
}
to your first viewController.

Resources