Skip to content

Instantly share code, notes, and snippets.

@myurieff
Created May 18, 2019 18:50
Show Gist options
  • Save myurieff/f137ed609b2c822ac61a1bc079183344 to your computer and use it in GitHub Desktop.
Save myurieff/f137ed609b2c822ac61a1bc079183344 to your computer and use it in GitHub Desktop.
Simple animatable bindings using RxSwift + RxCocoa
// Our binder will work universally for every UIView subclass.
extension Reactive where Base: UIView {
/// Provides an observer that will animate incoming values.
///
/// - Parameters:
/// - keyPath: keyPath to the property that will be mutated and animated.
/// - animator: the animator instance that will be used to animate changes.
/// - Returns: a Binder instance.
public func animated<Value>(
// using keyPaths will allow us to have a single function that will work
// on every property of the Base class.
// Otherwise we would have to write one for every property that we might
// want to animate later on (alpha, frame and so on).
// Of course, it is your responsibility to use it only with animatable properties.
_ keyPath: ReferenceWritableKeyPath<Base, Value>,
// I am a big fan of UIViewPropertyAnimator and I think it works wonderfully
// for the current use case. Alternatively, we can have an UIView.AnimationOptions
// parameter here or some other sort of a configuration that will later on be used
// for performing the animation.
with animator: UIViewPropertyAnimator
) -> Binder<Value> {
return Binder(self.base) { view, newValue in
// This is what essentially your ordinary Binder looks like.
// Here we're just setting the newValue inside an animation closure.
animator.addAnimations {
view[keyPath: keyPath] = newValue
}
// And then triggering the animator.
animator.startAnimation()
}
}
}
extension UIViewPropertyAnimator {
// If you use the same animation style all over the app, you can save yourself some code
// by making a static property that represents that animation.
// As an example, I like fading out / in views with a short, ease both curve animation,
// as it looks smoother than doing it instantly, and also is short enough to not be
// distracting or annoying when it happens all the time.
static let short = UIViewPropertyAnimator(duration: 0.2, curve: .easeInOut, animations: nil)
}
// Using all of this is super clean and simple.
// Instead of the usual
itemsCount.map { ($0 == 0) ? 0.4 : 1.0 }.drive(itemBadge.rx.alpha).disposed(by: bag)
// we now use our new binder function
itemsCount.map { ($0 == 0) ? 0.4 : 1.0 }.drive(itemBadge.rx.animated(\.alpha, with: .short)).disposed(by: bag)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment