How We Created Animated Preloaders

You have probably noticed, that we started putting up some cool animations on GitHub. The recent one is called Ophiuchus (they all have weird names of stars). In this article we’ll tell you a little bit about how we actually developed this animation.


Ophiuchus is based on our Dribbble design project, created by Dasha Ermolova. The animation is a preloader for our website featuring Yalantis logo. Dasha’s idea was to add some sort of a visual trick, the illusion of space to make a viewer feel that Y is an axis with planets rotating around it. For that reason the animation does not only show the motion, but the progress as well. It was created with the help of After Effects and then implemented by our iOS developers, Nikita Shytyk and Igor Muzyka.

Read also: Product design in Yalantis

Development for iOS

At first our iOS developers made an attempt to build a component that would only work with one letter. Here is how:

UIBezierPath *outsidePath = ({

 CGPoint point1 = CGPointMake(halfRectWidth - halfLetterWidth, bottomY);

 CGPoint point2 = CGPointMake(halfRectWidth + halfLetterWidth, bottomY);

 // ...

 CGPoint point9 = CGPointMake(halfRectWidth - halfLetterWidth, bottomY - legHeight);

 CGPoint points[9] = {point1, point2, point3, point4, point5, point6, point7, point8, point9};

 CGMutablePathRef cgPath = CGPathCreateMutable();

 CGPathAddLines(cgPath, &CGAffineTransformIdentity, points, sizeof points / sizeof *points);


 [UIBezierPath bezierPathWithCGPath:cgPath]; 


However, the code got fouled with magic numbers and became unusable. The next idea was to create a universal component suitable for any kind of text. Igor wrote the code with the help of native CoreText framework, where NSAttributedString breaks down into letter by letter UIBezierPath:

- (NSArray *)yal_bezierPaths {

 NSMutableArray *letters = [NSMutableArray new];

 CTLineRef line = CTLineCreateWithAttributedString((__bridge CFAttributedStringRef)self);

 CFArrayRef runArray = CTLineGetGlyphRuns(line);

 CGRect biggestBoundingBox = CGRectZero;

 for (CFIndex runIndex = 0; runIndex < CFArrayGetCount(runArray); runIndex++) {

 CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runArray, runIndex);

 CTFontRef runFont = CFDictionaryGetValue(CTRunGetAttributes(run), kCTFontAttributeName);

 for (CFIndex glyphIndex = 0; glyphIndex < CTRunGetGlyphCount(run); glyphIndex++) {

 CFRange range = CFRangeMake(glyphIndex, 1);

 CGGlyph glyph;

 CGPoint position;

 CTRunGetGlyphs(run, range, &glyph);

 CTRunGetPositions(run, range, &position);

 CGPathRef letter = CTFontCreatePathForGlyph(runFont, glyph, NULL);

 CGAffineTransform transform = CGAffineTransformMakeTranslation(position.x, position.y);

 UIBezierPath *path = [UIBezierPath bezierPathWithCGPath:letter];

 CGRect boundingBox = CGPathGetBoundingBox(letter);

 if (CGRectGetHeight(boundingBox) > CGRectGetHeight(biggestBoundingBox)) {

 biggestBoundingBox = boundingBox;



 [path applyTransform:transform];

 [letters addObject:path];



 for (UIBezierPath *letter in letters) {




 return [NSArray arrayWithArray:letters];


We assembled the whole text label from separate UIBezierPath objects. This way we received a vector representation for each letter of any font and size, which makes the code super reusable! We simply outlined all the letters in the Ophiuchus project and used them as a mask for the progress’ layer. Now we can animate the whole word or even a sentence in a variety of different ways. We can also animate each letter separately anyway you like it:

Yalantis animated preloader

And here is another one:

Yalantis preloader

Check out the original Ophiuchus animation:

We are going to build more awesome animations like that, so follow our updates!

4.2/ 5.0
Article rating
Remember those Facebook reactions? Well, we aren't Facebook but we love reactions too. They can give us valuable insights on how to improve what we're doing. Would you tell us how you feel about this article?
Excited to create something outstanding?

We share the same interests.

Contact us

We use cookies to personalize our service and to improve your experience on the website and its subdomains. We also use this information for analytics.