Drake
Drake

Reputation: 2703

Animate loading spinner arc Xamarin.iOS

In my Xamarin.iOS application, I am trying to animate a loading spinner. I can draw the circle and the arc, but I do not know how to animate it. This is my class for the loading spinner:

public class LoadingSpinnerView : UIView
{
    private CAShapeLayer _thinCirlce;
    private CAShapeLayer _arc;

    public LoadingSpinnerView()
    {
        _arc = new CAShapeLayer();
        _arc.LineWidth = 3;
        _arc.StrokeColor = UIColor.Blue.CGColor;
        _arc.FillColor = UIColor.Clear.CGColor;

        _thinCirlce = new CAShapeLayer();
        _thinCirlce.LineWidth = 1;
        _thinCirlce.StrokeColor = UIColor.Red.CGColor;
        _thinCirlce.FillColor = UIColor.Clear.CGColor;

        Layer.AddSublayer(_thinCirlce);
        Layer.AddSublayer(_arc);
    }

    private nfloat _angle;
    public nfloat Angle
    {
        get
        {
            return _angle;
        }

        set
        {
            _angle = value;
        }
    }

    public override void LayoutSubviews()
    {
        base.LayoutSubviews();

        _thinCirlce.Path = UIBezierPath.FromOval(new CoreGraphics.CGRect(0, 0, Frame.Width, Frame.Height)).CGPath;

        nfloat radius = Frame.Width / 2;
        _arc.Path = UIBezierPath.FromArc(new CoreGraphics.CGPoint(radius, radius), radius, 0, Angle, true).CGPath;
    }
}

I want to be able to animate it, something like this:

UIView.Animate(5, () => { _loadingSpinnerView.Angle = 3.14f; }); // This doesn't actually work...

Upvotes: 1

Views: 569

Answers (2)

Yauhen Sampir
Yauhen Sampir

Reputation: 2104

Ready to use control. Just set control to UIView in your Storyboard.

[Register(nameof(CircleLoadingView))]
public class CircleLoadingView : UIView
{
    public CircleLoadingView(IntPtr handle) : base(handle)
    {
    }

    public override void LayoutSubviews()
    {
        base.LayoutSubviews();

        SetUpAnimation(this.Layer, new CGSize(Frame.Width, Frame.Height), UIColor.Red);
    }

    public void SetUpAnimation(CALayer layer, CGSize size, UIColor color)
    {
        var beginTime = 0.5;
        var strokeStartDuration = 1.2;
        var strokeEndDuration = 0.7;

        var strokeEndAnimation = CABasicAnimation.FromKeyPath("strokeEnd");
        strokeEndAnimation.Duration = strokeEndDuration;
        strokeEndAnimation.TimingFunction = CAMediaTimingFunction.FromControlPoints(0.4f, 0.0f, 0.2f, 1.0f);
        strokeEndAnimation.From = NSNumber.FromFloat(0);
        strokeEndAnimation.To = NSNumber.FromFloat(1);

        var strokeStartAnimation = CABasicAnimation.FromKeyPath("strokeStart");
        strokeStartAnimation.Duration = strokeStartDuration;
        strokeStartAnimation.TimingFunction = CAMediaTimingFunction.FromControlPoints(0.4f, 0.0f, 0.2f, 1.0f);
        strokeStartAnimation.From = NSNumber.FromFloat(0);
        strokeStartAnimation.To = NSNumber.FromFloat(1);
        strokeStartAnimation.BeginTime = beginTime;

        var groupAnimation = new CAAnimationGroup
        {
            Animations = new CAAnimation[] {/*rotationAnimation,*/ strokeEndAnimation, strokeStartAnimation },
            Duration = strokeStartDuration + beginTime,
            RepeatCount = float.PositiveInfinity,
            RemovedOnCompletion = false,
            FillMode = CAFillMode.Forwards
        };

        var circle = CreateCircle(size, color);
        var frame = CGRect.FromLTRB(
            (layer.Bounds.Width - size.Width) / 2,
            (layer.Bounds.Height - size.Height) / 2,
            size.Width,
            size.Height
        );

        circle.Frame = frame;
        circle.AddAnimation(groupAnimation, "animation");
        layer.AddSublayer(circle);
    }

    private CAShapeLayer CreateCircle(CGSize size, UIColor color)
    {
        var layer = new CAShapeLayer();
        var path = new UIBezierPath();
        path.AddArc(new CGPoint(size.Width / 2, size.Height / 2),
            size.Width / 2,
            -(float)(Math.PI / 2),
            (float)(Math.PI + Math.PI / 2),
            true);
        layer.FillColor = null;
        layer.StrokeColor = color.CGColor;
        layer.LineWidth = 2;

        layer.BackgroundColor = null;
        layer.Path = path.CGPath;
        layer.Frame = CGRect.FromLTRB(0, 0, size.Width, size.Height);

        return layer;
    }
}

Upvotes: 1

Drake
Drake

Reputation: 2703

I figured it out. Here is my loading spinner class for other Xamarin developers to use. You can adjust it to your own requirements.

public class LoadingSpinnerView : UIView
{
    private CAShapeLayer _thinCirlce;
    private CAShapeLayer _arcLayer;

    public LoadingSpinnerView(nfloat radius)
    {
        UIColor.Red.SetColor();
        _thinCirlce = new CAShapeLayer();
        _thinCirlce.LineWidth = 1;
        _thinCirlce.Path = UIBezierPath.FromOval(new CoreGraphics.CGRect(0, 0, radius * 2, radius * 2)).CGPath;
        _thinCirlce.StrokeColor = UIColor.Red.CGColor;
        _thinCirlce.FillColor = UIColor.Clear.CGColor;
        Layer.AddSublayer(_thinCirlce);

        UIColor.Blue.SetColor();
        UIBezierPath arcPath = new UIBezierPath();
        arcPath.LineWidth = 4;
        arcPath.LineCapStyle = CGLineCap.Round;
        arcPath.LineJoinStyle = CGLineJoin.Round;
        arcPath.AddArc(new CoreGraphics.CGPoint(radius, radius), radius, 0, 2 * 3.14f, true);

        _arcLayer = new CAShapeLayer();
        _arcLayer.Path = arcPath.CGPath;
        _arcLayer.StrokeColor = UIColor.Blue.CGColor;
        _arcLayer.FillColor = UIColor.Clear.CGColor;
        _arcLayer.LineWidth = 4;
        _arcLayer.StrokeStart = 0;
        _arcLayer.StrokeEnd = 1;

        if (_arcLayer.SuperLayer != null)
        {
            _arcLayer.RemoveAllAnimations();
            _arcLayer.RemoveFromSuperLayer();
        }
    }

    public void StartAnimation()
    {
        Layer.AddSublayer(_arcLayer);

        CABasicAnimation animation = new CABasicAnimation();
        animation.KeyPath = "strokeEnd";
        animation.Duration = 3;
        animation.From = NSNumber.FromFloat(0);
        animation.To = NSNumber.FromFloat(1);
        _arcLayer.AddAnimation(animation, null);
    }

    public void StopAnimation()
    {
        _arcLayer.RemoveAllAnimations();
        _arcLayer.RemoveFromSuperLayer();
    }
}

Upvotes: 0

Related Questions