Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom UIRefreshControl with Lottie

My goal is to replace the default spinner of the UIRefreshControl with a Lottie animation.

My issue is that the animation does not play immediately when I pull down my UICollectionView whose subview is the UIRefreshControl. The animation only plays when I scroll down slightly and pause my finger. The moment I again start moving the scrolling position it immediately goes back to its initial starting state not playing.

Any guidance would be appreciated, below is relevant code..

func setupRefreshControl() {
    guard let refreshContents = Bundle.main.loadNibNamed("RefreshControlView", owner: self, options: nil), let refreshView = refreshContents[0] as? RefreshControlView else { return }

    refreshView.frame = refreshControl.frame
    refreshControl.addSubview(refreshView)

    refreshControl.tintColor = UIColor.clear
    refreshControl.backgroundColor = UIColor.clear
    refreshControl.addTarget(self, action: #selector(exampleFunction), for: .valueChanged)      
}

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    for subview in refreshControl.subviews {
        if let refreshControlView = subview as? RefreshControlView {
            refreshControlView.activityIndicatorControl.animationView.setAnimation(named: "lottieJson")                
            refreshControlView.activityIndicatorControl.animationView.loopAnimation = true
            refreshControlView.activityIndicatorControl.animationView.play()
            break
        } else {
            continue
        }
    }
}
like image 946
Chris Avatar asked Nov 30 '22 14:11

Chris


1 Answers

You don't see the animation playing while you are scrolling because every time scrollViewDidScroll is called you restart the animation by calling play. And because that function is called almost constantly while you scroll the animation does not have a chance to play more than its first frame. So it looks paused.

When implementing a custom Refresh Control you have to implement 3 phases:

1. User scrolls but the refresh has not been triggered yet

In this phase you probably want to show a progress in your animation depending on how far the user has scrolled. To do this you calculate the progress in scrollViewDidScroll and pass it to the LOTAnimationView's animationProgress property.

To calculate this progress you have to know how far the user has to scroll down until the refresh is triggered. In my experience this happens at a contentOffset.y of approximately 150.

2. Refresh is triggered

When the user has scrolled down enough a .valueChanged ControlEvent is triggered. When this happens you start the looping animation by calling play() on the LOTAnimationView

3. Refresh is done

When the refreshing is completed you call endRefreshing() on your custom Refresh Control. When this happens you stop the animation by calling stop() on the LOTAnimationView

Check out this small example of a LottieRefreshControl that I use in one of my projects:

import UIKit
import Lottie

class LottieRefreshControl: UIRefreshControl {
fileprivate let animationView = Lottie.AnimationView(name: "searchAnimation")
fileprivate var isAnimating = false

fileprivate let maxPullDistance: CGFloat = 150

override init() {
    super.init(frame: .zero)
    setupView()
    setupLayout()
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

func updateProgress(with offsetY: CGFloat) {
    guard !isAnimating else { return }
    let progress = min(abs(offsetY / maxPullDistance), 1)
    animationView.currentProgress = progress
}

override func beginRefreshing() {
    super.beginRefreshing()
    isAnimating = true
    animationView.currentProgress = 0
    animationView.play()
}

override func endRefreshing() {
    super.endRefreshing()
    animationView.stop()
    isAnimating = false
}
}

private extension LottieRefreshControl {
func setupView() {
    // hide default indicator view
    tintColor = .clear
    animationView.loopMode = .loop
    addSubview(animationView)

    addTarget(self, action: #selector(beginRefreshing), for: .valueChanged)
}

func setupLayout() {
    animationView.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
        animationView.centerXAnchor.constraint(equalTo: centerXAnchor),
        animationView.centerYAnchor.constraint(equalTo: centerYAnchor),
        animationView.widthAnchor.constraint(equalToConstant: 50),
        animationView.heightAnchor.constraint(equalToConstant: 32)
    ])
}
}

Now you only have to call updateProgress on the Refresh Control from scrollViewDidScroll:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    refreshControl.updateProgress(with: scrollView.contentOffset.y)
}
like image 166
joern Avatar answered Dec 10 '22 13:12

joern