In my previous article, I wrote about additional animation and showed a few cases of how the app can be improved. Thus I love animations,
I try to use it a lot. In this article, I would like to show how we can do fluid page indicator with progress using SwiftUI.
Of cause - understand from the name what I mean it’s quite hard because name selection it’s a hard topic and sometimes I spend hours to find out a good name (rare cases but it happens)
Here is a small demo:
problem
Why do I need such a kind of UI element? On design for onboarding flow Customer want to have auto-scroll with certain intervals and visual feedback for it. At the same moment, onboarding should be controlled with any manual actions, that can interrupt the process.
To solve this, as in most cases, I used the divide and conquer strategy.
We can split easily task into smaller chunks and solve them one by one, combining results from them will lead to a solved task.
solution
content
Of cause for onboarding, we have some views that can be scrolled. This view contains some info. This isn’t a problem - we can just use TabView with .page style:
PageTabViewStyle(indexDisplayMode: .never) makes sure that we hide default indicator of the UIPageViewController
To make things a bit interesting, we can add some simple effects to each page, that will be triggered on page selection:
This code will make our content appears from a hidden state with some nice scale effect.
I use TCA architecture, so viewStore - it’s just a component from this architecture that contains a few layers for proper work of view and data isolation. Visit the link above for more.
In your case u can just use @State var selectedItemPage and viewStore.binding equivalent to $selectedItemPage. Thus TCA is not the main topic of this article, I skip any explanation related to this.
The result:
page indicator
interface
Creating UI for pageIndicator using SwiftUI is an easy task - we can use Capsule (or a few of them) and mask, which will change according to progress. This will make an effect on the progressive changes.
We need 2 layers of the same structure - one for showing inactive state - another to show already completed state.
The code for this could be like this:
logic
Now the most interesting part - page indicator with progress. How it can be done? we can of cause think about various GeometryEffects and other stuff like this, but we also have user interaction.
If u interested in GeometryEffects - check this post
Here is good to know how user events are handled in iOS, how RunLoop works. Knowing that we can use this information to properly configure scrollProgress propagation.
I wrote an article about RunLoop if u would like to read more about it.
So, the idea - is to create progress propagation that depends on timePerPage and allow a user to manually scroll the tutorial, storing progress into currentIndex.
currentIndex can be easily obtained from tabView or calculated based on offset and pageWidth (here GeometryReader can help a lot).
Propagation can be done using Timer. We should use .default mode for RunLoop instead of .common pseudo-mode. This will auto stop timer on any user activities such as scroll - exactly what we need here.
On every tick we should update progress and selected page:
Our view with progress indicator will listen to changes from the progress propagator and user interaction. We can use @Binding here to make propagation between all 3 components even more easier (here is an example of the power of SwiftUI and Combine).
I wrote an article about Binding if u want to know more.
result
Putting this all together we can now add our components to the TabView as an overlay:
I gave it the name FluidProgressView - because it moves as a fluid in the tube. and show the progress of action :]