Popover with UIKit on iOS

Learn how to show Popovers on iOS with this practical guide, detailed step-by-step examples, and best practices for easy implementation

Ruslan Dzhafarov
7 min readMay 28, 2024

Popovers are a convenient way to display additional information or functionality in your iOS app without taking up too much screen space. In this guide, we’ll explore how to implement popovers in your iOS app using Swift, step by step.

When to use Popovers?

Popovers were introduced in iOS 8 as a way to present content in a transient, non-modal fashion. They are commonly used in iPad apps but can also enhance the user experience in iPhone apps. Popovers are ideal for displaying contextual information, options, or settings related to the current context or user interaction.

Implementing Popovers

Before we delve into the implementation details, it’s worth noting that popovers were introduced in iOS 8 as part of Apple’s ongoing efforts to enhance view controller management. For a deeper understanding of popover presentations and other advancements in view controllers introduced in iOS 8, I highly recommend watching the official Apple session WWDC 2014 session 214 “View Controller Advancement in iOS 8” (36:30).

WWDC 2014 session 214 “View Controller Advancement in iOS 8” (36:30)

In this tutorial, we’ll walk through creating and displaying a popover in an iOS application using UIKit. We’ll build a simple example where a button triggers a popover presentation with custom content. We’ll also cover how to handle the popover presentation style to ensure it behaves correctly on different devices.

To implement the popover, we require two essential components: the content view controller, responsible for displaying the popover’s content, and the presentation controller, which serves as the anchor for presenting the popover atop it.

Now, let’s get started with the implementation.

Step 1: Create a New Xcode Project

First, open Xcode and create a new project using the iOS “App” template. Name your project and choose Swift as the programming language. Now you have a basic ViewController class created by the project template.

Step 2: Add Button to Show Popover

We’ll start by adding a button to our ViewController that will trigger the popover presentation.

import UIKit

class ViewController: UIViewController {

private lazy var button: UIButton = {
let button = UIButton(configuration: .filled())
button.setTitle("Show popover", for: .normal)
button.addTarget(self,
action: #selector(showPopover(_:)),
for: .touchUpInside)

return button
}()

override func viewDidLoad() {
super.viewDidLoad()

view.addSubview(button)
}

override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()

button.frame = .init(x: view.bounds.width/2 - 50,
y: view.bounds.height/2 - 25,
width: 100,
height: 50)
}

@objc func showPopover(_ sender: UIButton) {
// To be implemented in Step 4
}
}

We create a button using a lazy property. This button is configured with a filled style and an action that triggers the showPopover method when tapped. In viewDidLoad, we add the button to the main view. In viewDidLayoutSubviews, we set the button's frame, positioning it on the screen.

Step 3: Implement Popover View Controller

Next, we create a simple view controller that will be presented as a popover.

import UIKit

class PopoverViewController: UIViewController {

private lazy var label: UILabel = {
let label = UILabel()
label.text = "Hello, Popover!"
label.textAlignment = .center
return label
}()

override func viewDidLoad() {
super.viewDidLoad()

view.addSubview(label)
}

override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()

label.frame = view.bounds
}
}

This is a simple view controller with a centered label displaying “Hello, Popover!”.

Step 4: Implementing the Popover Presentation

Now, we implement the method that will present the popover when the button is tapped.

import UIKit

class ViewController: UIViewController {

private lazy var button: UIButton = {
let button = UIButton(configuration: .filled())
button.setTitle("Show popover", for: .normal)
button.addTarget(self,
action: #selector(showPopover(_:)),
for: .touchUpInside)

return button
}()

override func viewDidLoad() {
super.viewDidLoad()

view.addSubview(button)
}

override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()

button.frame = .init(x: view.bounds.width/2 - 50,
y: view.bounds.height/2 - 25,
width: 100,
height: 50)
}

private lazy var popoverViewController = PopoverViewController()

@objc func showPopover(_ sender: UIButton) {
// 1. Set the size of the popover content
popoverViewController.preferredContentSize = .init(width: 200, height: 100)
// 2. Set the presentation style to .popover
popoverViewController.modalPresentationStyle = .popover

// 3. Configure the popover presentation
let popoverPresentationController = popoverViewController.popoverPresentationController
// Set the permitted arrow directions
popoverPresentationController?.permittedArrowDirections = .up
// Set the source rect (the bounds of the button)
popoverPresentationController?.sourceRect = sender.bounds
// Set the source view (the button)
popoverPresentationController?.sourceView = sender
// 4. Set the view controller as the delegate to manage the popover's behavior.
popoverPresentationController?.delegate = self

// 5. Present the popover view controller
present(popoverViewController, animated: true, completion: nil)
}
}

Firstly, we need to set the size of our popover by assigning the preferredContentSize property of the popoverViewController. By setting this property, we ensure that the popover appears with the correct dimensions.

Next, we need to define the presentation style of our popover view controller. We set the modalPresentationStyle property of the popoverViewController to .popover, indicating that it should be presented modally as a popover. This ensures that the popover view controller appears within a popover interface when presented to the user. This specifies that the view controller should be presented modally as a popover. By setting this style, iOS knows to present the view controller in a popover-style interface, which includes features such as rounded corners and arrow indicators pointing to the anchor point. This line ensures that the content view controller is presented as a popover rather than in other modal presentation styles.

After configuring the size and presentation style of our content view controller, the next step is to access its popoverPresentationController property. This property provides access to the UIPopoverPresentationController instance associated with the content view controller, allowing us to further customize the appearance and behavior of the popover.

After configuring the appearance of the popover, we need to set the view controller as the delegate to manage the popover’s behavior.

Step 5: Handling Adaptive Presentation Styles

After customizing the popover presentation controller, we need to implement the delegate methods of UIPopoverPresentationControllerDelegate to ensure the popover displays correctly on all devices.

// MARK: - UIPopoverPresentationControllerDelegate

extension ViewController: UIPopoverPresentationControllerDelegate {

func adaptivePresentationStyle(
for controller: UIPresentationController,
traitCollection: UITraitCollection
) -> UIModalPresentationStyle {
// Return no adaptive presentation style,
// use default presentation behaviour
return .none
}
}

This method ensures that the popover does not adapt to a different presentation style on compact-width environments (e.g., iPhones in portrait mode). By returning .none, we maintain the popover style regardless of the device's size class.

Step 6. Present Popover

After customizing the popover presentation controller and implementing the delegate methods, we can proceed to present the popover.

Firstly, we need to check if there is any popover currently presented. If presentedViewController is not nil, it means that there is an existing view controller presented. Attempting to present a view controller while another presentation is in progress will indeed result in an error. This is why it’s crucial to dismiss any existing presented view controller before presenting a new one, especially in scenarios involving popovers or other modal presentations. Dismissing the existing view controller ensures that only one presentation is active at a time, preventing conflicts and errors.

import UIKit

class ViewController: UIViewController {

private lazy var button: UIButton = {
let button = UIButton(configuration: .filled())
button.setTitle("Show popover", for: .normal)
button.addTarget(self,
action: #selector(showPopover(_:)),
for: .touchUpInside)

return button
}()

override func viewDidLoad() {
super.viewDidLoad()

view.addSubview(button)
}

override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()

button.frame = .init(x: view.bounds.width/2 - 50,
y: view.bounds.height/2 - 25,
width: 100,
height: 50)
}

private lazy var popoverViewController = PopoverViewController()

@objc func showPopover(_ sender: UIButton) {
// 1. Set the size of the popover content.
popoverViewController.preferredContentSize = .init(width: 200, height: 100)
// 2. Set the presentation style to .popover
popoverViewController.modalPresentationStyle = .popover

// 3. Configure the popover presentation
let popoverPresentationController = popoverViewController.popoverPresentationController
// Set the permitted arrow directions
popoverPresentationController?.permittedArrowDirections = .up
// Set the source rect (the bounds of the button)
popoverPresentationController?.sourceRect = sender.bounds
// Set the source view (the button)
popoverPresentationController?.sourceView = sender
// 4. Set the view controller as the delegate to manage the popover's behavior.
popoverPresentationController?.delegate = self

// 5. Present the popover view controller
if let presentedViewController {
// Dismiss any existing popover if it exists before presenting the new one
presentedViewController.dismiss(animated: true) { [weak self] in
self?.present(popoverViewController, animated: true)
}
} else {
// Present the view controller if no existing popover is presented
present(popoverViewController, animated: true)
}
}
}

If there is no existing view controllers presented, we proceed to present the new popover directly using present(popoverViewController, animated: animated). This is the case when no popover is currently visible, and we can present the popover without dismissing any existing popover.

Conclusion

In this tutorial, we’ve created a basic example of how to present a popover in an iOS application using UIKit. We’ve covered the setup of the main view controller, the configuration of the popover presentation, and ensuring proper behavior across different devices. This technique can be expanded to create more complex and interactive popovers as needed.

Thank you for reading until the end. If you have any questions or feedback, feel free to leave a comment below.

If you enjoyed reading this article, please press the clap button 👏 . Your support encourages me to share more of my experiences and write more articles like this. Follow me here on medium for more updates!

Happy coding! 😊👨‍💻👩‍💻

More from Ruslan Dzhafarov

Advanced UIKit Techniques

7 stories

Design Patterns in Swift

9 stories

Advanced Swift Programming

3 stories

--

--

Ruslan Dzhafarov

Senior iOS Developer since 2013. Sharing expert insights, best practices, and practical solutions for common development challenges