Custom loading animation for iOS  

I came a cross a bunch of links for CSS Loading Animations. This one seemed rather useless but funny.

Being new to iOS and learning the Cocoa-touch framework(s) I wanted to challenge my self to make a similar loading animation on iOS :)

Skjermbilde 2014-03-29 kl. 13.12.18.png

I solved this by setting up a path for each object to move along with UIBezierPath and CAKeyframeAnimation.

First. Create a new project, a Single View Application will do. Let’s name it FancyLoadingAnimation.

Skjermbilde 2014-03-29 kl. 11.13.03.png

Open Main.storyboard and add a button somewhere on the View. By default Xcode should also have created a ViewController as the Custom Class for the first ViewController in the Storyboard. If you click on ViewController in the dock, you can see under the Identity Inspector that the Custom Class is set to “ViewController”.

Skjermbilde 2014-03-29 kl. 11.21.00.png

Hide the dock and right-side panel to get more space. Then ⌥ (ALT) + Click the ViewController.m file. Both the Storyboard and the ViewController.m file should now be open. ⌃ (CTRL) + Click on the button and hold ⌃ (CTRL) pressed while moving your mouse over to ViewController.m under @implementation.

Skjermbilde 2014-03-29 kl. 11.32.51.png

This is one way to make an Action Connection in Xcode. Name is something useful like buttonPressed.

Let’s get back to this method later. Create a new file with ⌘ (CMD) + N. Choose Objective-C Class under Choose Cocoa Touch. Subclass should be UIView, name it LoadingAnimationView.

Open LoadingAnimationView.h and add two class methods.

@interface LoadingAnimationView : UIView

+ (void)showLoadingView;
+ (void)hideLoadingView;

@end

Using these class methods we will tell LoadingAnimationView to put it self on top of the view hierarchy and remove it self again. We don’t need to keep a reference to it. It will keep a reference to it self instead.

Let’s start with the init method. Here we’ll create the objects that we will animate and set the background of the view.

- (id)init
{
    self = [super init];
    if (self) {

        // Background with alpha so the view is transculent.
        self.backgroundColor = [UIColor colorWithWhite:0.2
                                                 alpha:0.85];


        // Get the size of the entire screen
        CGRect screenRect = [[UIScreen mainScreen] bounds];

        // Set the frame to be equal to the screen size
        self.frame = CGRectMake(0, 0, screenRect.size.width, screenRect.size.height);

        // Let's create a label
        UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.bounds), 100)];
        label.center = self.center;
        label.textAlignment = NSTextAlignmentCenter;
        label.textColor=[UIColor whiteColor];
        label.text = @"Loading";
        [self addSubview:label];

        /*
        Here we will add the animations
         */

    }
    return self;
}

Let’s implement + (void)showLoadingView. Before we can do that we will make it possible to keep one static reference to this View, so we don’t need to instantiate it everything we want to show it, and being forced to keep a reference to it until we want to hide it.

+ (instancetype)privateInstance
{
    static dispatch_once_t once;
    static LoadingAnimationView *privateInstance;
    dispatch_once(&once, ^{
        privateInstance = [[self alloc] init];
    });
    return privateInstance;
}

This method will keep a static reference to our self that only will be instantiated once. Now let’s create + (void)showLoadingView.

+ (void)showLoadingView
{
    LoadingAnimationView *loadingView = [LoadingAnimationView privateInstance];

    UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
    [keyWindow addSubview:loadingView];
}

The *keyWindow is our Window where all the views are added to. When we do [keyWindow addSubview:loadingView], we put this View on top of the hierarchy. Let’s make a transition where the View fades in which is a less dramatic appearance.

 [UIView transitionWithView:keyWindow
                      duration:0.3
                       options:UIViewAnimationOptionTransitionCrossDissolve
                    animations:^{
                        [keyWindow addSubview:loadingView];
                    }
                    completion:^(BOOL finished) {

                    }];

Go back to ViewController.m and implement the action for when you press the button you created in the Storyboard. Remember to import “LoadingAnimationView.h”.

- (IBAction)buttonPressed:(id)sender {
    [LoadingAnimationView showLoadingView];
}

Now run the application and click the button. Try to click the button again behind the LoadingAnimationView, you can’t. This way the user can’t do anything until you are done loading or doing what needs to be done. Not an optimal solution really, I wouldn’t use it in a real app. :)

Let’s add some animations!

Create a new file, this time subclass it to NSObject. Name it AnimatedCircleUtility.

This object will have one class method, it’s long and it’s ugly.. But it saves you a lot of time - using this utility it becomes easy to add those moving circles in big numbers ;)

@interface AnimatedCircleUtility : NSObject

+ (void)addAnimatedCircleToView:(UIView *)view
                   withDiameter:(CGFloat)diameter
                         center:(CGPoint)center
                          color:(UIColor *)color
                   movingRadius:(CGFloat)radius
                      clockwise:(BOOL)clockwise
                       duration:(CGFloat)duration;

@end

Let’s implement it in the .m file.

+ (void)addAnimatedCircleToView:(UIView *)view
                   withDiameter:(CGFloat)diameter
                         center:(CGPoint)center
                          color:(UIColor *)color
                   movingRadius:(CGFloat)radius
                      clockwise:(BOOL)clockwise
                       duration:(CGFloat)duration
{

    // Let's create the circle
    UIView *circle = [[UIView alloc] initWithFrame:CGRectMake(0, 0, diameter, diameter)];
    circle.backgroundColor = color;

    // This will make it circular
    circle.layer.cornerRadius = diameter / 2;

    // Let's place it where we want it on the screen
    circle.center = center;

    // This path is where the circle will move along
    UIBezierPath *path = [[UIBezierPath alloc] init];

    // This creates a circular path
    [path addArcWithCenter:center
                    radius:radius
                startAngle:0.0
                  endAngle:M_PI * 2.0
                 clockwise:clockwise];

    // This creates the animation
    CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    pathAnimation.calculationMode = kCAAnimationPaced;
    pathAnimation.fillMode = kCAFillModeForwards;
    pathAnimation.removedOnCompletion = NO;
    pathAnimation.repeatCount = INFINITY;
    pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    pathAnimation.duration = duration;

    // Let's use the UIBezier path we created
    pathAnimation.path = path.CGPath;

    // We add the animation to the circle
    [circle.layer addAnimation:pathAnimation forKey:@"movingAnimation"];

    [view addSubview:circle];
}

Alright.. Let’s go back to LoadingAnimationView’s init. Remember to import “AnimatedCircleUtility.h”.

- (id)init
{
    self = [super init];
    if (self) {

               ...

        [AnimatedCircleUtility
            addAnimatedCircleToView:self
            withDiameter:15
            center:self.center
            color:[UIColor blueColor]
            movingRadius:35
            clockwise:YES
            duration:2.0];

        [AnimatedCircleUtility
            addAnimatedCircleToView:self
            withDiameter:15
            center:self.center
            color:[UIColor greenColor]
            movingRadius:35
            clockwise:YES
            duration:3.0];

        [AnimatedCircleUtility
            addAnimatedCircleToView:self
            withDiameter:15
            center:self.center
            color:[UIColor yellowColor]
            movingRadius:35
            clockwise:NO
            duration:2.0];

        [AnimatedCircleUtility
            addAnimatedCircleToView:self
            withDiameter:15
            center:self.center
            color:[UIColor redColor]
            movingRadius:35
            clockwise:YES
            duration:4.0];

        [AnimatedCircleUtility
            addAnimatedCircleToView:self
            withDiameter:15
            center:self.center
            color:[UIColor brownColor]
            movingRadius:35
            clockwise:NO
            duration:5.0];


    }
    return self;
}

Now run the application again… :D

Let’s implement the method for hiding it again.

+ (void)hideLoadingView
{
    LoadingAnimationView *loadingView = [LoadingAnimationView privateInstance];
    [loadingView removeFromSuperview];
}

That’s it really. A final touch to se demonstrate how you hide it. In ViewController.m.

- (IBAction)buttonPressed:(id)sender {
    [LoadingAnimationView showLoadingView];

    dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 2);
    dispatch_after(delay, dispatch_get_main_queue(), ^(void){
        [LoadingAnimationView hideLoadingView];
    });
}

If you noticed, the UIView’s transitionWithView:duration:options:animations:completion: method will freeze all other animations until the transition is done. I haven’t figured out a solution to this problem. Still learning… :)

Source code

 
33
Kudos
 
33
Kudos