Present a popover from an arbitrary anchor point in Swift - ios

I know how to present a popover from a bar button item as is described in this answer (for both iPhone and iPad).
I would like to add a popover for an arbitrary anchor point. The other SO answers that I saw were for bar button items or in Objective-C.
I just learned how to do this, so I am adding my own answer below.

Updated for Swift 3
In the storyboard, add a view controller that you would like to be the popover. Set the Storyboard ID to be "popoverId".
Also add a button to your main view controller and hook up the IBAction to the following code.
import UIKit
class ViewController: UIViewController, UIPopoverPresentationControllerDelegate {
#IBAction func buttonTap(sender: UIButton) {
// get a reference to the view controller for the popover
let popController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "popoverId")
// set the presentation style
popController.modalPresentationStyle = UIModalPresentationStyle.popover
// set up the popover presentation controller
popController.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.up
popController.popoverPresentationController?.delegate = self
popController.popoverPresentationController?.sourceView = sender // button
popController.popoverPresentationController?.sourceRect = sender.bounds
// present the popover
self.present(popController, animated: true, completion: nil)
}
// UIPopoverPresentationControllerDelegate method
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
// Force popover style
return UIModalPresentationStyle.none
}
}
Setting the sourceView and sourceRect is what allows you to choose an arbitrary point to display the popover.
That's it. Now it should like something like this when the button is tapped.
Thanks to this article for help.

Solution for Swift 3.1 :
Add to your ViewController UIPopoverPresentationControllerDelegate delegate :
class OriginalViewController: UIViewController, UIPopoverPresentationControllerDelegate
Add a button to your ViewController and on tap on your button, call this code :
let controller = MyPopViewController()
controller.modalPresentationStyle = UIModalPresentationStyle.popover
let popController = controller.popoverPresentationController
popController?.permittedArrowDirections = .any
popController?.delegate = self
popController?.sourceRect = (self.myButton?.bounds)!
popController?.sourceView = self.myButton
self.present(controller, animated: true, completion: nil)

Update to func syntax above:
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentatinStyle {
return .none
}
For some reason the old syntax is still allowed but is not active and will not implement popup or anchor correctly.

Related

Present UIViewController as a popover [duplicate]

I am trying to make a popover menu with the following code:
import UIKit
class BeobachtungViewController: UIViewController, UIPopoverPresentationControllerDelegate {
#IBAction func addClicked(_ sender: AnyObject) {
// get a reference to the view controller for the popover
let popController = UIStoryboard(name: "Personenakte", bundle: nil).instantiateViewController(withIdentifier: "popoverId")
// set the presentation style
popController.modalPresentationStyle = UIModalPresentationStyle.popover
// set up the popover presentation controller
popController.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.up
popController.popoverPresentationController?.delegate = self
popController.popoverPresentationController?.sourceView = sender as! UIView // button
popController.popoverPresentationController?.sourceRect = sender.bounds
// present the popover
self.present(popController, animated: true, completion: nil)
}
// UIPopoverPresentationControllerDelegate method
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
// Force popover style
return UIModalPresentationStyle.none
}
}
This is working on iPad, but, on an iPhone, the popup takes the whole iPhone screen. I just want a small window with an arrow. I found several tutorials but none worked for me.
Change your delegate method to:
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
// return UIModalPresentationStyle.FullScreen
return UIModalPresentationStyle.none
}

how to use popover controller in iPhone

I couldn't show popover controller as popover in iPhone whereas it works very well with iPad.
Any ideas on how to do that in iPhone.
As far as I have searched I couldn't find any.
anyways to make the popover appear in iphone as it is in iPad is appreciated !
Set yourself as the popover view controller's delegate before presenting it, and implement the delegate method adaptivePresentationStyle(for:traitCollection:) to return .none. This will cause the popover to stop adapting on iPhone as a fullscreen presented view controller and turn into an actual popover just like on the iPad.
This is a complete working example that presents the popover in response to a button tap:
class ViewController: UIViewController {
#IBAction func doButton(_ sender: Any) {
let vc = MyPopoverViewController()
vc.preferredContentSize = CGSize(400,500)
vc.modalPresentationStyle = .popover
if let pres = vc.presentationController {
pres.delegate = self
}
self.present(vc, animated: true)
if let pop = vc.popoverPresentationController {
pop.sourceView = (sender as! UIView)
pop.sourceRect = (sender as! UIView).bounds
}
}
}
extension ViewController : UIPopoverPresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
return .none
}
}

Popover in swift 3 on iphone ios

I am trying to make a popover menu with the following code:
import UIKit
class BeobachtungViewController: UIViewController, UIPopoverPresentationControllerDelegate {
#IBAction func addClicked(_ sender: AnyObject) {
// get a reference to the view controller for the popover
let popController = UIStoryboard(name: "Personenakte", bundle: nil).instantiateViewController(withIdentifier: "popoverId")
// set the presentation style
popController.modalPresentationStyle = UIModalPresentationStyle.popover
// set up the popover presentation controller
popController.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.up
popController.popoverPresentationController?.delegate = self
popController.popoverPresentationController?.sourceView = sender as! UIView // button
popController.popoverPresentationController?.sourceRect = sender.bounds
// present the popover
self.present(popController, animated: true, completion: nil)
}
// UIPopoverPresentationControllerDelegate method
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
// Force popover style
return UIModalPresentationStyle.none
}
}
This is working on iPad, but, on an iPhone, the popup takes the whole iPhone screen. I just want a small window with an arrow. I found several tutorials but none worked for me.
Change your delegate method to:
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
// return UIModalPresentationStyle.FullScreen
return UIModalPresentationStyle.none
}

Why isn't presentationController:viewControllerForAdaptivePresentationStyle: being called?

I'm trying to programmatically present a view via an adaptive popover (e.g. in a popover on an iPad, full screen on an iPhone). In order to be able to dismiss the presented view controller on the iPhone, I've tried wrapping it in a navigation controller as in https://stackoverflow.com/a/29956631/5061277 or the nice example here: https://github.com/shinobicontrols/iOS8-day-by-day/tree/master/21-alerts-and-popovers/AppAlert, which looks like:
import UIKit
class ViewController: UIViewController, UIPopoverPresentationControllerDelegate {
#IBAction func handlePopoverPressed(sender: UIView) {
let popoverVC = storyboard?.instantiateViewControllerWithIdentifier("codePopover") as! UIViewController
popoverVC.modalPresentationStyle = .Popover
// Present it before configuring it
presentViewController(popoverVC, animated: true, completion: nil)
// Now the popoverPresentationController has been created
if let popoverController = popoverVC.popoverPresentationController {
popoverController.sourceView = sender
popoverController.sourceRect = sender.bounds
popoverController.permittedArrowDirections = .Any
popoverController.delegate = self
}
}
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
// This line _IS_ reached in the debugger
NSLog("Delagate asked for presentation style");
return .FullScreen
}
func presentationController(controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
// This line _IS_NOT_ reached in the debugger
NSLog("Delegate asked for view controller");
return UINavigationController(rootViewController: controller.presentedViewController)
}
}
While the adaptivePresentationStyleForPresentationController delegate method is called, the presentationController viewControllerForAdaptivePresentationStyle delegate method is not called. As a result there is no way to dismiss the presented controller on an iPhone.
I've tried XCode 6.4 and 7.0b2 on iOS 8.1 through 8.4, both in the simulator and on a device, and in no case is my delegate asked for viewControllerForAdaptivePresentationStyle. Why? Is there a build setting I should be looking at, or could having XCode 7 installed be changing something? This exact code is presented as working in the links above.
You need to present it after configuring it. Alternatively use a segue to make it much easier.
You need to add this method:
-(UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller traitCollection:(UITraitCollection *)traitCollection{
return UIModalPresentationOverFullScreen;
}
The trick is that you must set the
presentation controller’s delegate before calling present(_:animated:completion:);
otherwise, the adaptive presentation delegate methods won’t be called:
let vc = MyViewController()
vc.modalPresentationStyle = .popover
if let pop = vc.popoverPresentationController {
pop.delegate = self // *
}
self.present(vc, animated: true)

Creating a popover from a UIButton in Swift

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()
}
}

Resources