Let’s learn how to navigate a UIPageViewController using a UISegmentedControl in iOS. The UIPageViewController provides a way to display pages of content, where each page is managed by its own view controller. The UISegmentedControl displays segments and allows users to switch between them.
We’ll begin by setting up the UISegmentedControl and UIPageViewController, then move on to connecting them by programmatically navigating the UIPageViewController based on the selection in the UISegmentedControl.
The result will look something like this:

Making a Model for the Views
Our model consists of an array of titles and an array of colours. The titles will be used to display the segments of the UISegmentedControl and the colours will be used to display the background colour of each page in the UIPageViewController.
Here’s how it looks:
final class ViewController {
private enum Constants { // 1
static let titles = ["One", "Two", "Three", "Four"]
static let colors: [UIColor] = [.white, .yellow, .cyan, .orange]
}
private let viewControllers = zip(Constants.titles, Constants.colors).map(viewController) // 2
}
func viewController(_ text: String, _ backgroundColor: UIColor) -> UIViewController { // 3
let viewController = UIViewController()
viewController.view.backgroundColor = backgroundColor
let label = UILabel()
viewController.view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.text = text
label.centerXAnchor.constraint(equalTo: viewController.view.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: viewController.view.centerYAnchor).isActive = true
return viewController
}
- The
Constantsenum is a namespace. It stores our static model. - The
viewControllersproperty zips the label titles and colors together and runs the output through ourviewControllerfunction. - The
func viewControlleris a factory method to create simple view controllers with labels
Adding the UISegmentedControl
The UISegmentedControl is initialized using the titles defined in the model. The selected segment index is set to 0, which corresponds to the first title. The UISegmentedControl is added to the view hierarchy of the view controller and its constraints are set up.
private let segmentedControl = UISegmentedControl(items: Constants.titles)
private func setupSegmentedControl() {
view.addSubview(segmentedControl)
segmentedControl.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
segmentedControl.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
segmentedControl.leadingAnchor.constraint(equalTo: view.leadingAnchor),
segmentedControl.trailingAnchor.constraint(equalTo: view.trailingAnchor),
segmentedControl.heightAnchor.constraint(equalToConstant: 44.0)
])
segmentedControl.selectedSegmentIndex = 0
segmentedControl.addTarget(self, action: #selector(didSelect), for: .valueChanged)
}
The didSelect method is called when the value of the segmented control changes. This method is responsible for navigating the UIPageViewController to the corresponding page. We will implement it shortly.
Adding the UIPageViewController
The UIPageViewController is initialized using the scroll transition style and the horizontal navigation orientation. This will make it look like we are sliding from one view controller to another. The UIPageViewController is added as a child view controller and its constraints are set up. We also set the first view controller for the page view controller.
private func setupPageViewController() {
addChild(pageViewController)
view.addSubview(pageViewController.view)
pageViewController.didMove(toParent: self)
pageViewController.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
pageViewController.view.topAnchor.constraint(equalTo: segmentedControl.bottomAnchor),
pageViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
pageViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
pageViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
pageViewController.setViewControllers([viewControllers[0]],
direction: .forward,
animated: false)
}
Navigating using the UISegmentedControl tap and the UIPageViewController
Next, we implement the didSelect function like so:
@objc func didSelect() {
guard let visibleViewController = pageViewController.viewControllers?.first,
let currentIndex = viewControllers.firstIndex(of: visibleViewController),
segmentedControl.selectedSegmentIndex != currentIndex
else {
return
}
let newIndex = segmentedControl.selectedSegmentIndex
let direction: UIPageViewController.NavigationDirection = currentIndex < newIndex ? .forward : .reverse
pageViewController.setViewControllers([viewControllers[newIndex]],
direction: direction,
animated: true)
}
didSelect is called when the value of the segmented control changes.
The function starts by using a guard statement to unwrap and assign the first UIViewController object stored in the pageViewController.viewControllers array to the visibleViewController constant. It then finds the index of the current visibleViewController in the viewControllers array and assigns it to the currentIndex constant.
The guard statement also checks that the segmentedControl.selectedSegmentIndex is not equal to the currentIndex. If the statement evaluates to false, the function returns early and does nothing.
If the guard statement evaluates to true, the function continues by calculating the new index for the page view by using the segmentedControl.selectedSegmentIndex. The direction of the page view transition is then determined based on whether the currentIndex is less than the newIndex and set to .forward or .reverse accordingly.
Finally, the function updates the page view by calling pageViewController.setViewControllers and passing in the viewControllers[newIndex] as the new view controller to display, the calculated direction of the transition and animated set to true.
Putting it all together
All put together the final code should look like this.
import UIKit
final class ViewController: UIViewController {
private enum Constants {
static let titles = ["One", "Two", "Three", "Four"]
static let colors: [UIColor] = [.white, .yellow, .cyan, .orange]
}
private let segmentedControl = UISegmentedControl(items: Constants.titles)
private let pageViewController = UIPageViewController(transitionStyle: .scroll,
navigationOrientation: .horizontal)
private let viewControllers = zip(Constants.titles, Constants.colors).map(viewController)
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
setupSegmentedControl()
setupPageViewController()
}
private func setupSegmentedControl() {
view.addSubview(segmentedControl)
segmentedControl.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
segmentedControl.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
segmentedControl.leadingAnchor.constraint(equalTo: view.leadingAnchor),
segmentedControl.trailingAnchor.constraint(equalTo: view.trailingAnchor),
segmentedControl.heightAnchor.constraint(equalToConstant: 44.0)
])
segmentedControl.selectedSegmentIndex = 0
segmentedControl.addTarget(self, action: #selector(didSelect), for: .valueChanged)
}
private func setupPageViewController() {
addChild(pageViewController)
view.addSubview(pageViewController.view)
pageViewController.didMove(toParent: self)
pageViewController.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
pageViewController.view.topAnchor.constraint(equalTo: segmentedControl.bottomAnchor),
pageViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
pageViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
pageViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
pageViewController.setViewControllers([viewControllers[0]],
direction: .forward,
animated: false)
}
@objc func didSelect() {
guard let visibleViewController = pageViewController.viewControllers?.first,
let currentIndex = viewControllers.firstIndex(of: visibleViewController),
segmentedControl.selectedSegmentIndex != currentIndex
else {
return
}
let newIndex = segmentedControl.selectedSegmentIndex
let direction: UIPageViewController.NavigationDirection = currentIndex < newIndex ? .forward : .reverse
pageViewController.setViewControllers([viewControllers[newIndex]],
direction: direction,
animated: true)
}
}
func viewController(_ text: String, _ backgroundColor: UIColor) -> UIViewController {
let viewController = UIViewController()
viewController.view.backgroundColor = backgroundColor
let label = UILabel()
viewController.view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.text = text
label.centerXAnchor.constraint(equalTo: viewController.view.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: viewController.view.centerYAnchor).isActive = true
return viewController
}
And that should give you something like:

Conclusion
Using a UISegmentedControl or a similar tab-style view to handle navigation through separate view controllers is a common iOS pattern. Hopefully, this post gives a clean and simple example of how UISegmentedControl can navigate a UIPageViewController.