Popover on iPhone (Swift 3) from Programmatically Created Button - ios

I am trying to have a popover appear on an iPhone when I click on a programmatically created button. However, I am instead finding that the popover is taking over the entire screen. I've coded the following thus far in the popover view controller's class:
import UIKit
class GroupSetsPopoverController: UIViewController, UIPopoverPresentationControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.preferredContentSize = CGSize(width: 375, height: 162)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "infoButtonSegue" {
let popoverViewController = segue.destination as! GroupSetsPopoverController
let pvc = popoverViewController.popoverPresentationController
pvc?.delegate = self
//popoverViewController.modalPresentationStyle = UIModalPresentationStyle.popover
//popoverViewController.popoverPresentationController!.delegate = self
}
}
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
return .none
}
}
Any guidance would be appreciated. I've tried to follow various tutorials and posts here on Stack Overflow, to no avail.

You can check out the answer and library for your references. If the GroupSetsPopoverController is your popover viewcontroller, you can do something like this:
init(for sender: UIView)) {
super.init(nibName: nil, bundle: nil)
modalPresentationStyle = .popover
guard let pop = popoverPresentationController else { return }
pop.sourceView = sender
pop.sourceRect = sender.bounds
pop.delegate = self
}

Related

In Swiftui How do I prevent Popover switching to full screen on smaller devices

I know that the normal behavior on iPad is to show a popover view while on iPhone it switches to a full screen, but I don't want the full screen on the smaller devices. Is there a way to prevent this?
In UIKit we could override the adaptivepresentationstyle func like this(from https://stackoverflow.com/a/50428131/412154)
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
}
}
wondering if anyone found something similar to override for swiftui
thanks for the help!
Here is another solution that subclasses uihostingcontroller but I don’t know how I would incorporate it into my swiftui views since I’m trying to achieve this for some deeper views
sampleproject
Heres and alternative that worked better for me
resizable popover

swift4: can't pass value to previous view controller from current popover

I'm using swift4, i present view controller modally as popover and want to pass data back to previous view controller when dismissing current popover, this is the first View controller :
import UIKit
class SearchResultViewController: UIViewController, UIPopoverPresentationControllerDelegate, PopoupDelegate {
#IBOutlet var errorLable: UILabel!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "pop") {
let dest = segue.destination
if let pop = dest.popoverPresentationController {
dest.preferredContentSize = CGSize(width: self.view.frame.size.height - 20, height: 500)
pop.delegate = self
}
}
}
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
func popupValueSelected(value: String) {
print("value: ", value)
self.errorLable.text = value
}
}
and this the popover code:
import UIKit
class SearchPopoverViewController: UIViewController {
#IBOutlet var cityLable: UILabel!
var delegate: PopoupDelegate?
#IBAction func closeButtonAction(_ sender: Any) {
self.delegate?.popupValueSelected(value: "hiiiiii from the other side")
self.dismiss(animated: false, completion: nil)
}
}
and this is the PopoupDelegate
import Foundation
protocol PopoupDelegate {
func popupValueSelected(value: String)
}
when i click in the popover close button, the popover should dismiss and the errorLable in SearchResultViewController should change it's text to new passed text, but none happens, only the popover dismiss,
what should i do?
You delegate function is wrong, function inside function in SearchResultViewController:
func popupValueSelected(value: String) {
func popupValueSelected(value: String) {
print("value: ", value)
self.errorLable.text = value
}
}
It should be:
func popupValueSelected(value: String) {
print("value: ", value)
self.errorLable.text = value
}
After editing the question :
You never assigned any instance to the delegate variable. Try to change delegate variable name to popoUpDelegate.
pop.delegate is for UIPopoverPresentationControllerDelegate not your delegate. This function should be like this :
if (segue.identifier == "pop") {
if let dest = segue.destination.popoverPresentationController as? SearchPopoverViewController {
dest.preferredContentSize = CGSize(width: self.view.frame.size.height - 20, height: 500)
dest.popoUpDelegate = self
}
}
}
And your SearchPopoverViewController look like this :
class SearchPopoverViewController: UIViewController {
#IBOutlet var cityLable: UILabel!
var popoUpDelegate: PopoupDelegate?
#IBAction func closeButtonAction(_ sender: Any) {
self.popoUpDelegate?.popupValueSelected(value: "hiiiiii from the other side")
self.dismiss(animated: false, completion: nil)
}
}
you have to define delegate to SearchResultViewController.
var PopoupDelegate : PopoupDelegate?
and before pop viewcontroller assign delegate to popoupDelegate
PopoupDelegate.delegate = self
and you have right function inside function. first make it correct.
Try this. Hope this will work. and let me know in case of any concern
When you are presenting SearchPopoverViewController from SearchResultViewController you need to set delegate.
Using segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "segueToPopOver") {
let dest = segue.destination
if let vc = dest.popoverPresentationController as? SearchPopoverViewController {
//set other properties
vc.delegate = self
}
}
}
Using UINavigationController
let vc = SearchPopoverViewController() // initialise view controller
vc.delegate = self
present(vc, animated: true, completion: nil)
Respond to delegate
#IBAction func closeButtonAction(_ sender: Any) {
self.delegate?.popupValueSelected(value: "hiiiiii from the other side")
self.dismiss(animated: false, completion: nil)
}
In SearchResultViewController implement delegate method
func popupValueSelected(value: String) {
print("value: ", value)
self.errorLable.text = value
}
I think you have a fundamental misunderstanding as to how segues and protocols work for view controllers. Frankly, I would recommended reviewing the topic in Apple's App Development with Swift. This is written clearly with exercises to help you understand the basics of passing information between view controllers. I have been where you are, and while the suggestions and comments have been correct, you don't seem to have the foundation to understand what they are saying. Good luck!
I used popover dismiss delegate instead of protocol:
this code solved my problem:
in the SearchResultViewController:
func popoverPresentationControllerDidDismissPopover(_ popoverPresentationController: UIPopoverPresentationController) {
if let searchPopoverViewController =
popoverPresentationController.presentedViewController as? SearchPopoverViewController{
self.passed_city_id = searchPopoverViewController.selected_city_id
}
}
in the SearchPopoverViewController
#IBAction func searchButtonAction(_ sender: Any) {
self.selected_city_id = 10
popoverPresentationController?.delegate?.popoverPresentationControllerDidDismissPopover?(popoverPresentationController!)
self.dismiss(animated: true, completion: nil)
}
you can use "Unwind segue". pls refer
https://medium.com/#mimicatcodes/create-unwind-segues-in-swift-3-8793f7d23c6f

Swift Popover Anchor

I'm trying to show a popover over an image and I'm failing trying to point the anchor point to my image border (the image is the info icon).
Here is my code:
The button action:
#IBAction func infoTapped(_ sender: AnyObject) {
self.performSegue(withIdentifier: "InfoPopOver", sender: nil)
}
And the prepare for segue:
extension AddExpenseViewController: UIPopoverPresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.none
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "InfoPopOver" {
if let vctr = segue.destination as? MyPopOverViewController {
vctr.modalPresentationStyle = .popover
vctr.popoverPresentationController?.delegate = self
vctr.popoverPresentationController?.sourceView = self.view
vctr.popoverPresentationController?.sourceRect = infoIcon.frame
}
}
}
}
class MyPopOverViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.preferredContentSize = CGSize(width: 185, height: 80)
}
}
Here a couple of screenshots:
Main View
Popover shown
The best I got was setting the coordinates manually, but it doesn't fit neither every screen type...
I would change it to this:
vctr.popoverPresentationController?.sourceView = infoIcon.superview
vctr.popoverPresentationController?.sourceRect = infoIcon.frame
That way you can be sure that you are using the superview of the infoIcon and the frame will be in the correct position.

Can we handle the error "Cannot convert the value of type BOOL to expected argument UIiew"

I am tired of trying to PopOver the view controller and searched every where, tried myself also.This issue has again arrived and i am confused what to do next
func showPopover(base: UIView)
{
let storyboard : UIStoryboard = UIStoryboard(name: "Messaging", bundle: nil)
if let viewController = storyboard.instantiateViewControllerWithIdentifier("PreferencesViewController") as?PreferencesViewController
{
let navController = UINavigationController(rootViewController: viewController)
navController.modalPresentationStyle = .Popover
if let pctrl = navController.popoverPresentationController
{
pctrl.delegate = self
pctrl.sourceView = base
pctrl.sourceRect = base.bounds
self.presentViewController(navController, animated: true, completion: nil)
}
}
}
I am calling this method in any one of the actions clicked from UIBarButtons
func optionChoosed(hello:Bool)
{
if (hello)
{
self.showPopover(hello)
}
}
it says Cannot convert the value of type BOOL to expected argument UIiew.. can we fix this or am i going wrong direction.
class SHNewStylesViewController: UIViewController, UIPopoverPresentationControllerDelegate {
var popover: UIPopoverPresentationController? = nil
//MARK: - View life cycle
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
//MARK: - IBActions
#IBAction func showPopover(sender: AnyObject) {
let genderViewController = storyboard!.instantiateViewControllerWithIdentifier("ViewControllerTwoIdentifier") as! ViewControllerTwo
genderViewController.modalPresentationStyle = UIModalPresentationStyle.Popover
genderViewController.preferredContentSize = CGSize(width: 200.0, height: 400.0) // To change the popover size
popover = genderViewController.popoverPresentationController!
popover!.barButtonItem = sender as? UIBarButtonItem
popover!.delegate = self
presentViewController(genderViewController, animated: true, completion:nil)
}
//MARK: - Popover Presentation Controller Delegate methods
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.None
}
}
In my case showPopover is a IBAction of my bar button item. You can you that code inside the showPopover method wherever you want.
Thanks:)

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