UIScrollView scroll event blocks UIView animation

2019-03-16 01:17发布

问题:

I have an animating set of UIImageView's that represent a current value and continually keeps ticking upwards...ideally never stopping, also in the view is a scrollView or to and whenever I'm scrolling or zooming in the scrollView, the animation stops, and starts up again when the scrollView stops moving completely. I believe this is due to a threading issue since redrawing elements all happens on the main thread, At first i tried UIView animation and then even core animation to no effect...Is there a way to have my cake and eat it too?

Any and all help would be appreciated

code follows

- (void)TestJackpotAtRate:(double)rateOfchange
{
    double roc = rateOfchange;

    for (int i = 0; i < [_jackPotDigits count]; ++i)
    {
        roc = rateOfchange/(pow(10, i));

        UIImageView *jackpotDigit = [_jackPotDigits objectAtIndex:i];

        float foreveryNseconds = 1/roc;

        NSDictionary *dict = @{@"interval"      :   [NSNumber numberWithFloat:foreveryNseconds],
                           @"jackPotDigit"  :   jackpotDigit
                           };

        [NSTimer scheduledTimerWithTimeInterval:foreveryNseconds target:self selector:@selector(AscendDigit:) userInfo:dict repeats:YES];
    }
}

-(void)AscendDigit:(NSTimer*)timer
{
    NSDictionary *dict = [timer userInfo];

    NSTimeInterval interval = [(NSNumber*)[dict objectForKey:@"interval"] floatValue];
    UIImageView *jackpotDigit = [dict objectForKey:@"jackPotDigit"];

    float duration = (interval < 1) ? interval : 1;

    if (jackpotDigit.frame.origin.y < -230 )
    {
        NSLog(@"hit");
        [timer invalidate];
        CGRect frame = jackpotDigit.frame;
        frame.origin.y = 0;
        [jackpotDigit setFrame:frame];

        [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(AscendDigit:) userInfo:dict repeats:YES];
    }

    [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionAllowUserInteraction animations:^
     {
         CGRect frame = [jackpotDigit frame];

         double yDisplacement = 25;

         frame.origin.y -= yDisplacement;

         [jackpotDigit setFrame:frame];

     }
                      completion:^(BOOL finished)
     {

     }];

}

回答1:

As danypata pointed out in my comments via this thread My custom UI elements are not being updated while UIScrollView is scrolled It has something to do with the NStimer thread rather than the animation thread, or possibly both if someone can clarify. In any case when scrolling it seems all scroll events get exclusive use of the main loop, the solution is to put the timer you are using to do your animation into the same loop mode which is UITrackingLoopMode so it too get's use of the main loop when scrolling and...

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:foreveryNseconds target:self selector:@selector(AscendDigit:) userInfo:dict repeats:YES];

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

Tada.



回答2:

There is another way to control your animations during UIScrollview scroll with scrollView delegate methods; start your animations with Timer than stop the timer in scrollViewWillBeginDragging method and continue to your animations with displayLink etc. for example :

// Declare an displayLink
var displayLink: CADisplayLink?

// delegate your scrollview
self.scrollView.delegate = self

// start your anmiations
override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    self.startTimer()
}

// animation block
@objc private func shakeStartButton() {...}

// your animation timer
private func startTimer() {
    timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.shakeStartButton), userInfo: nil, repeats: true)
    timer?.fire()
}

private func startDisplayLinkIfNeeded() {
    if displayLink == nil {
        self.displayLink = CADisplayLink(target: self, selector: #selector(self.shakeStartButton))
        displayLink?.add(to: .main, forMode: .tracking)
        // Render frame only one time in second
        displayLink?.preferredFramesPerSecond = 1
    }
}

private func stopDisplayLink() {

    displayLink?.invalidate()
    displayLink = nil
}

extension YourViewController: UIScrollViewDelegate {

 func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {

    self.timer?.invalidate()
    self.startDisplayLinkIfNeeded()
 }

 func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {

    if !decelerate {
        self.stopDisplayLink()
        self.startTimer()
    }
 } 

 func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {

    self.stopDisplayLink()
    self.startTimer()
 }
}

Note: This is litte bit longer than @gehan answer but it may help with other things also?