Controlling "animations" with a UIScrollView

This is something that I've done a number of times myself but also something that I see fairly regularly popping up on Stack Overflow.

The idea is to change something based on the content offset of a scroll view.

Example of changing the background colour based on scroll position. I wouldn't actually do this in a real app just showing the idea.

The required effect is almost always based on having a begin state when the scroll view is not scrolled at all and an end state when the scroll view is scrolled to its maximum scroll distance.

Normally I write this from scratch but I've decided to blog this so people can use it as a resource.

The way I approach this is to work everything out in percentages. I don't want to get into actual points because this can (and will) change depending on device size etc...

In the scroll view delegate method you can have something like this...

// this just calculates the percentages and passes the calculated values off to another method.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    // horizontal
    CGFloat maximumHorizontalOffset = scrollView.contentSize.width - CGRectGetWidth(scrollView.frame);
    CGFloat currentHorizontalOffset = scrollView.contentOffset.x;

    // vertical
    CGFloat maximumVerticalOffset = scrollView.contentSize.height - CGRectGetHeight(scrollView.frame);
    CGFloat currentVerticalOffset = scrollView.contentOffset.y;    
    
    // percentages
    CGFloat percentageHorizontalOffset = currentHorizontalOffset / maximumHorizontalOffset;
    CGFloat percentageVerticalOffset = currentVerticalOffset / maximumVerticalOffset;
    
    [self scrollView:scrollView didScrollToPercentageOffset:CGPointMake(percentageHorizontalOffset, percentageVerticalOffset)];
}

This takes the content offset of the scroll view and turns it into a "percentageOffset".

Percentage Offset (CGPoint)
  • (0, 0) means that the scrollView is not scrolled. This is when contentOffset is (0, 0).
  • (1, 1) means that the scrollView is scrolled to its maximum contentOffset both horizontally and vertically.
  • (0, 0.5) (for example) means that the scroll view is not scrolled horizontally and is at 50% of it's maximum content offset vertically.

This CGPoint is then passed into the next function...

- (void)scrollView:(UIScrollView *)scrollView didScrollToPercentageOffset:(CGPoint)percentageOffset
{
    // here I set the backgroundColor of the text view to a color calculated in the next method
    self.textView.backgroundColor = [self HSBColorForOffsetPercentage:percentageOffset.y];
}

Now I can start doing work. I no longer have to worry about contentOffsets and points. I can just work with the percentageOffset argument that gets passed into me.

The next method does the actual work in my example.

- (UIColor *)HSBColorForOffsetPercentage:(CGFloat)percentage
{
    // set the begin and end "states"
    CGFloat minColorHue = 0.0;
    CGFloat maxColorHue = 0.18;
    
    // calculate the current state from the percentage
    CGFloat actualHue = (maxColorHue - minColorHue) * percentage + minColorHue;
    
    // make the colour from that state
    return [UIColor colorWithHue:actualHue saturation:1.0 brightness:1.0 alpha:1.0];
}

In this method I set my minimum and maximum state and use the percentage value to calculate the required value between that maximum and minimum.

This is everything I need. There is no need to "animate" anything as we are not doing anything over time. All we are doing is responding to changing values from the scroll view. The scroll view handles all aspects of "animation" for us.

Using this method it's possible to control almost anything using a scroll view.

You can control the size, rotation, opacity, colour, position, of anything. You can even change multiple aspects or change different values based of horizontal and vertical scrolling (just use the x and y percentages different).

I've used this exact technique before for creating a fancy scroll on boarding screen thingy.

If you create key frames in the animation you can then just interpolate these key frames based on percentages along the scroll view. Really easy to do from scratch too.

In fact, I have used this so much that it makes me wonder whether there should be a percentageOffset type delegate method on scroll view? LOL!

4 Comments

  1. Skylar Thomas

    Hey Oliver,
    My name is Skylar and I'm a 16 year old developer. I wanted to say first how much I've loved your tutorials and how cool it is that you've done what a lot of other developers haven't.
    I wanted to ask you a question regarding your controlling animation on scrollview tutorial -
    I am working in Swift and have converted your percentage offset formula to Swift. I am trying to control a move animation (as you mention in your tutorial) as well as a fill animation using a cashapelayer to animate an object when it is scrolled in and out of visibility.
    I am having trouble applying your logic altering background color to this situation.

    Is there any way you would provide a Swift example of a move animation controlled by scroll? I'm really having trouble with this part on my project and could use some help from the expert either just by email or in the comments.

    Thanks so much Oliver. Again, great tutorials.

  2. oliverfoggin

    Hi Skylar,

    Thanks for the comment.

    The part of the scrollview delegate methods that you keep is the part that calculates the percentage offset.

    For movement what you can do is determine a start point (say 10) and an end point (say 70) of your movement. Then you need to calculate how the percentage moves between them. So 0% = 10 and 100% = 70.

    So x% (x from 0.0 to 1.0) = (70 - 10) * x + 10

    In a more general way...

    pointY = (maximumY - minimumY) * x + minimumY

    That should then animate between the two values as you scroll.

    I hope that helps with your work.

    Let me know if you need any more help.

    Thanks

    Oliver

  3. oliverfoggin

    That's great! Actually, the first time I used this was for creating an animated on boarding screen. It worked great and in the end we dropped JazzHands and used this instead 😀

Leave a Reply

Your email address will not be published. Required fields are marked *