Yalantis: iOS, Android And Web App Development Company

Eat. Drink. Track. How We Created Eat Fit iOS Animation Inspired by Google Fit

By Alla Kupriianova

Today there are plenty of apps that are aimed at tracking any type of activity from sleeping to eating to molding your muffin top to six pack abs. But despite the tracking hype, sometimes it turns into some sort of a quest to find the app that fits your personal needs and does something more meaningful than show you graphs and stats.

Even if you’re lucky to find that app, it’s still not perfect because you have to complete your user profile with all the data required, not to mention that some of it can be really useless for that certain app.

Who’s got to solve this problem? A Unicorn, I guess! In my humble designer’s opinion, there are the following three aspects for a successful activity tracking app:

  • Clear and user-friendly interface. (Less is more! So White Space Zen adepts are welcome!)
  • Understandable and intuitive in-app navigation. (Simple registration requesting only essential personal data)
  • Meaningful interactions. (Data-driven insights, not simply numbers put on the screen)
  • A feature that makes this app distinctive or something you’ll be remembered for. (I could come up with a smooth morphing animation)

Inspired by Google Fit customizable animation I made up my mind to create my own meaningful Eat Fit animated component for an activity tracking app. I used After Effects to create it, probably because I don’t like taking the easy road.

You might also like: Prototyping in Flinto, Principle, and Pixate

Eat Fit customizable animationThe Eat Fit component consists of three screens: one for food, one for water, and the other one for energy. I tried to make every screen as informative as it can possibly be. When a user opens the app, he/she sees the following information associated with an activity on a single page:

  • percent of consumed food, hydration, energy, and whatever
  • how much there is still left to reach the goal
  • a cute illustrative chart that smoothly converts into a drop, flame, and a pear

I believe that this simple animation will inspire you to make your own great activity tracking apps that will help thousands of people become fit and happy. Luckily, we’ve got the Swift coding part wrapped and ready thanks to Aleksey Chernish, our iOS developer.

Now let’s find out how great design ideas turn into something you can actually touch and feel (if you have an iPhone, that is).

How We Development the Eat Fit Animation in Swift

By Aleksey Chernish

Every time I start working on a reusable component, I think over the interface that users will be interacting with before I even start writing any code. Our component is represented by a set of pages not limited to food, hydration and energy. The idea is to let a user choose the activities they want to track and put those on the screen. Each page in the Eat Fit animation includes the following components:

  • header
  • description
  • % done against goal
  • tint color
  • logo image

Since we’re building a fully customizable component, thickness of charts should be also adjustable.

Below you can see the structure of the component in terms of iOS SDK. Component itself is ChartPagingViewController. Inside there is a custom paging control ChartPageControl and a child page view controller. Each page is a view controller or ChartSlideViewController.

Eat Fit animation development

I don’t think we should consider the paging implementation here, because it’s quite simple. Instead, let’s explore the particularities of building the following five parts of the Eat Fit animation:

altogether.gif

  • jumping drop
  • logo transformations
  • chart
  • typewriter effect
  • animation for percents

 

To display each part I used different views that encapsulate specific animation logic. Each view animates with a certain delay. Presetting those delays helped me a lot at the final cut stage when I needed to synchronize all animations.

A drop transforming into logo

Drop is an instance of UIBrezierPath that changes its position with a custom timing function.

drop.gif

If you’re wondering how a circle transforms into a logo let’s take a look at the slow-mo version.

low_speed_transform.gif

As you can see, there’s no magic there. By reducing a circle’s size, the logo is getting bigger. When the speed is high enough it smoothly transforms its shape.

Chart

Chart is CAShapeLayer, which applies UIBezierPath as a ‘path’ property. To make chart move along the path I used the ‘strokeEnd’ property of the layer. The CALayer allows you to adjust line tips with the lineCap property. For our particular case I made them round.

chart.gif

Text with a typewriter effect

As you can see, the text shows up on the screen as if it’s created using a typewriter. I achieved this effect by applying attributed text to the UILabel. By the start of the animation all text has a clear color. Then, by making the text black gradually, symbol by symbol, with a specific delay, I made the whole text visible.

typewriter.gif

Animation for percents

This animation represents a very interesting case where you have to animate something that cannot be animated.

percentage.gif

Core Animation and UIKit give us a really wide set of tools to animate ‘displayed’ properties of objects such as opacity, location on the screen, scale, rotation, and so on. All these parameters can be continuously changed using timing functions, bounces, and more.

But what if we needed to animate something that’s not displayed, like % done against goal which is represented by NSString? CALayer can help us with that. Below we’ll implement a handy Tween class that continuously updates the value for a key of a given object.

Create a new Single View Application and add a label outlet to the ViewController’s view:

@IBOutlet weak var label: UILabel!​

Now create a new swift file and import QuartzCore and UIKit at the top:

import QuartzCore

Declare two new classes: Tween and TweenLayer as the CALayer descendant:

class Tween {

}

class TweenLayer: CALayer {

}

Tween will encapsulate interaction with an animated object. Therefore, all the actual work will be performed by TweenLayer.

Add layer’ property to the Tween class:

private weak var layer: TweenLayer!

We need an object and a key. Let’s add two more properties:

let object: UIView

let key: String

In addition, we need starting and ending values for the animated value and duration. Let’s make Tween’s initializer look like this:

init (object: UIView, key: String, from: CGFloat, to: CGFloat, duration: NSTimeInterval) {

  self.object = object

  self.key = key

  layer = TweenLayer()

}

From, to and duration options belong to the layer, so we’ll pass them later. Create a method that starts the animation and leave it blank for now.

func start() {

}

Now, let’s switch to the TweenLayer class. First, let’s declare some properties:

var from: CGFloat = 0

var to: CGFloat = 0

var tweenDuration: NSTimeInterval = 0

var timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)

var delay: NSTimeInterval = 0

It’s quite clear that to define the animation, we need starting and ending values, duration, timing function, and delay.

Let’s add one more property. It’s very important.

@NSManaged private var animatableProperty: CGFloat

This property will smoothly change it’s value. By declaring it as @NSManaged, we pass control over the real value hidden behind the property declaration to CoreAnimation.

When some properties of CALayer change, it’s required to show changes, which happens at needsDisplayForKey method. Let’s implement it:

override class func needsDisplayForKey(event: String) -> Bool {

  return event == "animatableProperty" ? true : super.needsDisplayForKey(event)

}

Here we inform CoreAnimation that animatableProperty requires animation. But this isn’t enough, so we have to provide CAAction for the ‘animatableProperty’ key. We will do this by implementing the following method:

override func actionForKey(event: String) -> CAAction? {

  if event != "animatableProperty" {

      return super.actionForKey(event)

  }

  let animation = CABasicAnimation(keyPath: event)

  animation.timingFunction = timingFunction

  animation.fromValue = from

  animation.toValue = to

  animation.duration = tweenDuration

  animation.beginTime = CACurrentMediaTime() + delay

  animation.delegate = self

  return animation;

}

Here we created and set up the animation for our property. Also, we assigned 'self' as an animation’s delegate. But according to the CAAnimation Class Reference, there’s no delegate protocol. Furthermore, it’s one of the rare cases when a delegate is retained by the object.

What we’re interested in now is getting a callback on the animation completion, so let’s implement the following delegate method:

override func animationDidStop(anim: CAAnimation, finished flag: Bool) {

}

We’ll leave it blank for now. Knowing that animatableProperty changes it’s value through time is not enough. We need to receive continuous updates during the course of the animation. A method ‘display’ is what we need:

override func display() {

  if let value = presentationLayer()?.animatableProperty {

  }

}

It’s noteworthy that in order to obtain a current animatableProperty value we use presentationLayer, not the layer itself. Let’s see what the official documentation states:

The layer object returned by this method provides a close approximation of the layer that is currently being displayed onscreen. While an animation is in progress, you can retrieve this object and use it to get the current values for those animations.

We already mentioned that to animate the layer we must change one of its animated properties. For convenience let’s create the following method:

func startAnimation() {

  animatableProperty = to

}

And then update a start method in the Tween class:

func start() {

layer.startAnimation()

}

By this moment all the changes happened inside the TweenLayer class. To make Tween know about those changes let’s declare a delegate protocol:

protocol TweenLayerDelegate: class {

  func tweenLayer(layer: TweenLayer, didSetAnimatableProperty to: CGFloat) -> Void

  func tweenLayerDidStopAnimation(layer: TweenLayer) -> Void

}

And a corresponding property in the TweenLayer class:

var tween: TweenLayerDelegate?

Now it’s time for callbacks! Add the following to the animationDidStop method:

tween?.tweenLayerDidStopAnimation(self)​

And update a display() method:

override func display() {

  if let value = presentationLayer()?.animatableProperty {

      tween?.tweenLayer(self, didSetAnimatableProperty: value)

  }

}

Let’s go back to the Tween class. Now, when the TweenLayer is ready, let’s update the layer creation in the Tween’s initializer:

layer = {

  let layer = TweenLayer()

  layer.from = from

  layer.to = to

  layer.tweenDuration = duration

  layer.tween = self

  object.layer.addSublayer(layer)

  return layer

}()

Pay attention that the layer is a weak property and no one can guarantee that it won’t be destroyed during the setup if we don’t use block. The actual retention happens only when the layer is added as a sublayer to the object’s layer.

Also, let’s add a computed property timingFunction to the Tween class. It’ll represent the same property of the layer.

var timingFunction: CAMediaTimingFunction {

  set {

      layer.timingFunction = newValue

  }

  get {

      return layer.timingFunction

  }

}

The Tween above was assigned as a ‘tween’ property of the TweenLayer. Let’s adopt the TweenLayerDelegate protocol in the extension to the Tween class:

extension Tween: TweenLayerDelegate {

  func tweenLayer(layer: TweenLayer, didSetAnimatableProperty to: CGFloat) {

          object.setValue(to, forKey: key)

  }​

  func tweenLayerDidStopAnimation(layer: TweenLayer) {

      layer.removeFromSuperlayer()

  }

}

In the first method we finally assigned intermediate values to the object’s modified property. In the second method we made a clean up when the animation is finished.

Some may ask why the property that requires animation isn’t necessarily CGFloat. Well, let’s add a mapper property to the Tween class. The Mapper will transform an intermediate float value into the value of the type we need.

var mapper: ((value: CGFloat) -> (AnyObject))?

And now let’s update a corresponding delegate method:

func tweenLayer(layer: TweenLayer, didSetAnimatableProperty to: CGFloat) {

  if let mapper = mapper {

      object.setValue(mapper(value: to), forKey: key)

  } else {

      object.setValue(to, forKey: key)

  }

}

Ready! Now we can test our Tween class on the label that we created at the beginning of the tutorial.

override func viewDidAppear(animated: Bool) {

super.viewDidAppear(animated)

let tween = Tween(object: label, key: "text", from: 0, to: 50, duration: 3)

tween.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)

tween.mapper = { value in return String(format: "%0.f%%", value)}

tween.start()

}

If you run the application, you’ll see something like this:

percentage_50_demo.gif

Memory management

You might be curious why Tween stays alive after the viewDidAppear method execution is finished. Well, let’s look at the retain scheme.

Tween itself doesn’t bind its lifetime with a given object. Besides, we should remember that the layer is a weak property of Tween.

private weak var layer: TweenLayer!

However, when we create a layer property, we insert it into the object’s layer hierarchy that keeps the layer alive.

object.layer.addSublayer(layer)

Pay attention that animationDelegate property of TweenLayer isn’t declared as weak.

var animationDelegate: TweenLayerDelegate?

Because of this Tween is held in the memory by TweenLayer.

When the animation is over, Tween receives a callback from its layer, and removes this layer from the layers’ hierarchy. This leads to the layer’s deallocation, and consequently the Tween (that was held in memory by the layer) gets released as well.

func tweenLayerDidStopAnimation(layer: TweenLayer) {

  layer.removeFromSuperlayer()

}

That’s pretty much it!

Check out: How much does it cost to build an app?

In conclusion, I’d like to say that the main challenge in the project was implementing the possibility for a user to customize the animation. After all, animating graphs and images isn’t enough, I had to provide the user with a full control over content.

Eat Fit customization is similar to working with UITableView: you need to pass the total number of pages and parameters for each page: text, color, image, and so on. Eat Fit is undoubtedly, a flexible component and during its development I learned to think not only about the code, but about how the animation will be perceived and used.

Check out our links:

Insights

Health and Fitness App Development: 3 Types of Apps We Designed

Tech

Koloda Tinder-Like Animation Version 2. Prototyping in Pixate and Development in Swift

Design

UIDynamics, UIKit or OpenGL? 3 Types of iOS Animations for the Star Wars

See our workout book

Know what your health & fitness app can look like

Check out the case study