Skip to content

Commit

Permalink
Merge pull request #151 from feature/UIViewPropertyAnimator+Rx
Browse files Browse the repository at this point in the history
Add UIViewPropertyAnimator reactive extension
  • Loading branch information
freak4pc authored Apr 6, 2018
2 parents 78a0d11 + 2c5c03e commit 7a9728f
Show file tree
Hide file tree
Showing 9 changed files with 255 additions and 8 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ master
-----
- added `withUnretained(_:)` operator

3.3.0
-----
- Added UIViewPropertyAnimator Reactive Extensions (`animate()` operator)

3.2.0
-----
- added `mapAt(keyPath:)` operator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
- [filterMap()](filterMap) operator, filters out some values and maps the rest (replaces `filter` + `map` combo)
- [Observable.fromAsync()](fromAsync) constructor, translates an async function that returns data through a completionHandler in a function that returns data through an Observable
- [ofType()](ofType) operator, filters the elements of an observable sequence, if that is an instance of the supplied type.
- **UIViewPropertyAnimator** [animate()](UIViewPropertyAnimator+Rx) operator, returns a Completable that completes as soon as the animation ends.
*/

//: [Next >>](@next)
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*:
> # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please:

1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed
1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios`
1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target
1. Choose `View > Show Debug Area`
*/

//: [Previous](@previous)

import RxSwift
import RxCocoa
import RxSwiftExt
import PlaygroundSupport
import UIKit

/*:
## animate

The `animate` operator provides a Completable that triggers the animation upon subscription and completes when the animation ends.

Please open the Assistant Editor (⌘⌥⏎) to see the Interactive Live View example.
*/

class AnimateViewController: UIViewController {

let disposeBag = DisposeBag()

lazy var box1: UIView = {
let view = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
view.backgroundColor = .red
return view
}()

lazy var box2: UIView = {
let view = UIView(frame: CGRect(x: 100, y: 220, width: 100, height: 100))
view.backgroundColor = .green
return view
}()

lazy var box3: UIView = {
let view = UIView(frame: CGRect(x: 100, y: 340, width: 100, height: 100))
view.backgroundColor = .blue
return view
}()

lazy var button: UIButton = {
let button = UIButton(frame: CGRect(x: 100, y: 500, width: 200, height: 50))
button.setTitle("Play animation", for: .normal)
button.setTitleColor(.blue, for: .normal)
button.backgroundColor = .white
button.layer.borderColor = UIColor.black.cgColor
button.layer.borderWidth = 2

return button
}()

var animator1: UIViewPropertyAnimator!
var animator2: UIViewPropertyAnimator!
var animator3: UIViewPropertyAnimator!

private func makeAnimators() {
animator1 = UIViewPropertyAnimator(duration: 0.3, curve: .easeInOut) { [unowned self] in
self.box1.transform = self.box1.transform != .identity ? .identity
: self.box1.transform.translatedBy(x: 0, y: -100)
}

animator2 = UIViewPropertyAnimator(duration: 0.25, curve: .easeInOut) { [unowned self] in
self.box2.transform = self.box2.transform != .identity ? .identity
: self.box2.transform
.translatedBy(x: 0, y: -100)
.scaledBy(x: 1.2, y: 1.2)
}

animator3 = UIViewPropertyAnimator(duration: 0.15, curve: .easeInOut) { [unowned self] in
self.box3.transform = self.box3.transform != .identity ? .identity
: self.box3.transform
.translatedBy(x: 0, y: -100)
.rotated(by: .pi)
}
}

override func viewDidLoad() {
super.viewDidLoad()

// construct the main view
let views = [box1, box2, box3, button]
view.backgroundColor = .white

views.forEach {
view.addSubview($0)
}

makeAnimators()

// Trigger chained animations after a button tap
button.rx.tap
.flatMap { [unowned self] in
self.animator1.rx.animate()
.andThen(self.animator2.rx.animate(afterDelay: 0.15))
.andThen(self.animator3.rx.animate(afterDelay: 0.1))
.do(onCompleted: {
self.makeAnimators()
})
.debug("animation sequence")
}
.subscribe()
.disposed(by: disposeBag)
}
}

// Present the view controller in the Live View window
PlaygroundPage.current.liveView = AnimateViewController()

//: [Next](@next)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Timeline
version = "3.0">
<TimelineItems>
</TimelineItems>
</Timeline>
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@
<page name='zipWith'/>
<page name='ofType'/>
<page name='withUnretained'/>
<page name='UIViewProperty+Rx'/>
</pages>
</playground>
41 changes: 33 additions & 8 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
RxSwiftExt
===========

If you're using [RxSwift](https://github.com/ReactiveX/RxSwift), you may have encountered situations where the built-in operators do not bring the exact functionality you want. The RxSwift core is being intentionally kept as compact as possible to avoid bloat. This repository's purpose is to provide additional convenience operators.
If you're using [RxSwift](https://github.com/ReactiveX/RxSwift), you may have encountered situations where the built-in operators do not bring the exact functionality you want. The RxSwift core is being intentionally kept as compact as possible to avoid bloat. This repository's purpose is to provide additional convenience operators and Reactive Extensions.

Installation
===========
Expand All @@ -15,7 +15,6 @@ This branch of RxSwiftExt targets Swift 4.x and RxSwift 4.0.0 or later.
* If you're looking for the Swift 3 version of RxSwiftExt, please use version `2.5.1` of the framework.
* If your project is running on Swift 2.x, please use version `1.2` of the framework.


#### CocoaPods

Using Swift 4:
Expand Down Expand Up @@ -44,11 +43,14 @@ Add this to your `Cartfile`
github "RxSwiftCommunity/RxSwiftExt"
```


Operators
===========

RxSwiftExt is all about adding operators to [RxSwift](https://github.com/ReactiveX/RxSwift)! Currently available operators:
RxSwiftExt is all about adding operators and Reactive Extensions to [RxSwift](https://github.com/ReactiveX/RxSwift)!

## Operators

These operators are much like the RxSwift & RxCocoa core operators, but provide additional useful abilities to your Rx arsenal.

* [unwrap](#unwrap)
* [ignore](#ignore)
Expand All @@ -71,13 +73,21 @@ RxSwiftExt is all about adding operators to [RxSwift](https://github.com/Reactiv
* [Observable.zip(with:)](#zipwith)
* [withUnretained](#withunretained)

Two additional operators are available for `materialize()`'d sequences:
There are two more available operators for `materialize()`'d sequences:

* [errors](#errors-elements)
* [elements](#errors-elements)

Read below for details about each operator.

## Reactive Extensions

RxSwift/RxCocoa Reactive Extensions are provided to enhance existing objects and classes from the Apple-ecosystem with Reactive abilities.

* [UIViewPropertyAnimator.animate](#uiviewpropertyanimatoranimate)

--------

Operator details
===========

Expand Down Expand Up @@ -154,7 +164,7 @@ completed
Pass elements through only if they were never seen before in the sequence.

```swift
Observable.of("a","b","a","c","b","a","d")
Observable.of("a","b","a","c","b","a","d")
.distinct()
.subscribe { print($0) }
```
Expand Down Expand Up @@ -512,9 +522,9 @@ completed
```
This example emits 2, 5 (`NSDecimalNumber` Type).

### withUnretained
#### withUnretained

The `withUnretained(_:)` operator provides an unretained, safe to use (i.e. not implicitly unwrapped), reference to an object along with the events emitted by the sequence.
The `withUnretained(_:resultSelector:)` operator provides an unretained, safe to use (i.e. not implicitly unwrapped), reference to an object along with the events emitted by the sequence.
In the case the provided object cannot be retained successfully, the seqeunce will complete.

```swift
Expand Down Expand Up @@ -546,6 +556,21 @@ next((Test Class, 13))
completed
```

Reactive Extensions details
===========

#### UIViewPropertyAnimator.animate

The `animate(afterDelay:)` operator provides a Completable that triggers the animation upon subscription and completes when the animation ends.

```swift
button.rx.tap
.flatMap {
animator1.rx.animate()
.andThen(animator2.rx.animate(afterDelay: 0.15))
.andThen(animator3.rx.animate(afterDelay: 0.1))
}
```
## License

This library belongs to _RxSwiftCommunity_.
Expand Down
16 changes: 16 additions & 0 deletions RxSwiftExt.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@

/* Begin PBXBuildFile section */
188C6DA31C47B4240092101A /* RxSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 188C6DA21C47B4240092101A /* RxSwift.framework */; };
1A8741AC20745A91004BB762 /* UIViewPropertyAnimatorTests+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A8741AB20745A91004BB762 /* UIViewPropertyAnimatorTests+Rx.swift */; };
1A8741AD20745A96004BB762 /* UIViewPropertyAnimatorTests+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A8741AB20745A91004BB762 /* UIViewPropertyAnimatorTests+Rx.swift */; };
1A8741AE20745A97004BB762 /* UIViewPropertyAnimatorTests+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A8741AB20745A91004BB762 /* UIViewPropertyAnimatorTests+Rx.swift */; };
1AA8395B207451D6001C49ED /* RxCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1AA8395A207451D5001C49ED /* RxCocoa.framework */; };
3D11958B1FCAD9AE0095134B /* and.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBDE5FB1FBBAE3900DF47F9 /* and.swift */; };
3D11958C1FCAD9AF0095134B /* and.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBDE5FB1FBBAE3900DF47F9 /* and.swift */; };
3D638DEC1DC2B2D50089A590 /* RxSwiftExt.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 188C6D911C47B2B20092101A /* RxSwiftExt.framework */; };
Expand All @@ -32,6 +36,7 @@
3DBDE5FF1FBBB09900DF47F9 /* AndTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBDE5FD1FBBB05400DF47F9 /* AndTests.swift */; };
3DBDE6001FBBB09A00DF47F9 /* AndTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBDE5FD1FBBB05400DF47F9 /* AndTests.swift */; };
3DBDE6011FBBB09A00DF47F9 /* AndTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBDE5FD1FBBB05400DF47F9 /* AndTests.swift */; };
4A73956C206D501300E2BE2D /* UIViewPropertyAnimator+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A73956B206D501300E2BE2D /* UIViewPropertyAnimator+Rx.swift */; };
5386076F1E6F1C0A000361DE /* mapTo+RxCocoa.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5386076E1E6F1C0A000361DE /* mapTo+RxCocoa.swift */; };
538607731E6F1D51000361DE /* MapToTests+RxCocoa.swift in Sources */ = {isa = PBXBuildFile; fileRef = 538607711E6F1CFB000361DE /* MapToTests+RxCocoa.swift */; };
538607AA1E6F334B000361DE /* apply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5386079A1E6F334B000361DE /* apply.swift */; };
Expand Down Expand Up @@ -247,11 +252,14 @@
/* Begin PBXFileReference section */
188C6D911C47B2B20092101A /* RxSwiftExt.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RxSwiftExt.framework; sourceTree = BUILT_PRODUCTS_DIR; };
188C6DA21C47B4240092101A /* RxSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxSwift.framework; path = Carthage/Build/iOS/RxSwift.framework; sourceTree = SOURCE_ROOT; };
1A8741AB20745A91004BB762 /* UIViewPropertyAnimatorTests+Rx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewPropertyAnimatorTests+Rx.swift"; sourceTree = "<group>"; };
1AA8395A207451D5001C49ED /* RxCocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxCocoa.framework; path = Carthage/Build/iOS/RxCocoa.framework; sourceTree = "<group>"; };
3D638DE71DC2B2D40089A590 /* RxSwiftExtTests-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "RxSwiftExtTests-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
3D638E1E1DC2B3A40089A590 /* RxTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxTest.framework; path = Carthage/Build/iOS/RxTest.framework; sourceTree = "<group>"; };
3DB034F61DC376D9002C6A26 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Tests/Info.plist; sourceTree = "<group>"; };
3DBDE5FB1FBBAE3900DF47F9 /* and.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = and.swift; sourceTree = "<group>"; };
3DBDE5FD1FBBB05400DF47F9 /* AndTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AndTests.swift; sourceTree = "<group>"; };
4A73956B206D501300E2BE2D /* UIViewPropertyAnimator+Rx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewPropertyAnimator+Rx.swift"; sourceTree = "<group>"; };
5386076E1E6F1C0A000361DE /* mapTo+RxCocoa.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "mapTo+RxCocoa.swift"; path = "Source/RxCocoa/mapTo+RxCocoa.swift"; sourceTree = SOURCE_ROOT; };
538607711E6F1CFB000361DE /* MapToTests+RxCocoa.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MapToTests+RxCocoa.swift"; sourceTree = "<group>"; };
5386079A1E6F334B000361DE /* apply.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = apply.swift; path = Source/RxSwift/apply.swift; sourceTree = SOURCE_ROOT; };
Expand Down Expand Up @@ -325,6 +333,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
1AA8395B207451D6001C49ED /* RxCocoa.framework in Frameworks */,
188C6DA31C47B4240092101A /* RxSwift.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -429,6 +438,7 @@
53F336E71E70CBF700D35D38 /* distinct+RxCocoa.swift */,
5386076E1E6F1C0A000361DE /* mapTo+RxCocoa.swift */,
538607EF1E6F589E000361DE /* not+RxCocoa.swift */,
4A73956B206D501300E2BE2D /* UIViewPropertyAnimator+Rx.swift */,
);
path = RxCocoa;
sourceTree = "<group>";
Expand All @@ -439,6 +449,7 @@
53F336E91E70D59000D35D38 /* DistinctTests+RxCocoa.swift */,
538607711E6F1CFB000361DE /* MapToTests+RxCocoa.swift */,
53C79D5F1E6F5AAB00CD9B6A /* NotTests+RxCocoa.swift */,
1A8741AB20745A91004BB762 /* UIViewPropertyAnimatorTests+Rx.swift */,
);
name = RxCocoa;
path = Tests/RxCocoa;
Expand Down Expand Up @@ -532,6 +543,7 @@
9DAB77991D6763AC007E85BC /* Frameworks */ = {
isa = PBXGroup;
children = (
1AA8395A207451D5001C49ED /* RxCocoa.framework */,
E36BDFB81F38755F008C9D56 /* tvOS */,
62512C561F0EAEB90083A89F /* macOS */,
62512C551F0EAEB20083A89F /* iOS */,
Expand Down Expand Up @@ -933,6 +945,7 @@
538607B81E6F334B000361DE /* retryWithBehavior.swift in Sources */,
66C663061EA0ECD9005245C4 /* materialized+elements.swift in Sources */,
538607B51E6F334B000361DE /* once.swift in Sources */,
4A73956C206D501300E2BE2D /* UIViewPropertyAnimator+Rx.swift in Sources */,
538607B11E6F334B000361DE /* mapTo.swift in Sources */,
538607AA1E6F334B000361DE /* apply.swift in Sources */,
C4D2153F20118A81009804AE /* ofType.swift in Sources */,
Expand Down Expand Up @@ -989,6 +1002,7 @@
538607731E6F1D51000361DE /* MapToTests+RxCocoa.swift in Sources */,
538607E61E6F36A9000361DE /* MapToTests.swift in Sources */,
5A5FCE411ED5AEC60052A9B5 /* PausableBufferedTests.swift in Sources */,
1A8741AC20745A91004BB762 /* UIViewPropertyAnimatorTests+Rx.swift in Sources */,
3DBDE5FF1FBBB09900DF47F9 /* AndTests.swift in Sources */,
58C545FD1AE234C7F290334F /* ZipWithTest.swift in Sources */,
);
Expand Down Expand Up @@ -1062,6 +1076,7 @@
62512CA21F0EB1850083A89F /* WeakTarget.swift in Sources */,
62512C9D1F0EB1850083A89F /* PausableTests.swift in Sources */,
62512C991F0EB1850083A89F /* MapToTests.swift in Sources */,
1A8741AE20745A97004BB762 /* UIViewPropertyAnimatorTests+Rx.swift in Sources */,
3DBDE6001FBBB09A00DF47F9 /* AndTests.swift in Sources */,
58C54302EC14B6FF2034BAF6 /* ZipWithTest.swift in Sources */,
);
Expand Down Expand Up @@ -1135,6 +1150,7 @@
E39C42091F18B13E007F2ACD /* NotTests.swift in Sources */,
E39C42081F18B13E007F2ACD /* Materialized+elementsTests.swift in Sources */,
E39C42071F18B13E007F2ACD /* MapToTests.swift in Sources */,
1A8741AD20745A96004BB762 /* UIViewPropertyAnimatorTests+Rx.swift in Sources */,
3DBDE6011FBBB09A00DF47F9 /* AndTests.swift in Sources */,
58C54B6E1B4C678DE2378145 /* ZipWithTest.swift in Sources */,
);
Expand Down
40 changes: 40 additions & 0 deletions Source/RxCocoa/UIViewPropertyAnimator+Rx.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// UIViewPropertyAnimator+Rx.swift
// RxSwiftExt
//
// Created by Wittemberg, Thibault on 29/03/18.
// Copyright © 2017 RxSwift Community. All rights reserved.
//

import Foundation
import UIKit
import RxSwift
import RxCocoa

@available(iOS 10.0, *)
public extension Reactive where Base: UIViewPropertyAnimator {
/// Provides a Completable that triggers the UIViewPropertyAnimator upon subscription
/// and completes once the animation ends.
///
/// - Parameter afterDelay: the delay to apply to the animation start
///
/// - Returns: Completable
func animate(afterDelay delay: TimeInterval = 0) -> Completable {
return Completable.create { [base] completable in
base.addCompletion { position in
guard position == .end else { return }
completable(.completed)
}

if delay != 0 {
base.startAnimation(afterDelay: delay)
} else {
base.startAnimation()
}

return Disposables.create {
base.stopAnimation(true)
}
}
}
}
Loading

0 comments on commit 7a9728f

Please sign in to comment.