Godfather
Godfather

Reputation: 4340

RxSwift bind to with animation with RxCocoa

I have a UIStepper value binded to a UILabel text:

    unitsStepper.rx.value.asObservable()
        .map { Int($0).description }
        .bind(to: stepperCountLabel.rx.text)
        .disposed(by: rx.disposeBag)

And i would like to animate the label every time it changes, theres a better way to that instead of this?

    unitsStepper.rx.value.asObservable()
        .map { Int($0).description }.subscribe(onNext: { [weak self] value in
            guard let strongSelf = self else { return }
            UIView.animate(withDuration: 0.4, animations: {
                strongSelf.stepperCountLabel.text = value
                strongSelf.stepperCountLabel.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
            }, completion: { completed in
                strongSelf.stepperCountLabel.transform = CGAffineTransform.identity
            })

        }).disposed(by: rx.disposeBag)

Upvotes: 1

Views: 1939

Answers (1)

Chris Conover
Chris Conover

Reputation: 9039

Personally, I don't think there is anything wrong with that. If you wanted it to be more readable, less nested, then you can break it out a bit. Your needs will vary, and you don't want to pollute something general with something very specific, but it looks like you have something repetitive, a sort of pulsate.

I needed something like that for form validation, and wrote this (your actual animation is simpler though, I think I started with a multi-part key frame animation but reduced it to just back and forth)

extension UIView {

    func shake(totalDuration: TimeInterval, totalCycles: Int = 1) {

        let animationDuration = totalDuration / Double(totalCycles)
        let wiggle = CAKeyframeAnimation(keyPath: "transform.rotation.z")
        wiggle.values = [0.0, -0.04, 0.04, 0.0]
        wiggle.duration = animationDuration
        wiggle.repeatCount = Float(totalCycles)
        layer.add(wiggle, forKey: "wiggle")

        let vertical = CAKeyframeAnimation(keyPath: "transform.translation.y")
        vertical.values = [0.0, -2.0, 2.0, 0]
        vertical.duration = animationDuration
        vertical.repeatCount = Float(totalCycles)
        layer.add(vertical, forKey: "vertical")

        let horizontal = CAKeyframeAnimation(keyPath: "transform.translation.x")
        horizontal.values = [0.0, 2.0, -2.0, 0]
        horizontal.duration = animationDuration
        horizontal.repeatCount = Float(totalCycles)
        layer.add(horizontal, forKey: "horizontal")
    }

    func pulse(totalDuration: TimeInterval, ratio: CGFloat = 1.2) {

        UIView.animateKeyframes(
            withDuration: totalDuration,
            delay: 0,
            options: [],
            animations: {
                UIView.addKeyframe(
                    withRelativeStartTime: 0,
                    relativeDuration: 0.5,
                    animations: { [unowned self] in
                        self.transform = .init(scaleX: ratio, y: ratio)

                })
                UIView.addKeyframe(
                    withRelativeStartTime: 0.5,
                    relativeDuration: 0.5,
                    animations: { [unowned self] in
                        self.transform = .identity
                })
        }, completion: { completed in })
    }
}

I then wrote a simple validator struct with a condition and action closure, and would zip through a list of them upon form changes / validation.

But back to your question, your animation itself is minimal, if it was a one-off, you could put it on the enclosing class, if it was general, you could add it as an extension, but as a one-off, it's still fine as it is (if I were reading the code)

Upvotes: 0

Related Questions