I am new to iOS development (but not to programming in general), and while the dream of my own program on pause due to delays in payment on developer account, I have time to share interesting solution to the problem that occurred during developing. Especially since this element is quite common and often used. But I hope that this method can be useful to someone even for other tasks or inspire improvement, so I decided to share a detailed explanation of how the method works.
In the process of writing own app for iOS, I was need to create a custom Page Control in the form of circles showing the current position in the list of cards.
To begin with, I will explain the essence of the problem. The simplest version of Page Control looks like the first animation below:
The problem is that we have a width limit, and at some point the number of elements reaches such a level that the overall width of the page control will go beyond the specified limits. Since I did not find a ready solution which I liked, I drew several options and chose the ones that I think would be acceptable. Rejecting all the options with a space elements like three dots inside or conversion to a slider that the user clearly and quickly realized where the position is, allocated as follows:
- Do all items less size to meet the width limit (but current element is not well seen at the small sizes).
- Leave one element of the original size, and equally reduce all the others.
- Make a smooth transition from the original size of the element to a smaller one through the specified number of steps.
I chose option #3, because it gives the most interesting and attractive appearance, yet we can clearly see the current page that is most highlighted item. In addition, in order to achieve a harmonic transition, it was decided to make a transition through the cosine function.
The result is clearly shown in the following animation.
Let's take the basic size of the element for w and mark it on the graph. The ultimate goal of calculations is to find the size of the smallest element. All elements outside one cosine wave will be the same size as the smallest element at the end of the wave.
The main condition and essence of the problem is to keep the same width of the whole Page Control, ie the sum of all elements must be constant.
For fewer elements, you can manually enter an element size limit, as applied in the animation above.
Without limitation, the side elements will become larger on the contrary in the lack of elements. Also, if the width is very small, the dimensions can take negative values. In this article, I will not impose these restrictions so as not to complicate the article.
To begin, let's build the desired graph:
Let's design the function that will give us the appropriate graph. The following function gives a result in the range from 0 to 1, which will be useful for calculating the relative coefficient for calculating the base value of the reduction w, through which we calculate the size of the smallest element.
0.5cos( p ) + 0.5 , where p is the angle in radians.
Remember this formula, it will be needed below for calculations. Graph:
The next step is to enter the values needed to calculate the size of each element.
l - total width of Page Control (sum of all elements)
w - base size of one element
c - total number of elements in the Page Control
s - desired size of the smallest element
x - inversely proportional coefficient of w size reduction, from 0 to 1. For example, when x = 0.2, s = w * 0.8
α - is a directly proportional coefficient of reduction of the coefficient x
Assume that the base size is w. As the smaller size will be reduced by the x, smallest element is calculated by the formula s = w(1 – x) . To calculate the intermediate dimensions it is not enough to use x, so we introduce an additional coefficient α that will take part of x in the range from 0 to 1. So now any size can be calculated by the formula s = w (1 – xα) . According to this formula for each element only α will be changed, so the sum of all elements will be calculated as follows:
where c this is the number of elements (see the last index). Therefore, to find the total width, we can derive the following final formula:
where α‾ is the arithmetic mean of all α from 1 to c.
Since our goal is to find the size of the element, we need to derive x from this formula, which we will do. For ease of calculation and prevent errors I have used the service WolframAlpha (forgive me math school teacher).
All we have to do now is calculate all α for each element and find the arithmetic mean for them. Remember our cosine function?
0.5cos( p ) + 0.5. Move the angle π rad to the left to invert it relatively to the graph with the size of the elements, and get the formula:
α = 0.5cos( p – π ) + 0.5 , where p is the angle in radians (or the distance from the central element).
Let's build a graph indicating the required values:
Did you notice? Of course! The same inverted graph from a previous image in the range of yet unknown x :)
Let's remember the rule developed above, which determines the appearance of our Page Control. The value of p for the last element of the transition always falls on the π radian. Therefore, for all elements outside one wave we determine p = π, which will give a straight line on the graph and the same size for all subsequent elements.
The half-wave on example graph is divided into 4 sections (Below in the example of the code it will be determined as 5 elements in the half-wave, the largest and least inclusive). So for the largest element calculation takes the form:
α = 0.5cos( 0 – π ) + 0.5 = 0, and therefore
s = w( 1 – x * 0 ) = w
ie full size. And for the last element of the wave we will have the next calculation:
α = 0.5cos( π – π ) + 0.5 = 1, and therefore
s = w( 1 – x * 1 ) = w( 1 – x )
ie the base amount reduced by the full x, that is the smallest element .
What's next? Then we calculate α for each element, and substitute in formula derived in the preceding paragraph.
Theory is certainly interesting, but much more interesting to create something where it would be useful. So just let's sum up all the calculations using the code. I used Swift language, but I'm sure it will not be difficult to rewrite using any language you use, the syntax is simple to understand.
Everything worked, the calculations are correct. Check in the end of the test code produce the desired width of 160 points.
Duplicating variant of the real interface element, based on the above calculations. Looks much better animated and on real 60 fps;)
So what can be learned from this article? I actually strengthened in the belief that much more fun to exercise creativity and invent creative solutions at least occasionally. It does not let you get bored and gives reason to enjoy the results of your work. And who among us does not want to feel a little aesthetic pleasure …
What do you think? Add your interesting solutions in the comments.