Tutorial: How to Create a Timed Paging Carousel Like Instagram
Learn how to create a page controller that automatically changes page with the new UIPageControlTimerProgress component.
• 4 min read
Availability
iOS 17
tvOS 17
This tutorial main focus will be the UIPageControlTimerProgress
class.
UIPageControlTimerProgress
has been introduced in iOS 17, and it is also available in tvOS 17.
This class allows us to create timed paging really easily and with a relatively small amount of code.
Visual
The new UIPageControlTimerProgress
will display a UIPageControl
as usual, but the current page indicator has a pill shape with a progress bar inside.
Once the progress bar is filled, the UIPageViewController
will transition to the next page.
It is the same kind of behavior that we can see in apps like Instagram, Snapchat, and WhatsApp stories.
We have seen this behavior for a long time, but it is nice that Apple decided to introduce it eventually.
SwiftUI interoperability
Currently, it is available only for UIKit, but you can wrap the UIPageViewController
in a UIViewControllerRepresentable
struct and use it in your SwiftUI project.
Hopefully, Apple will eventually add it to SwiftUI as a style modifier for the TabView
.
Now, let's see how to use it.
Page control timer implementation
UIPageControlTimerProgress
is not aUIPageControl
in itself, but it has to be assigned to the progress
property of the UIPageControl
component.
To do so, let's start by creating the ViewController
that will host the page controller.
class TimerPageViewController: UIPageViewController {
/// This property will hold the pages to show.
let pages: [PageVC]
}
The
PageVC
code and theUIPageViewController
implementation is here. I didn't include it in the tutorial since this isn't the main focus.
Then we create the UIPageControlTimerProgress
component and assign it to the UIPageControl
.
So, inside the viewDidLoad
method add:
let timerProgress = UIPageControlTimerProgress(preferredDuration: 2)
timerProgress.delegate = self // we will configure this later
timerProgress.resetsToInitialPageAfterEnd = true
let pageControl = UIPageControl()
pageControl.progress = timerProgress
pageControl.numberOfPages = pages.count
// Add the page control to the view controller.
view.addSubview(pageControl)
NSLayoutConstraint.activate([
pageControl.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
pageControl.centerXAnchor.constraint(equalTo: view.centerXAnchor),
])
UIPageControlTimerProgressDelegate
Then conform the TimerPageViewController
to UIPageControlTimerProgressDelegate
and implement the pageControlTimerProgress(_:shouldAdvanceToPage:)
method.
extension TimerPageViewController: UIPageControlTimerProgressDelegate {
func pageControlTimerProgress(_ progress: UIPageControlTimerProgress, shouldAdvanceToPage page: Int) -> Bool {
let nextPage = pages[page]
self.setViewControllers([nextPage], direction: .forward, animated: true)
return true
}
}
This method is called every time the current progress of the UIPageControlTimerProgress
reaches the end and asks you whether it should advance to the next page.
Here we get the next view controller and set it to the PageViewController in order to show it.
Finally, we return true
to tell the control that it can advance to the next page.
Start the timer process page controller
The UIPageControlTimerProgress
won't start automatically, but we need to do so by calling resumeTimer()
.
I have found that the most useful place to start the timer is inside viewDidAppear
, since inside viewDidLoad
it won't have any effect. Maybe this is a bug that will be fixed in the public release.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
timerProgress.resumeTimer()
}
Handle user interaction
As it is now, if the user swipes to the next page, the progress control won't go to the next indicator until it has finished with the current one.
To fix this behavior, we need to implement a method from the UIPageViewControllerDelegate
protocol, called pageViewController(_:willTransitionTo:)
.
This method is called as soon as the user starts scrolling.
class TimerPageViewController {
// 1.
var suspensionTimer: Timer?
}
extension TimerPageViewController: UIPageViewControllerDelegate {
func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
// 2.
timerProgress.pauseTimer()
suspensionTimer?.invalidate()
// 3.
suspensionTimer = Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { _ in
self.timerProgress.resumeTimer()
}
}
}
Here we:
- Add a variable that will hold the timer that handles user interactions
- Pause the page control timer progress as soon as the user starts swiping
- Start a timer for 2 seconds that will resume the progress page controller in case the user stops swiping through pages
This way, if the user swipes a page, the page control timer will pause and resume after 2 seconds.
And we are done with the new UIPageControlTimerProgress
component.
Preview
To preview what we have done, you can use the new #Preview
macro available in Swift 5.9.
#Preview {
TimerPageViewController(pages: [PageVC(index: 0, image: "bergamo"),
PageVC(index: 1, image: "venice"),
PageVC(index: 2, image: "florence")])
}
Here is the result
Complete code
Here you can find the complete code.
Conclusion
This implementation is not written anywhere else since it is a new component and Apple didn't provide any sample code, as far as I know.
So, if you have any suggestions on how to improve it, let me know here, I will really appreciate it.
UIPageControlTimerProgress
is a really cool feature that Apple introduced in iOS 17.
It makes it clearer to the user that the slideshow is animated and how long each page will last.
If you have any question about this article, feel free to email me or tweet me @franceleonidev and share your opinion.
Thank you for reading and see you in the next article!
Share this article