Animating UICollectionView contentOffset does not display non-visible cells

后端 未结 7 2179
旧巷少年郎
旧巷少年郎 2021-02-01 18:31

I\'m working on some ticker-like functionality and am using a UICollectionView. It was originally a scrollView, but we figure a collectionView will make it easier

7条回答
  •  猫巷女王i
    2021-02-01 19:10

    I've built upon what's already in these answers and made a generic manual animator, as everything can be distilled down to a percentage float value and a block.

    class ManualAnimator {
        
        enum AnimationCurve {
            
            case linear, parametric, easeInOut, easeIn, easeOut
            
            func modify(_ x: CGFloat) -> CGFloat {
                switch self {
                case .linear:
                    return x
                case .parametric:
                    return x.parametric
                case .easeInOut:
                    return x.quadraticEaseInOut
                case .easeIn:
                    return x.quadraticEaseIn
                case .easeOut:
                    return x.quadraticEaseOut
                }
            }
            
        }
        
        private var displayLink: CADisplayLink?
        private var start = Date()
        private var total = TimeInterval(0)
        private var closure: ((CGFloat) -> Void)?
        private var animationCurve: AnimationCurve = .linear
        
        func animate(duration: TimeInterval, curve: AnimationCurve = .linear, _ animations: @escaping (CGFloat) -> Void) {
            guard duration > 0 else { animations(1.0); return }
            reset()
            start = Date()
            closure = animations
            total = duration
            animationCurve = curve
            let d = CADisplayLink(target: self, selector: #selector(tick))
            d.add(to: .current, forMode: .common)
            displayLink = d
        }
    
        @objc private func tick() {
            let delta = Date().timeIntervalSince(start)
            var percentage = animationCurve.modify(CGFloat(delta) / CGFloat(total))
            //print("%:", percentage)
            if percentage < 0.0 { percentage = 0.0 }
            else if percentage >= 1.0 { percentage = 1.0; reset() }
            closure?(percentage)
        }
    
        private func reset() {
            displayLink?.invalidate()
            displayLink = nil
        }
    }
    
    extension CGFloat {
        
        fileprivate var parametric: CGFloat {
            guard self > 0.0 else { return 0.0 }
            guard self < 1.0 else { return 1.0 }
            return ((self * self) / (2.0 * ((self * self) - self) + 1.0))
        }
        
        fileprivate var quadraticEaseInOut: CGFloat {
            guard self > 0.0 else { return 0.0 }
            guard self < 1.0 else { return 1.0 }
            if self < 0.5 { return 2 * self * self }
            return (-2 * self * self) + (4 * self) - 1
        }
        
        fileprivate var quadraticEaseOut: CGFloat {
            guard self > 0.0 else { return 0.0 }
            guard self < 1.0 else { return 1.0 }
            return -self * (self - 2)
        }
        
        fileprivate var quadraticEaseIn: CGFloat {
            guard self > 0.0 else { return 0.0 }
            guard self < 1.0 else { return 1.0 }
            return self * self
        }
    }
    

    Implementation

    let initialOffset = collectionView.contentOffset.y
    let delta = collectionView.bounds.size.height
    let animator = ManualAnimator()
    animator.animate(duration: TimeInterval(1.0), curve: .easeInOut) { [weak self] (percentage) in
        guard let `self` = self else { return }
        self.collectionView.contentOffset = CGPoint(x: 0.0, y: initialOffset + (delta * percentage))
        if percentage == 1.0 { print("Done") }
    }
    

    It might be worth combining the animate function with an init method.. it's not a huge deal though.

提交回复
热议问题