Animating The Arrow

Creating a Progress Bar

Ben Watanabe
A First Project With PaintCode

--

A First Project With PaintCode

& the story of Look Up’s arrow

- Part 2 -

At the core of Look Up is the 20 second rest. During the rest the goal is to look up into the distance and rest the eyes, so we didn’t want numbers that would be watched. Instead we animated the arrows into an arrowGlass that moves up until the down arrow fills back up and it’s time to get back down to business. Once the rest is over the entire screen changes colors, which is easily noticed even when looking up using peripheral vision.

This animation is also the reason that our original arrow was more intricate than might have seemed necessary using clipping masks.

The Big Picture:

Parameters vs. Local Variables

Shown in the image are the key variables. The most important being the percent variable, not local variable though, but parameter. There are additional static variables determining the distance the arrow’s bars move. The derived static variables, made up of expressions, are colored gray and decide when they move. The only variable that needs to be a parameter is the percent, Johnny doesn’t need to set anything else.

Based upon those variables are many dependent variables moving each block. The moveBar[#] variables below can be seen increasing in relation to the percent variable. The variables necessary for this style of progress bar are more complicated due to its staggered progression.

Sidenote: If I were to build this animation again it would be built with the necessary movement “distance” being 100. With distance equal to percent completed there would be one less variable and level of abstraction, keeping the expressions simpler. PaintCode’s smart symbols would make the animated arrow still resizable.

Linking Variables

In the previous section the arrow was shown animated. In order to make the bars move each moveBar variable has to be linked to it’s corresponding bar. The method for linking is similar to linking variables to objects in Xcode’s storyboards.

There’s two methods for linking variables my preferred method is the one illustrated above and involves.

  1. Selecting the layer or group that you want the variable attached to.
  2. Clicking on the small circle/anchor next to the variable.
  3. Dragging it to the property that you want altered on the layer/group.
  4. Set the default starting value again, if necessary for numerical variables.

Sidenote: There’s a second method that is easier, but isn’t as accurate. I don’t use it often, but when I do though there’s just something satisfying about the way it links. With complicated canvases with stacked layers it doesn’t work well, but still I want to show you how it works just because I like it so much ☺

Implementing into Xcode:

This level of complexity calls for a custom NSView class. I know a few developers who will shy away at the mention of custom views, but with PaintCode they are super easy. When we implement our custom class, we are going to be interested in three types of methods:

- (void)drawRect☹NSRect)dirtyRect;
- (void)setClassProperty☹NSObject *)newPropertyValue;
@property BOOL needsDisplay;

The drawRect: method is what does the drawing work for us and is left almost entirely up to the StyleKit. All we have to do is pass it the correct parameters.

The setClassProperty: method is an example of one of the ways to change the state of the NSView. The properties we are interested in here are those that are being passed through to the StyleKit method.

The needsDisplay property is used by the NSView class to understand when it needs to be re-drawn. Set this property to YES when you change one of the parameters that are going into the StyleKit method in the drawRect. This is usually from one of the custom properties you define (see above, setClassProperty:).

A final element we need is something to drive the animation. In this particular case we are using an NSTimer, because the animation is direclty linked to the countdown (which also uses the same timer). There are other ways to do this, which will be discussed in a future post.

@interface NSTimer : NSObject

So now for our implementation, lets take a look at the code:

-(void)setPercentageComplete☹float)newPercentage{
if (newPercentage != _percentageComplete) {
_percentageComplete = newPercentage;
[self setNeedsDisplay:YES];
}
}

This method receives updates to the percentage that the rest has finished. So it starts at zero and ends at 100. First we check that the new and the old percentage is not the same (to avoid unnecessarily asking for a redraw), then we update the value and ask the interface to redraw itself. When that redraw request is processed, it will tell the view to redraw in the drawRect: method:

- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
[StyleKit drawTimerWithFrame:dirtyRect
percent:self.percentageComplete];
}

All we need to do here is pass the rect and our property values through to the StyleKit method and the rest is taken care for us. We are done! Just run Xcode and… it does not animate. Why is that??? The setNeedsDisplay: method was called properly, so that is not it. Well the reason is down to a nice little gotcha when using the NSTimer. It has to do with which runloop the timer is on. For more details, check out this excellent answer on StackOverflow. In short, you need to add your timer to the current run loop:

NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:timer forMode:NSDefaultRunLoopMode];

The Nitty Gritty:

Simplifying Numeric Variables

Numeric variables can be simplified by adding a static amount to the variable based upon the object it’s being applied to. In the case of the arrow’s bars the 201.9 starting point was not included in the equation to keep things simpler. Not only did this make the variables’ expressions easier to read, but also allowed the same variable to be used in a non-arrowGlass version of the arrow (shown below).

ArrowGlass Variables Explained

If you really want to know how the variables in the arrow’s progress bar work then this is for you, if not skip to the next article [link]

gap:
barHeight * 3.5

gap sets the distance that the preceding bar must move before the next begins its progression.

change:
( percent / maxPercent * distance ) * 3.5

change controls the movement of a bar.

stop:
distance / maxPercent * 10

stop sets the point where the bar should stop moving.

moveBlock:
change / 3.3

moveBlock is closer to what might be found in a typical progress bar. It moves the block in the bottom of the arrow. The bottom arrow is not filled with the bars from the top arrow, because then the expression would have needed to include an acceleration method. The end result would have been far more complicated.

percent < 2 * 10 ?
// this checks if the previous bar has moved far enough for the attached bar to begin
0 :
change — ( 2 * gap )
< distance — ( 2 * stop ) ?

// if it should start moving then it next needs to check if it should have stopped moving
: change — ( 2 * gap )
//this determines how far the bar should have moved, taking into account how far it should be from the previous bar
distance — ( 2 * stop )
//this creates the stopping point of the bar, so that it is below the previous bar.

moveBar2 controls the progression of the 2nd bar in the top arrow. This expression had to be written multiple times as in moveBar3 the 2 had to also be 3:

Supporting Files:

Get the PaintCode file of Look Up’s animated arrow on 96Problems.com

~ our “advertizement” ~

Want to learn more about the Mac app that this is all based on?
See the animated comic explanation on Look Up’s site

Or get the app direct

--

--