Forum Xamarin.iOS

Announcement:

The Xamarin Forums have officially moved to the new Microsoft Q&A experience. Microsoft Q&A is the home for technical questions and answers at across all products at Microsoft now including Xamarin!

To create new threads and ask questions head over to Microsoft Q&A for .NET and get involved today.

Using a custom easing function for UIScrollView.ContentOffset

GillesDignardGillesDignard CAMember ✭✭
edited August 2014 in Xamarin.iOS

I'm trying to animate a scroll view's content offset using a custom easing function.

The UIView.Animate method below works, but the CABasicAnimation does not. The completion block triggers so it jumps to where I want it to be, but I can't figure out just what is missing / incorrect with the animation's definition (the KeyPath? the NSValue.FromPoint? somethine else?)

Has anyone done this before and know what's missing?

Works:

        private void AnimateContentOffset(double duration, PointF oldOffset, PointF newOffset) {
            // Animate to the new content offset
            UIView.Animate(duration,
                // Animation
                () => {
                    gScrollView.ContentOffset = newOffset;
                },
                // Completion
                () => {
                    SetRecentShotIsHidden(_currentZoneDisplayed, false);
                });
        }

Doesn't work (completion block is triggered, but there's no animation to preceed it):

        private void AnimateContentOffset(double duration, PointF oldOffset, PointF newOffset) {
            // Animate to the new content offset
            CATransaction.Begin();

            CATransaction.CompletionBlock = () => {
                gScrollView.ContentOffset = newOffset;
                SetRecentShotIsHidden(_currentZoneDisplayed, false);
            };

            CABasicAnimation animation = new CABasicAnimation();
            animation.KeyPath = "contentOffset";
            animation.From = NSValue.FromPointF(oldOffset);
            animation.To = NSValue.FromPointF(newOffset);
            animation.TimingFunction = CAMediaTimingFunction.FromControlPoints(0.60f, 0.00f, 0.40f, 1.15f);
            animation.Duration = duration;
            animation.RemovedOnCompletion = true;
            animation.FillMode = CAFillMode.Forwards;

            gScrollView.Layer.AddAnimation(animation, "horizContentOffset");

            CATransaction.Commit();
        }

Posts

  • GillesDignardGillesDignard CAMember ✭✭

    Adam -

    Perfect; that solved it.

    Thank you!

    • Gilles
  • GillesDignardGillesDignard CAMember ✭✭
    edited August 2014

    And to all those who are led here by some future search, here is the final code I ended up with:

    public static void AnimateContentOffsetWithBounce(this UIScrollView scrollView, PointF newOffset, double duration = 0.25) {
        const string kAnimationKey = "customEasedBounds";
    
        PointF oldOffset = scrollView.ContentOffset;
        RectangleF oldBounds = scrollView.Bounds;
        RectangleF newBounds = new RectangleF(oldBounds.X + newOffset.X - oldOffset.X, oldBounds.Y + newOffset.Y - oldOffset.Y, oldBounds.Width, oldBounds.Height);
    
        CATransaction.Begin();
    
        CATransaction.CompletionBlock = () => {
            scrollView.Bounds = newBounds;
            scrollView.Layer.RemoveAnimation(kAnimationKey);
        };
    
        CABasicAnimation animation = new CABasicAnimation();
        animation.KeyPath = "bounds";
        animation.From = NSValue.FromRectangleF(oldBounds);
        animation.To = NSValue.FromRectangleF(newBounds);
        animation.TimingFunction = CAMediaTimingFunction.FromControlPoints(0.60f, 0.00f, 0.50f, 1.40f);
        animation.Duration = duration;
        animation.RemovedOnCompletion = false;
        animation.FillMode = CAFillMode.Forwards;
    
        scrollView.Layer.AddAnimation(animation, kAnimationKey);
    
        CATransaction.Commit();
    }
    
  • JonathanPeppersJonathanPeppers USInsider, Beta, University ✭✭

    Anyone else looking to do this, you can just do this once you've figured out the new bounds to apply:

    UIView.Animation(duration, () => scrollView.Bounds = bounds);

    If you want more control, in my case I needed BeginFromCurrentState because the animation could get fired again in the middle:

    UIView.BeginAnimations("ScrollLikeMichaelJacksonWould");
    UIView.SetAnimationCurve(UIViewAnimationCurve.EaseInOut);
    UIView.SetAnimationDuration(duration);
    UIView.SetAnimationBeginsFromCurrentState(true);
    
    collectionView.Bounds = bounds;
    
    UIView.CommitAnimations();
    

    No reason to mess with CABasicAnimation in my opinion.

Sign In or Register to comment.