Home > Back-end >  Strange behaviour with a wiggle animation of a pill shaped UIButton
Strange behaviour with a wiggle animation of a pill shaped UIButton

Time:10-11

My view has a UIButton which is pill shaped by setting the corner radius to half the view height. All good, but when its animated (wiggle by rotating), the corner radius seems to change, deforming the pill shape. When the animation ends all returns to normal:

Normal appearance:

Normal appearance

Animating appearance (see corner radius):

Pill shape messed up

A recording of this is shown here:

https://www.dropbox.com/s/xoowpfzdf0ijuql/Simulator Screen Recording - iPhone 12 - 2021-10-08 at 15.05.14.mp4?dl=0

The animation code (Xamarin c#) looks like:

public static void Wiggle(UIView view, double duration = 0.1f, float repeatCount = 3.0f)
{
    UIView.Animate(duration,
        () => view.Transform = CGAffineTransform.MakeRotation((nfloat)(-5f * Math.PI / 180f)),
        () =>
        {
            UIView.AnimateNotify(duration, 0.0f, UIViewAnimationOptions.Repeat | UIViewAnimationOptions.Autoreverse | UIViewAnimationOptions.AllowUserInteraction,
                () =>
                {
                    UIView.SetAnimationRepeatCount(repeatCount);
                    view.Transform = CGAffineTransform.MakeRotation((nfloat)(5f * Math.PI / 180f));
                },
                (animationFinished) =>
                {
                    UIView.Animate(duration, 0, UIViewAnimationOptions.AllowUserInteraction,
                        () => { view.Transform = CGAffineTransform.MakeIdentity(); }, null);
                });
        });
}

As seen in the video it does not happen always. Sometimes the pill shape remains. It happens on both simulator and real device. Any clues? Thanks

CodePudding user response:

You don't show your "PillShape" code, but I'm assuming you are "setting the corner radius to half the view height" in LayoutSubviews()?

If so, the problem is likely that as you rotate the button its height is changing, and you no longer have a correct radius.

You may be better off using Layer animation, rather than rotating the view itself.

I don't use Xamarin / C#, so this is based off Objective-C code... hopefully it will get you on your way.

This uses a slow (0.5-second) animation to make it easy to see that the radius does not change during rotation. Tap the button to start/stop the animation:

using Foundation;
using System;
using UIKit;
using CoreAnimation;
using ObjCRuntime;

namespace QuickTestApp
{
    public partial class ViewController : UIViewController
    {
        public WigglePillButton btn { get; private set; }
        public bool isWiggling { get; private set; }

        public ViewController(IntPtr handle) : base(handle)
        {
        }

        public override void ViewDidLoad()
        {
            base.ViewDidLoad();
            // Perform any additional setup after loading the view, typically from a nib.

            btn = new WigglePillButton();
            btn.TranslatesAutoresizingMaskIntoConstraints = false;

            btn.SetTitle("Testing", UIControlState.Normal);
            btn.BackgroundColor = UIColor.SystemBlueColor;

            View.AddSubview(btn);

            // Get the parent view's layout
            var margins = View.SafeAreaLayoutGuide;

            btn.CenterXAnchor.ConstraintEqualTo(margins.CenterXAnchor).Active = true;
            btn.CenterYAnchor.ConstraintEqualTo(margins.CenterYAnchor).Active = true;
            btn.WidthAnchor.ConstraintEqualTo(120).Active = true;

            btn.AddTarget(this, new Selector("ButtonClickAction:"), UIControlEvent.TouchUpInside);

            isWiggling = false;
        }

        [Export("ButtonClickAction:")]
        public void ButtonClickAction(UIButton sender)
        {
            if (isWiggling)
            {
                btn.StopAnim();
            } else {
                btn.StartAnim();
            }
            isWiggling = !isWiggling;
        }

        public override void DidReceiveMemoryWarning()
        {
            base.DidReceiveMemoryWarning();
            // Release any cached data, images, etc that aren't in use.
        }
    }

    public class WigglePillButton : UIButton
    {
        public override void LayoutSubviews()
        {
            base.LayoutSubviews();
            var maskingShapeLayer = new CAShapeLayer()
            {
                Path = UIBezierPath.FromRoundedRect(Bounds, 12).CGPath
            };
            Layer.Mask = maskingShapeLayer;
        }

        public void StartAnim()
        {
            AddAnimations();

        }

        public void StopAnim()
        {
            Layer.RemoveAllAnimations();

        }

        private void AddAnimations()
        {
            CATransaction.Begin();

            Layer.AddAnimation(RotAnim(), "rot");

            CATransaction.Commit();

        }

        private CAKeyFrameAnimation RotAnim()
        {
            CAKeyFrameAnimation animation = CAKeyFrameAnimation.FromKeyPath("transform.rotation.z");
            double angle = 5f * Math.PI / 180f;
            animation.Values = new[] { NSNumber.FromDouble(angle), NSNumber.FromDouble(-angle) };
            animation.AutoReverses = true;
            animation.Duration = 0.5;
            animation.RepeatCount = float.PositiveInfinity;
            return animation;
        }

    }
}
  • Related