How to both scroll and stop UIScrollView programmatically?

221 views Asked by At

There are a lot of similar questions but they all differ from this one.

I have UIScrollView which I could both scroll and stop programmatically.

I scroll via the following code:

[UIView animateWithDuration:3
                              delay:0
                            options:UIViewAnimationOptionCurveEaseInOut
                         animations:^{ [self.scrollView scrollRectToVisible:newPageRect animated:NO]; }];

And I don't know how to stop it at all. In all the cases it won't stop or will stop but it also jumps to newPageRect (for example in the case of removeAllAnimations).

Could you suggest how to stop it correctly? Should I possibly change my code for scrolling to another one?

1

There are 1 answers

1
Matic Oblak On BEST ANSWER

I think this is something you best do yourself. It may take you a few hours to create a proper library to animate data but in the end it can be very rewarding.

A few components are needed:

A time bound animation should include either a CADispalyLink or a NSTimer. Create a public method such as animateWithDuration: which will start the timer, record a current date and set the target date. Insert a floating value as a property which should then be interpolated from 0 to 1 through date. Will most likely look something like that:

- (void)onTimer {
    NSDate *currentTime = [NSDate date];
    CGFloat interpolation = [currentTime timeIntervalSinceDate:self.startTime]/[self.targetTime timeIntervalSinceDate:self.startTime];

    if(interpolation < .0f) { // this could happen if delay is implemented and start time may actually be larger then current
        self.currentValue = .0f;
    }
    else if(interpolation > 1.0f) { // The animation has ended
        self.currentValue = 1.0f;
        [self.displayLink invalidate]; // stop the animation
        // TODO: notify owner that the animation has ended
    }
    else {
        self.currentValue = interpolation;
        // TODO: notify owner of change made
    }
}

As you can see from the comments you should have 2 more calls in this method which will notify the owner/listener to the changes of the animation. This may be achieved via delegates, blocks, invocations, target-selector pairs...

So at this point you have a floating value interpolating between 0 and 1 which can now be used to interpolate the rect you want to be visible. This is quite an easy method:

- (CGRect)interpolateRect:(CGRect)source to:(CGRect)target withScale:(CGFloat)scale
{
    return CGRectMake(source.origin.x + (target.origin.x-source.origin.x)*scale,
                      source.origin.y + (target.origin.y-source.origin.y)*scale,
                      source.size.width + (target.size.width-source.size.width)*scale,
                      source.size.height + (target.size.height-source.size.height)*scale);
}

So now to put it all together it would look something like so:

- (void)animateVisibleRectTo:(CGRect)frame {
    CGRect source = self.scrollView.visibleRect;
    CGRect target = frame;
    [self.animator animateWithDuration:.5 block:^(CGFloat scale, BOOL didFinish) {
        CGRect interpolatedFrame = [Interpolator interpolateRect:source to:target withScale:scale];
        [self.scrollView scrollRectToVisible:interpolatedFrame animated:NO];
    }];
}

This can be a great system that can be used in very many systems when you want to animate something not animatable or simply have a better control over the animation. You may add the stop method which needs to invalidate the timer or display link and notify the owner.

What you need to look out for is not to create a retain cycle. If a class retains the animator object and the animator object retains the listener (the class) you will create a retain cycle.

Also just as a bonus you may very easily implement other properties of the animation such as delay by computing a larger start time. You can create any type of curve such as ease-in, ease-out by using an appropriate function for computing the currentValue for instance self.currentValue = pow(interpolation, 1.4) will be much like ease-in. A power of 1.0/1.4 would be a same version of ease-out.