PendingAnimationHandler

 avatar
unknown
swift
3 years ago
4.4 kB
1
Indexable
class PendingAnimationHandler {
    private var animationsCount: Int = 0
    private let lock = NSLock()
    private var pendingCompletions: [() -> Void] = []

    private var isAnimationRunning: Bool { animationsCount != 0 }
    var debugIsAnimationRunning: Bool {
        animationsCount != 0
    }

    func animationStarted() {
        lock.critical {
            animationsCount += 1
        }
    }

    func animationEnded() {
        lock.critical {
            animationsCount -= 1
            if !isAnimationRunning {
                pendingCompletions.forEach { $0() }
                pendingCompletions = []
            }
        }
    }

    func afterAnimations(_ closure: @escaping () -> Void) {
        lock.critical {
            if isAnimationRunning {
                pendingCompletions.append(closure)
            } else {
                closure()
            }
        }
    }
}


final class PollAnimationViewController: UIViewController {
    ...
    
    let animationsHelper = PendingAnimationHandler()

    ...

    @objc private func someFuncTriggeresUpdate() {
        var nextView: UIView
        ...
        // Обновляем. Анимация - 2 секунды
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            cell.containerView.swap(to: nextView, animated: true, using: animationsHelper)
        }
        // имитируем обновление tabelView
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.1) {
            let numbers = [1, 2, 3]
                .map { _ -> Int in Int.random(in: 0...100) }
                .map { "\($0)" }
            animationsHelper.afterAnimations {
                self.viewModel = ViewModel(titles: numbers)
                tableView.reloadData()
            }
        }
    }
}




class ContainerView: UIView {

    var delegate: ContainerViewDelegate?

    ...

    func swap(
        to nextView: UIView,
        animated: Bool,
        using animationsHelper: PendingAnimationHandler
    ) {
        guard shouldChangeView(to: nextView) else { return }
        layoutNextView(nextView: nextView)
        if animated { animateChange(nextView: nextView, duration: 2, using: animationsHelper) }
        else { changeWithoutAnimation(nextView: nextView) }

    }

    private func layoutNextView(nextView: UIView) {
        nextView.translatesAutoresizingMaskIntoConstraints = false
        addSubview(nextView)
        NSLayoutConstraint.activate([
            nextView.leadingAnchor.constraint(equalTo: leadingAnchor),
            nextView.trailingAnchor.constraint(equalTo: trailingAnchor),
            nextView.topAnchor.constraint(equalTo: topAnchor),
        ])
        bottomC?.isActive = false
        layoutIfNeeded()
    }

    

    private func animateChange(nextView: UIView, duration: Int, using animationsHelper: PendingAnimationHandler) {
        animationsHelper.animationStarted()
        let heightConstraint = heightAnchor.constraint(equalToConstant: 30)
        heightConstraint.isActive = true
        nextView.alpha = 0

        nextView.transform = CGAffineTransform(
            translationX: 0,
            y: nextView.frame.height
        )

        UIView.animate(withDuration: TimeInterval(duration)) { [weak self] in
            guard let `self` = self else { return }
            self.contentView.alpha = 0
            self.contentView.transform = CGAffineTransform(
                translationX: 0,
                y: self.contentView.frame.height
            )

            nextView.transform = CGAffineTransform(
                translationX: 0,
                y: 0
            )
            nextView.alpha = 1

            self.delegate?.containerViewNeedsLayout(self, height: nextView.frame.height, animated: true)
            self.superview?.layoutIfNeeded()
        } completion: { [weak self] _ in
            guard let `self` = self else { return }

            self.contentView.removeFromSuperview()

            heightConstraint.isActive = false
            self.bottomC = nextView.bottomAnchor.constraint(equalTo: self.bottomAnchor)
            self.bottomC?.isActive = true

            self._contentView = nextView
            animationsHelper.animationEnded()
        }
    }

    private func changeWithoutAnimation(nextView: UIView) { ... }

    private func shouldChangeView(to nextView: UIView) -> Bool { !(nextView === contentView) }
}
Editor is loading...