In the project shown below there is an InitialViewController
that has a single button labeled "Show Popover". When that button is tapped the app is supposed to present the second view controller (PopoverViewController
) as a popover. The second view controller just has a label saying "Popover!".
This works fine if the InitialViewController
takes care of instantiating PopoverViewController
, retrieving the popoverPresentationController
and then setting the popoverPresentationController's delegate
to itself (to InitialViewController
). You can see the result, below:
For maximum reusability, however, it would be great if the InitialViewController
did not need to know anything about how the presentation controller is delegated. I think it should be possible for the PopoverViewController
to set itself as the popoverPresentationController's delegate
. I've tried this in either the viewDidLoad
or the viewWillAppear
functions of the PopoverViewController
. However, the PopoverViewController
is presented modally in both cases, as shown below:
All the code is contained in just the InitialViewController
and the PopoverViewController
. The code used in the failing version of the InitialViewController
is shown below:
import UIKit
// MARK: - UIViewController subclass
class InitialViewController: UIViewController {
struct Lets {
static let storyboardName = "Main"
static let popoverStoryboardID = "Popover View Controller"
}
@IBAction func showPopoverButton(_ sender: UIButton) {
// instantiate & present the popover view controller
let storyboard = UIStoryboard(name: Lets.storyboardName,
bundle: nil )
let popoverViewController =
storyboard.instantiateViewController(withIdentifier: Lets.popoverStoryboardID )
popoverViewController.modalPresentationStyle = .popover
guard let popoverPresenter = popoverViewController.popoverPresentationController
else {
fatalError( "could not retrieve a pointer to the 'popoverPresentationController' property of popoverViewController")
}
present(popoverViewController,
animated: true,
completion: nil )
// Retrieve and configure UIPopoverPresentationController
// after presentation (per
// https://developer.apple.com/documentation/uikit/uipopoverpresentationcontroller)
popoverPresenter.permittedArrowDirections = .any
let button = sender
popoverPresenter.sourceView = button
popoverPresenter.sourceRect = button.bounds
}
}
The code in the failing PopoverViewController
is shown below:
import UIKit
// MARK: - main UIViewController subclass
class PopoverViewController: UIViewController {
// MARK: API
var factorForMarginsAroundButton: CGFloat = 1.2
// MARK: outlets and actions
@IBOutlet weak var popoverLabel: UILabel!
// MARK: lifecycle
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear( animated )
// set the preferred size for popover presentations
let labelSize =
popoverLabel.systemLayoutSizeFitting( UILayoutFittingCompressedSize )
let labelWithMargins =
CGSize(width: labelSize.width * factorForMarginsAroundButton,
height: labelSize.height * factorForMarginsAroundButton )
preferredContentSize = labelWithMargins
// set the delegate for the popoverPresentationController to self
popoverPresentationController?.delegate = self
}
}
// MARK: - UIPopoverPresentationControllerDelegate
// (inherits from protocol UIAdaptivePresentationControllerDelegate)
extension PopoverViewController: UIPopoverPresentationControllerDelegate
{
func adaptivePresentationStyle(for controller: UIPresentationController,
traitCollection: UITraitCollection)
-> UIModalPresentationStyle{
return .none
}
}
Is it possible for a view controller that is being presented as a popover to be the delegate for its own popoverPresentationController
?
I'm using Xcode 8.0, Swift 3.1 and the target is iOS 10.0
It's certainly possible. You're dealing with a timing issue. You need to set the delegate before viewWillAppear
. Unfortunately, there is no convenient view lifecycle function to insert the assignment into, so I did this instead.
In your PopoverViewController
class, assign the delegate in an overriden getter. You can make the assignment conditional if you'd like. This creates a permanent relationship, so other code code never "override" the delegate by assigning it.
override var popoverPresentationController: UIPopoverPresentationController? {
get {
let ppc = super.popoverPresentationController
ppc?.delegate = self
return ppc
}
}