Skip to content

Commit

Permalink
fix for auto layout animations + readme
Browse files Browse the repository at this point in the history
  • Loading branch information
icanzilb committed Sep 22, 2017
1 parent 498a884 commit a0831fe
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 64 deletions.
77 changes: 57 additions & 20 deletions Example/RxAnimated/Base.lproj/Main.storyboard

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion Example/RxAnimated/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
</array>
</dict>
</plist>
26 changes: 23 additions & 3 deletions Example/RxAnimated/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ class ViewController: UIViewController {
@IBOutlet var labelIsHidden: UILabel!
@IBOutlet var imageIsHidden: UIImageView!

@IBOutlet var leftConstraint: NSLayoutConstraint!
@IBOutlet var rightConstraint: NSLayoutConstraint!

private let timer = Observable<Int>.timer(0, period: 1, scheduler: MainScheduler.instance).shareReplay(1)
private let bag = DisposeBag()

Expand All @@ -34,21 +37,21 @@ class ViewController: UIViewController {

// Animate `text` with a crossfade
timer
.map { "label fade [\($0)]" }
.map { "Text + fade [\($0)]" }
.bind(animated: labelFade.rx.animated.fade(duration: 0.33).text)
.disposed(by: bag)

// Animate `text` with a top flip
timer
.delay(0.33, scheduler: MainScheduler.instance)
.map { "label flip top [\($0)]" }
.map { "Text + flip [\($0)]" }
.bind(animated: labelFlip.rx.animated.flip(.top, duration: 0.33).text)
.disposed(by: bag)

// Animate `text` with a custom animation `tick`, as driver
timer
.delay(0.67, scheduler: MainScheduler.instance)
.map { "custom tick animation [\($0)]" }
.map { "Text + custom [\($0)]" }
.asDriver(onErrorJustReturn: "error")
.bind(animated: labelCustom.rx.animated.tick(.left, duration: 0.75).text)
.disposed(by: bag)
Expand All @@ -64,6 +67,22 @@ class ViewController: UIViewController {
.bind(animated: imageFlip.rx.animated.tick(.right, duration: 1.0).image)
.disposed(by: bag)

// Animate layout constraint
timer
.scan(0) { acc, _ in
return acc == 0 ? 105 : 0
}
.bind(animated: leftConstraint.rx.animated.layout(duration: 0.33).constant )
.disposed(by: bag)

// Activate/Deactivate a constraint
timer
.scan(true) { acc, _ in
return !acc
}
.bind(animated: rightConstraint.rx.animated.layout(duration: 0.33).isActive )
.disposed(by: bag)

// Animate `alpha` with a flip
let timerAlpha = timer
.scan(1) { acc, _ in
Expand Down Expand Up @@ -92,6 +111,7 @@ class ViewController: UIViewController {
.map { "hidden: \($0)" }
.bind(to: labelIsHidden.rx.text)
.disposed(by: bag)

}

}
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![License](https://img.shields.io/cocoapods/l/RxAnimated.svg?style=flat)](http://cocoapods.org/pods/RxAnimated)
[![Platform](https://img.shields.io/cocoapods/p/RxAnimated.svg?style=flat)](http://cocoapods.org/pods/RxAnimated)

> **NB:** This is a pre-release software, currently the lib works with Xcode9 GM and RxSwift4 beta.0 - and Xcode doesn't offer autocomplete when you use it.
> **NB:** This is a pre-release software, currently the lib works with Xcode9 GM and RxSwift4 beta.0.

**RxAnimated** provides animation interface to RxCocoa's bindings.
Expand Down Expand Up @@ -46,7 +46,15 @@ List of built-in animated sinks:
UIView.rx.animated...isHidden
UIView.rx.animated...alpha
UILabel.rx.animated...text
UILabel.rx.animated...attributedText
UIControl.rx.animated...isEnabled
UIControl.rx.animated...isSelected
UIButton.rx.animated...title
UIButton.rx.animated...image
UIButton.rx.animated...backgroundImage
UIImageView.rx.animated...image
NSLayoutConstraint.rx.animated...constant
NSLayoutConstraint.rx.animated...isActive
```

List of the built-in animations:
Expand All @@ -55,8 +63,11 @@ List of the built-in animations:
UIView.rx.animated.fade(duration: TimeInterval)
UIView.rx.animated.flip(FlipDirection, duration: TimeInterval)
UIView.rx.animated.tick(FlipDirection, duration: TimeInterval)
NSLayoutConstraint.rx.animated.layout(duration: TimeInterval)
```

Check the demo app for a number of examples.

## Custom animations

You can easily add your custom bind animations to match the visual style of your app.
Expand Down
30 changes: 18 additions & 12 deletions RxAnimated/Core/RxAnimated+animations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,6 @@
import RxSwift
import RxCocoa

// MARK: - built-in animations

extension AnimatedSink where Base: UIView {
/// cross-dissolve animation on `UIView`
public func fade(duration: TimeInterval) -> AnimatedSink<Base> {
let type = AnimationType<Base>(type: RxAnimationType.transition(.transitionCrossDissolve), duration: duration, animations: nil)
return AnimatedSink<Base>(base: self.base, type: type)
}
}

/// custom direction enumeration
public enum FlipDirection {
case left, right, top, bottom
Expand All @@ -29,15 +19,21 @@ public enum FlipDirection {
}
}

// MARK: - built-in animations

extension AnimatedSink where Base: UIView {
/// cross-dissolve animation on `UIView`
public func fade(duration: TimeInterval) -> AnimatedSink<Base> {
let type = AnimationType<Base>(type: RxAnimationType.transition(.transitionCrossDissolve), duration: duration, animations: nil)
return AnimatedSink<Base>(base: self.base, type: type)
}

/// flip animation on `UIView`
public func flip(_ direction: FlipDirection, duration: TimeInterval) -> AnimatedSink<Base> {
let type = AnimationType<Base>(type: RxAnimationType.transition(direction.viewTransition), duration: duration, animations: nil)
return AnimatedSink<Base>(base: self.base, type: type)
}
}

extension AnimatedSink where Base: UIView {
/// example of extending RxAnimated with a custom animation
public func tick(_ direction: FlipDirection = .right, duration: TimeInterval) -> AnimatedSink<Base> {
let type = AnimationType<Base>(type: RxAnimationType.spring(damping: 0.33, velocity: 0), duration: duration, setup: { view in
Expand All @@ -50,3 +46,13 @@ extension AnimatedSink where Base: UIView {
return AnimatedSink<Base>(base: self.base, type: type)
}
}

extension AnimatedSink where Base: NSLayoutConstraint {
/// auto layout animations
public func layout(duration: TimeInterval) -> AnimatedSink<Base> {
let type = AnimationType<Base>(type: RxAnimationType.animation, duration: duration, animations: { view in
view.layoutIfNeeded()
})
return AnimatedSink<Base>(base: self.base, type: type)
}
}
45 changes: 29 additions & 16 deletions RxAnimated/Core/RxAnimated+bindings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,20 @@
import RxSwift
import RxCocoa

// MARK: - Reactive ext on UIView

extension Reactive where Base: UIView {
/// adds animated bindings to view classes under `rx.animated`
public var animated: AnimatedSink<Base> {
return AnimatedSink<Base>(base: self.base)
}
}

// MARK: - UIView
extension AnimatedSink where Base: UIView {
public var isHidden: Binder<Bool> {
return Binder(self.base) { view, hidden in
self.type.animate(view: view, block: {
self.type.animate(view: view, binding: {
view.isHidden = hidden
})
}
Expand All @@ -19,7 +28,7 @@ extension AnimatedSink where Base: UIView {
extension AnimatedSink where Base: UIView {
public var alpha: Binder<CGFloat> {
return Binder(self.base) { view, alpha in
self.type.animate(view: view, block: {
self.type.animate(view: view, binding: {
view.alpha = alpha
})
}
Expand All @@ -30,15 +39,15 @@ extension AnimatedSink where Base: UIView {
extension AnimatedSink where Base: UILabel {
public var text: Binder<String> {
return Binder(self.base) { label, text in
self.type.animate(view: label, block: {
self.type.animate(view: label, binding: {
guard let label = label as? UILabel else { return }
label.text = text
})
}
}
public var attributedText: Binder<NSAttributedString> {
return Binder(self.base) { label, text in
self.type.animate(view: label, block: {
self.type.animate(view: label, binding: {
guard let label = label as? UILabel else { return }
label.attributedText = text
})
Expand All @@ -50,15 +59,15 @@ extension AnimatedSink where Base: UILabel {
extension AnimatedSink where Base: UIControl {
public var isEnabled: Binder<Bool> {
return Binder(self.base) { control, enabled in
self.type.animate(view: control, block: {
self.type.animate(view: control, binding: {
guard let control = control as? UIControl else { return }
control.isEnabled = enabled
})
}
}
public var isSelected: Binder<Bool> {
return Binder(self.base) { control, selected in
self.type.animate(view: control, block: {
self.type.animate(view: control, binding: {
guard let control = control as? UIControl else { return }
control.isSelected = selected
})
Expand All @@ -70,23 +79,23 @@ extension AnimatedSink where Base: UIControl {
extension AnimatedSink where Base: UIButton {
public var title: Binder<String> {
return Binder(self.base) { button, title in
self.type.animate(view: button, block: {
self.type.animate(view: button, binding: {
guard let button = button as? UIButton else { return }
button.setTitle(title, for: button.state)
})
}
}
public var image: Binder<UIImage?> {
return Binder(self.base) { button, image in
self.type.animate(view: button, block: {
self.type.animate(view: button, binding: {
guard let button = button as? UIButton else { return }
button.setImage(image, for: button.state)
})
}
}
public var backgroundImage: Binder<UIImage?> {
return Binder(self.base) { button, image in
self.type.animate(view: button, block: {
self.type.animate(view: button, binding: {
guard let button = button as? UIButton else { return }
button.setBackgroundImage(image, for: button.state)
})
Expand All @@ -98,25 +107,30 @@ extension AnimatedSink where Base: UIButton {
extension AnimatedSink where Base: UIImageView {
public var image: Binder<UIImage?> {
return Binder(self.base) { imageView, image in
self.type.animate(view: imageView, block: {
self.type.animate(view: imageView, binding: {
guard let imageView = imageView as? UIImageView else { return }
imageView.image = image
})
}
}
}

// MARK: - NSLayoutConstraint

// TODO: test this in the demo app
// MARK: - Reactive ext on NSLayoutConstraint
extension Reactive where Base: NSLayoutConstraint {
/// adds animated bindings to view classes under `rx.animated`
public var animated: AnimatedSink<Base> {
return AnimatedSink<Base>(base: self.base)
}
}

// MARK: - NSLayoutConstraint
extension AnimatedSink where Base: NSLayoutConstraint {
public var constant: Binder<CGFloat> {
return Binder(self.base) { constraint, constant in
guard let view = constraint.firstItem as? UIView,
let superview = view.superview else { return }

self.type.animate(view: superview, block: {
self.type.animate(view: superview, binding: {
constraint.constant = constant
})
}
Expand All @@ -126,10 +140,9 @@ extension AnimatedSink where Base: NSLayoutConstraint {
guard let view = constraint.firstItem as? UIView,
let superview = view.superview else { return }

self.type.animate(view: superview, block: {
self.type.animate(view: superview, binding: {
constraint.isActive = active
})
}
}
}

15 changes: 4 additions & 11 deletions RxAnimated/Core/RxAnimated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,25 +54,25 @@ public struct AnimationType<Base> {
* - parameter view: a view to run the animations on
* - parameter block: a custom block to inject inside the animation
*/
func animate(view: UIView, block: (()->Void)?) {
func animate(view: UIView, binding: (()->Void)?) {
setup?(view)

DispatchQueue.main.async {
switch self.type {
case .animation:
UIView.animate(withDuration: self.duration, delay: 0, options: self.options, animations: {
binding?()
self.animations?(view)
block?()
}, completion: self.completion)
case .transition(let type):
UIView.transition(with: view, duration: self.duration, options: self.options.union(type), animations: {
binding?()
self.animations?(view)
block?()
}, completion: self.completion)
case .spring(let damping, let velocity):
UIView.animate(withDuration: self.duration, delay: 0, usingSpringWithDamping: damping, initialSpringVelocity: velocity, options: self.options, animations: {
binding?()
self.animations?(view)
block?()
}, completion: self.completion)
}
}
Expand All @@ -95,13 +95,6 @@ extension SharedSequence {
}
}

extension Reactive where Base: UIView {
/// adds animated bindings to view classes under `rx.animated`
public var animated: AnimatedSink<Base> {
return AnimatedSink<Base>(base: self.base)
}
}

/// an animations proxy data type
public struct AnimatedSink<Base> {
internal var type: AnimationType<Base>!
Expand Down

0 comments on commit a0831fe

Please sign in to comment.