Circular loading animation (like Weather Channel App)

1.1k Views Asked by At

How would I implement a loading animation like the one found in the Weather Channel iOS App?

Specifically, I have a rounded UIButton that I want a spinning circle around when the user has tapped it, and something is loading.

Preferably, I'd want to make this using a built-in iOS framework, and as few 3rd party libraries as possible.

Here's how the Weather Channel Application looks like (couldn't get a gif, so to see it in action download the application from the App Store):

Screenshot of Weather Channel iOS App

It doesn't necessarily have to look exactly like this, a solid color would be a good start. But the concept should be the same. I don't believe it should be hard to make, but sadly I have no idea where to start.

Thanks for all help!

EDIT 1: I should point out that a solid color is good enough, and it doesn't need a gradient or a glow.

I have been working on some simple code that might be in the right direction. Please feel free to use that as a starting point:

- (void)drawRect:(CGRect)rect {
  // Make the button round
  self.layer.cornerRadius = self.frame.size.width / 2.0;
  self.clipsToBounds = YES;

  // Create the arc
  CAShapeLayer *circle = [CAShapeLayer layer];
  circle.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2) radius:(self.frame.size.width / 2) startAngle:M_PI_2 endAngle:M_PI clockwise:NO].CGPath;
  circle.fillColor = [UIColor clearColor].CGColor;
  circle.strokeColor = [UIColor redColor].CGColor;
  circle.lineWidth = 10;

  // Create the animation

  // Add arc to button
  [self.layer addSublayer:circle];
}
2

There are 2 best solutions below

4
On

Maybe I miss something but I think you only need to rotate a custom spin asset.

You can easily achieve this with a method like:

- (void) runSpinAnimationOnView:(UIView*)view duration:(CGFloat)duration repeat:(float)repeat;
{
    CABasicAnimation* rotationAnimation;
    rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    rotationAnimation.toValue = [NSNumber numberWithFloat: M_PI * 2.0 * duration ];
    rotationAnimation.duration = duration;
    rotationAnimation.cumulative = YES;
    rotationAnimation.repeatCount = repeat;

    [view.layer addAnimation:rotationAnimation forKey:@"rotationAnimation"];
}

You need to repeat it indefinitely. So put HUGE_VALF as repeat parameter.

And if you want to create a custom spin image according to your button size, you can use bezier path. For example, you can do something like:

-(UIImageView*)drawBezierPathInView:(UIView*)view{

    UIGraphicsBeginImageContext(CGSizeMake(view.frame.size.width, view.frame.size.height));

    //this gets the graphic context
    CGContextRef context = UIGraphicsGetCurrentContext();

    //you can stroke and/or fill
    CGContextSetStrokeColorWithColor(context, [UIColor colorWithRed:0.5f green:0.5f blue:0.5f alpha:1.f].CGColor);

    CGFloat lineWidth = 8.f;
    UIBezierPath *spin = [UIBezierPath bezierPath];
    [spin addArcWithCenter:CGPointMake(view.frame.size.width * 0.5f, view.frame.size.height * 0.5f) radius:view.frame.size.width * 0.5f - lineWidth * 0.5f startAngle:-M_PI_2 endAngle:2 * M_PI_2 clockwise:YES];
    [spin setLineWidth:lineWidth];
    [spin stroke];

    //now get the image from the context
    UIImage *bezierImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    UIImageView *bezierImageView = [[UIImageView alloc]initWithImage:bezierImage];
    return bezierImageView;
}

And you create your button like:

// myButton is your custom button. spinView is an UIImageView, you keep reference on it to apply or to stop animation
self.spinView = [self drawBezierPathInView:mybutton];
[mybutton addSubview:self.spinView];
[self runSpinAnimationOnView:self.spinView duration:3 repeat:HUGE_VALF];
5
On

As pointed out elsewhere, you can just add a view, and the rotate it. If you want to rotate with block-based animation, it might look like:

- (void)rotateView:(UIView *)view {
    [UIView animateWithDuration:1.0 delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
        [UIView animateKeyframesWithDuration:1.0 delay:0.0 options:UIViewKeyframeAnimationOptionRepeat animations:^{
            for (NSInteger i = 0; i < 4; i++) {
                [UIView addKeyframeWithRelativeStartTime:(CGFloat) i / 4.0 relativeDuration:0.25 animations:^{
                    view.transform = CGAffineTransformMakeRotation((CGFloat) (i + 1) * M_PI / 2.0);
                }];
            }
        } completion:nil];
    } completion:nil];
}

Frankly, while I generally prefer block-based animation, the silliness of wrapping an animation with an animation (which is necessary to get no ease-in/ease-out) makes the block-based approach less compelling. But it illustrates the idea.


Alternatively, in iOS 7 and later, you could use UIKit Dynamics to spin it:

  1. Define a property for the animator:

    @property (nonatomic, strong) UIDynamicAnimator *animator;
    
  2. Instantiate the animator:

    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
    
  3. Add rotation to an item:

    UIDynamicItemBehavior *rotationBehavior = [[UIDynamicItemBehavior alloc] initWithItems:@[self.imageView]];
    [rotationBehavior addAngularVelocity:2.0 forItem:self.imageView];
    [self.animator addBehavior:rotationBehavior];