Merge remote-tracking branch 'head/develop' into feature/delay-operator

This commit is contained in:
tarunon 2016-02-29 17:24:32 +09:00
commit 74e9262f42
87 changed files with 1510 additions and 1430 deletions

View File

@ -3,6 +3,50 @@ All notable changes to this project will be documented in this file.
---
## [2.2.0](https://github.com/ReactiveX/RxSwift/releases/tag/2.2.0)
#### Public Interface anomalies
* Fixes problem with `timer` operator. Changes return type from `Observable<Int64>` to `Observable<T>`. This could potentially cause code breakage, but it was an API anomaly.
* Curried functions were marked deprecated so they were replaced in `UITableView` and `UICollectionView` extensions with equivalent lambdas. This shouldn't break anyone's code, but it is a change in public interface.
This is example of those changes:
```swift
- public func rx_itemsWithCellFactory<S : SequenceType, O : ObservableType where O.E == S>
(source: O)
(cellFactory: (UITableView, Int, S.Generator.Element) -> UITableViewCell) -> Disposable
+ public func rx_itemsWithCellFactory<S : SequenceType, O : ObservableType where O.E == S>
(source: O)
-> (cellFactory: (UITableView, Int, S.Generator.Element) -> UITableViewCell) -> Disposable
```
* Fixes anomaly in `CLLocationManager` extensions
```swift
- public var rx_didFinishDeferredUpdatesWithError: RxSwift.Observable<NSError> { get }
+ public var rx_didFinishDeferredUpdatesWithError: RxSwift.Observable<NSError?> { get }
```
#### Features
* Adds `UIBindingObserver`.
* Adds `doOnNext` convenience operator (also added to `Driver`).
* Adds `doOnError` convenience operator.
* Adds `doOnCompleted` convenience operator (also added to `Driver`).
* Adds `skip`, `startWith` to `Driver`.
* Adds `rx_active` extension to `NSLayoutConstraint`.
* Adds `rx_refreshing` extension to `UIRefreshControl`.
* Adds `interval` and `timer` to `Driver`.
* Adds `rx_itemAccessoryButtonTapped` to `UITableView` extensions.
* Adds `rx_networkActivityIndicatorVisible` to `UIApplication`.
* Adds `rx_selected` to `UIControl`.
#### Anomalies
* Fixes anomaly with registering multiple observers to `UIBarButtonItem`.
* Fixes anomaly with blocking operators possibly over-stopping the `RunLoop`.
## [2.1.0](https://github.com/ReactiveX/RxSwift/releases/tag/2.1.0)
#### Features
@ -20,7 +64,7 @@ All notable changes to this project will be documented in this file.
#### Anomalies
* Removes usage of `OSSpinLock`s from all `Darwin` platforms because of problems with inversion of priority on iOS. [Original thread on swift mailing list is here](https://lists.swift.org/pipermail/swift-dev/Week-of-Mon-20151214/000321.html)
* Reduces verbose output from `RxCocoa` project in debug mode. `TRACE_RESOURCES` is now also treated as a verbosity level setting. It is possible to get old output by using `TRACE_RESOURCES` with verbosity level `>= 2`. [#397](https://github.com/ReactiveX/RxSwift/issues/397)
* Reduces verbose output from `RxCocoa` project in debug mode. `TRACE_RESOURCES` is now also treated as a verbosity level setting. It is possible to get old output by using `TRACE_RESOURCES` with verbosity level `>= 2`. [#397](https://github.com/ReactiveX/RxSwift/issues/397)
* Fixes anomaly with logging of HTTP body of requests in `RxCocoa` project.
## [2.0.0](https://github.com/ReactiveX/RxSwift/releases/tag/2.0.0)

View File

@ -0,0 +1,19 @@
## Comparison with ReactiveCocoa
RxSwift is somewhat similar to ReactiveCocoa since ReactiveCocoa borrows a large number of concepts from Rx.
One of the main goals of this project was to create a significantly simpler interface that is more aligned with other Rx implementations, offers richer concurrency model, offers more optimization opportunities and is more aligned with built-in Swift error handling mechanisms.
We've also decided to only rely on Swift/llvm compiler and not introduce any external dependencies.
Probably the main difference between these projects is the difference of approach in building abstractions.
The main goal of RxSwift project is to provide environment agnostic compositional computation glue abstracted in the form of observable sequences.
We then aim to improve the experience of using RxSwift on specific platforms. To do this RxCocoa uses those generic computations to build more practical abstractions and wrap Foundation/Cocoa/UKit frameworks. That means that other libraries give context and semantics to the generic computation engine RxSwift provides such as `Driver`, `ControlProperty`, `ControlEvent`s and more.
One of the benefits to representing all these abstractions as a single concept; _observable sequences_, is that all computation abstractions built on top of them are also composable in the same fundamental way. They all follow the same contract and implement the same interface.
It is also easy to create flexible subscription (resource) sharing strategies or use one of the built-in ones: `share`, `shareReplay`, `publish`, `multicast`, `shareReplayLatestWhileConnected`...
This library also offers fine-tunable concurrency model. In case concurrent schedulers are used, observable sequence operators will preserve sequence properties. The same observable sequence operators will also know how to detect and optimally use known serial schedulers. ReactiveCocoa has a more limited concurrency model and only allows serial schedulers.
Multithreaded programming is really hard and detecting non trivial loops is even harder. That's why all operators are built in a fault tolerant way. Even in case element generation occurs during element processing (recursion), operators will try to handle that situation and prevent deadlocks. That means that in worst possible case programming error will cause stack overflow, but users won't have to manually kill the app, and you will get crash report in error reporting systems so you can find and fix the problem.

View File

@ -0,0 +1,13 @@
## RxExamples
To run the example app:
* Open `Rx.xcworkspace`
* Choose one of example schemes (RxExample-iOS, RxExample-OSX) and hit `Run`.
You can also run the example app using CocoaPods.
```
pod try RxSwift
```

View File

@ -5,7 +5,7 @@ Examples
1. [Simple UI bindings](#simple-ui-bindings)
1. [Autocomplete](#autocomplete)
1. [more examples](../RxExample)
1. [Playgrounds](../README.md#playgrounds)
1. [Playgrounds](Playgrounds.md)
## Calculated variable

View File

@ -0,0 +1,56 @@
## Build / Install / Run
Rx doesn't contain any external dependencies.
These are currently supported options:
### Manual
Open Rx.xcworkspace, choose `RxExample` and hit run. This method will build everything and run sample app
### [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html)
**:warning: IMPORTANT! For tvOS support CocoaPods `0.39` is required. :warning:**
```
# Podfile
use_frameworks!
target 'YOUR_TARGET_NAME' do
pod 'RxSwift', '~> 2.0'
pod 'RxCocoa', '~> 2.0'
pod 'RxBlocking', '~> 2.0'
pod 'RxTests', '~> 2.0'
end
```
replace `YOUR_TARGET_NAME`, then type in the `Podfile` directory:
```
$ pod install
```
### [Carthage](https://github.com/Carthage/Carthage)
**Xcode 7.1 required**
Add this to `Cartfile`
```
github "ReactiveX/RxSwift" ~> 2.0
```
```
$ carthage update
```
### Manually using git submodules
* Add RxSwift as a submodule
```
$ git submodule add git@github.com:ReactiveX/RxSwift.git
```
* Drag `Rx.xcodeproj` into Project Navigator
* Go to `Project > Targets > Build Phases > Link Binary With Libraries`, click `+` and select `RxSwift-[Platform]` and `RxCocoa-[Platform]` targets

View File

@ -0,0 +1,47 @@
**Please copy the following template [here](https://github.com/ReactiveX/RxSwift/issues/new) and fill in the missing fields so we can help you as soon as possible.**
**If you don't have something to report in the following format, maybe you don't really have an issue an it's easier and faster to resolve doubts in [slack channel](http://http://slack.rxswift.org/) first.**
```
*Short description*:
description here
*Code that reproduces the issue*:
code goes here
*Xcode version*:
Xcode version goes here
*Expected outcome*:
what do you expect to happen goes here
*What actually happens*:
what actually happens goes here
// filling additional information below is optional, but resolving your issue could be potentially a lot faster
*Integration method*:
(so we don't lose time investigating wrong integration)
* CocoaPods
* Carthage
* Git submodules
*I have multiple versions of Xcode installed*:
(so we can understand can this cause your issue)
* yes (which ones)
* no
*How long have I been using this project*:
(this is so we can understand your level of knowledge
and formulate the response in an appropriate manner)
* just starting
* I have a small code base
* I have a significant code base
```

View File

@ -3,19 +3,18 @@ Math Behind Rx
## Duality between Observer and Iterator / Enumerator / Generator / Sequences
There is a duality between observer and generator pattern. That's what enables transition from async callback world to synchronous world of sequence transformations.
There is a duality between the observer and generator patterns. This is what enables us to transition from the async callback world to synchronous world of sequence transformations.
In short, enumerator and observer pattern both describe sequences. It's pretty obvious why does enumerator defined sequence, but what about observer.
In short, the enumerator and observer patterns both describe sequences. It's fairly obvious why the enumerator defines a sequence, but the observer is slightly more complicated.
There is also a pretty simple explanation that doesn't include a lot of math. Assume that you are observing mouse movements. Every received mouse movement is an element of a sequence of mouse movements over time.
There is, however, a pretty simple example that doesn't require a lot of mathematical knowledge. Assume that you are observing the position of your mouse cursor on screen at given time periods. Over time, these mouse positions form a sequence. This is, in essence, an observer sequence.
In short, there are two basic ways elements of a sequence can be accessed.
There are two basic ways elements of a sequence can be accessed:
* Push interface - Observer (observed elements over time make a sequence)
* Pull interface - Iterator / Enumerator / Generator
To learn more about this, these videos should help
You can also see a more formal explanation in this video:
You can also see a more formal explanation explained in a fun way in this video:
[Expert to Expert: Brian Beckman and Erik Meijer - Inside the .NET Reactive Framework (Rx) (video)](https://www.youtube.com/watch?v=looJcaeboBY)
* [Expert to Expert: Brian Beckman and Erik Meijer - Inside the .NET Reactive Framework (Rx) (video)](https://www.youtube.com/watch?v=looJcaeboBY)
* [Reactive Programming Overview (Jafar Husain from Netflix)](https://www.youtube.com/watch?v=dwP1TNXE6fc)

View File

@ -0,0 +1,20 @@
**Please copy the following template [here](https://github.com/ReactiveX/RxSwift/issues/new) and fill in the missing fields so we can help you as soon as possible.**
```
*Short description of missing functionality*:
E.g. I want this library to implement xxx operator.
*Short code example of how you would like to use the API*:
code goes here
*The reason why I need this functionality*:
E.g. I was trying to ....
*Code I have right now*:
code snippet that demonstrates how you've attempted to solve the problem
```

View File

@ -0,0 +1,8 @@
## Playgrounds
To use playgrounds:
* Open `Rx.xcworkspace`
* Build `RxSwift-OSX` scheme
* And then open `Rx` playground in `Rx.xcworkspace` tree view.
* Choose `View > Show Debug Area`

295
Documentation/Why.md Normal file
View File

@ -0,0 +1,295 @@
## Why
**Rx enables building apps in a declarative way.**
### Bindings
```swift
Observable.combineLatest(firstName.rx_text, lastName.rx_text) { $0 + " " + $1 }
.map { "Greeting \($0)" }
.bindTo(greetingLabel.rx_text)
```
This also works with `UITableView`s and `UICollectionView`s.
```swift
viewModel
.rows
.bindTo(resultsTableView.rx_itemsWithCellIdentifier("WikipediaSearchCell", cellType: WikipediaSearchCell.self)) { (_, viewModel, cell) in
cell.title = viewModel.title
cell.url = viewModel.url
}
.addDisposableTo(disposeBag)
```
** Official suggestion is to always use `.addDisposableTo(disposeBag)` even though that's not necessary for simple bindings.**
### Retries
It would be great if APIs wouldn't fail, but unfortunately they do. Let's say there is an API method
```swift
func doSomethingIncredible(forWho: String) throws -> IncredibleThing
```
If you are using this function as it is, it's really hard to do retries in case it fails. Not to mention complexities modeling [exponential backoffs](https://en.wikipedia.org/wiki/Exponential_backoff). Sure it's possible, but code would probably contain a lot of transient states that you really don't care about, and it won't be reusable.
You would ideally want to capture the essence of retrying, and to be able to apply it to any operation.
This is how you can do simple retries with Rx
```swift
doSomethingIncredible("me")
.retry(3)
```
You can also easily create custom retry operators.
### Delegates
Instead of doing the tedious and non-expressive
```swift
public func scrollViewDidScroll(scrollView: UIScrollView) { // what scroll view is this bound to?
self.leftPositionConstraint.constant = scrollView.contentOffset.x
}
```
... write
```swift
self.resultsTableView
.rx_contentOffset
.map { $0.x }
.bindTo(self.leftPositionConstraint.rx_constant)
```
### KVO
Instead of
```
`TickTock` was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object.
```
and
```objc
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
```
Use [`rx_observe` and `rx_observeWeakly`](GettingStarted.md#kvo)
This is how they can be used:
```swift
view.rx_observe(CGRect.self, "frame")
.subscribeNext { frame in
print("Got new frame \(frame)")
}
```
or
```swift
someSuspiciousViewController
.rx_observeWeakly(Bool.self, "behavingOk")
.subscribeNext { behavingOk in
print("Cats can purr? \(behavingOk)")
}
```
### Notifications
Instead of using
```swift
@available(iOS 4.0, *)
public func addObserverForName(name: String?, object obj: AnyObject?, queue: NSOperationQueue?, usingBlock block: (NSNotification) -> Void) -> NSObjectProtocol
```
... just write
```swift
NSNotificationCenter.defaultCenter()
.rx_notification(UITextViewTextDidBeginEditingNotification, object: myTextView)
.map { /*do something with data*/ }
....
```
### Transient state
There is also a lot of problems with transient state when writing async programs. Typical example is autocomplete search box.
If you were to write the autocomplete code without Rx, first problem that probably needs to be solved is when `c` in `abc` is typed, and there is a pending request for `ab`, pending request gets cancelled. Ok, that shouldn't be too hard to solve, you just create additional variable to hold reference to pending request.
The next problem is if the request fails, you need to do that messy retry logic. But ok, a couple of more fields that capture number of retries that need to be cleaned up.
It would be great if program would wait for some time before firing that request to server, after all, we don't want to spam our servers in case somebody is in the process of fast typing something very long. Additional timer field maybe?
There is also a question of what needs to be shown on screen while that search is executing, and also what needs to be shown in case we fail even with all of the retries.
Writing all of this and properly testing it would be tedious. This is that same logic written with Rx.
```swift
searchTextField.rx_text
.throttle(0.3, scheduler: MainScheduler.instance)
.distinctUntilChanged()
.flatMapLatest { query in
API.getSearchResults(query)
.retry(3)
.startWith([]) // clears results on new search term
.catchErrorJustReturn([])
}
.subscribeNext { results in
// bind to ui
}
```
There is no additional flags or fields required. Rx takes care of all that transient mess.
### Compositional disposal
Lets assume that there is a scenario where you want to display blurred images in a table view. The images should be first fetched from URL, then decoded and then blurred.
It would also be nice if that entire process could be cancelled if a cell exits the visible table view area because bandwidth and processor time for blurring are expensive.
It would also be nice if we didn't just immediately start to fetch image once the cell enters visible area because if user swipes really fast there could be a lot of requests fired and cancelled.
It would be also nice if we could limit the number of concurrent image operations because blurring images is an expensive operation.
This is how we can do it using Rx.
```swift
// this is conceptual solution
let imageSubscription = imageURLs
.throttle(0.2, scheduler: MainScheduler.instance)
.flatMapLatest { imageURL in
API.fetchImage(imageURL)
}
.observeOn(operationScheduler)
.map { imageData in
return decodeAndBlurImage(imageData)
}
.observeOn(MainScheduler.instance)
.subscribeNext { blurredImage in
imageView.image = blurredImage
}
.addDisposableTo(reuseDisposeBag)
```
This code will do all that, and when `imageSubscription` is disposed it will cancel all dependent async operations and make sure no rogue image is bound to UI.
### Aggregating network requests
What if you need to fire two requests, and aggregate results when they have both finished?
Well, there is of course `zip` operator
```swift
let userRequest: Observable<User> = API.getUser("me")
let friendsRequest: Observable<Friends> = API.getFriends("me")
Observable.zip(userRequest, friendsRequest) { user, friends in
return (user, friends)
}
.subscribeNext { user, friends in
// bind them to user interface
}
```
So what if those APIs return results on a background thread, and binding has to happen on main UI thread? There is `observeOn`.
```swift
let userRequest: Observable<User> = API.getUser("me")
let friendsRequest: Observable<[Friend]> = API.getFriends("me")
Observable.zip(userRequest, friendsRequest) { user, friends in
return (user, friends)
}
.observeOn(MainScheduler.instance)
.subscribeNext { user, friends in
// bind them to user interface
}
```
There are many more practical use cases where Rx really shines.
### State
Languages that allow mutation make it easy to access global state and mutate it. Uncontrolled mutations of shared global state can easily cause [combinatorial explosion] (https://en.wikipedia.org/wiki/Combinatorial_explosion#Computing).
But on the other hand, when used in smart way, imperative languages can enable writing more efficient code closer to hardware.
The usual way to battle combinatorial explosion is to keep state as simple as possible, and use [unidirectional data flows](https://developer.apple.com/videos/play/wwdc2014-229) to model derived data.
This is what Rx really shines at.
Rx is that sweet spot between functional and imperative world. It enables you to use immutable definitions and pure functions to process snapshots of mutable state in a reliable composable way.
So what are some of the practical examples?
### Easy integration
And what if you need to create your own observable? It's pretty easy. This code is taken from RxCocoa and that's all you need to wrap HTTP requests with `NSURLSession`
```swift
extension NSURLSession {
public func rx_response(request: NSURLRequest) -> Observable<(NSData, NSURLResponse)> {
return Observable.create { observer in
let task = self.dataTaskWithRequest(request) { (data, response, error) in
guard let response = response, data = data else {
observer.on(.Error(error ?? RxCocoaURLError.Unknown))
return
}
guard let httpResponse = response as? NSHTTPURLResponse else {
observer.on(.Error(RxCocoaURLError.NonHTTPResponse(response: response)))
return
}
observer.on(.Next(data, httpResponse))
observer.on(.Completed)
}
task.resume()
return AnonymousDisposable {
task.cancel()
}
}
}
}
```
### Benefits
In short using Rx will make your code:
* composable <- because Rx is composition's nick name
* reusable <- because it's composable
* declarative <- because definitions are immutable and only data changes
* understandable and concise <- raising level of abstraction and removing transient states
* stable <- because Rx code is thoroughly unit tested
* less stateful <- because you are modeling application as unidirectional data flows
* without leaks <- because resource management is easy
### It's not all or nothing
It is usually a good idea to model as much of your application as possible using Rx.
But what if you don't know all of the operators and does there even exist some operator that models your particular case?
Well, all of the Rx operators are based on math and should be intuitive.
The good news is that about 10-15 operators cover most typical use cases. And that list already includes some of the familiar ones like `map`, `filter`, `zip`, `observeOn` ...
There is a huge list of [all Rx operators](http://reactivex.io/documentation/operators.html) and list of all of the [currently supported RxSwift operators](API.md).
For each operator there is [marble diagram](http://reactivex.io/documentation/operators/retry.html) that helps to explain how does it work.
But what if you need some operator that isn't on that list? Well, you can make your own operator.
What if creating that kind of operator is really hard for some reason, or you have some legacy stateful piece of code that you need to work with? Well, you've got yourself in a mess, but you can [jump out of Rx monad](GettingStarted.md#life-happens) easily, process the data, and return back into it.

527
README.md
View File

@ -2,58 +2,9 @@
======================================
[![Travis CI](https://travis-ci.org/ReactiveX/RxSwift.svg?branch=master)](https://travis-ci.org/ReactiveX/RxSwift) ![platforms](https://img.shields.io/badge/platforms-iOS%20%7C%20OSX%20%7C%20tvOS%20%7C%20watchOS%20%7C%20Linux%28experimental%29-333333.svg) ![pod](https://img.shields.io/cocoapods/v/RxSwift.svg)
[![Slack channel](http://slack.rxswift.org/badge.svg)](http://slack.rxswift.org)
Xcode 7 Swift 2.1 required
### Change Log (from 1.9 version)
* Removes deprecated APIs
* Adds `ObservableType`
* Moved from using `>-` operator to protocol extensions `.`
* Adds support for Swift 2.0 error handling `try`/`do`/`catch`
You can now just write
```swift
API.fetchData(URL)
.map { rawData in
if invalidData(rawData) {
throw myParsingError
}
...
return parsedData
}
```
* RxCocoa introduces `bindTo` extensions
```swift
Observable.combineLatest(firstName.rx_text, lastName.rx_text) { $0 + " " + $1 }
.map { "Greeting \($0)" }
.bindTo(greetingLabel.rx_text)
```
... works for `UITableView`/`UICollectionView` too
```swift
viewModel.rows
.bindTo(resultsTableView.rx_itemsWithCellIdentifier("WikipediaSearchCell", cellType: WikipediaSearchCell.self)) { (_, viewModel, cell) in
cell.viewModel = viewModel
}
.addDisposableTo(disposeBag)
```
* Adds new operators (array version of `zip`, array version of `combineLatest`, ...)
* Renames `catch` to `catchError`
* Change from `disposeBag.addDisposable` to `disposable.addDisposableTo`
* Deprecates `aggregate` in favor of `reduce`
* Deprecates `variable` in favor of `shareReplay(1)` (to be consistent with RxJS version)
Check out [Migration guide to RxSwift 2.0](Documentation/Migration.md)
## About Rx
Rx is a [generic abstraction of computation](https://youtu.be/looJcaeboBY) expressed through `Observable<Element>` interface.
@ -68,479 +19,55 @@ Like the original Rx, its intention is to enable easy composition of asynchronou
KVO observing, async operations and streams are all unified under [abstraction of sequence](Documentation/GettingStarted.md#observables-aka-sequences). This is the reason why Rx is so simple, elegant and powerful.
## I came here because I want to ...
```
RxSwift
|
├-LICENSE.md
├-README.md
├-RxSwift - platform agnostic core
├-RxCocoa - extensions for UI, NSURLSession, KVO ...
├-RxBlocking - set of blocking operators for unit testing
├-RxExample - example apps: taste of Rx
└-Rx.xcworkspace - workspace that contains all of the projects hooked up
```
###### ... understand
## Docs
* [why use rx?](Documentation/Why.md)
* how does RxSwift work? [Getting Started Guide](Documentation/GettingStarted.md)
* what is `Driver`, `ControlProperty`, and `Variable` ... and why do they exist? [Units](Documentation/Units.md)
* [the math behind Rx](Documentation/MathBehindRx.md)
* [what are hot and cold observable sequences?](Documentation/HotAndColdObservables.md)
* [what does the the public API look like?](Documentation/API.md)
1. [Getting started](Documentation/GettingStarted.md)
1. [Math behind](Documentation/MathBehindRx.md)
1. [Units](Documentation/Units.md)
1. [Simple Examples](Documentation/Examples.md)
1. [API - RxSwift operators / RxCocoa extensions](Documentation/API.md)
1. [Hot and cold observables](Documentation/HotAndColdObservables.md)
###### ... install
## Content
* Integrate RxSwift/RxCocoa with my app. [Installation Guide](Documentation/Installation.md)
1. [Why](#why)
1. [State](#state)
1. [Bindings](#bindings)
1. [Retries](#retries)
1. [Transient state](#transient-state)
1. [Aggregating network requests](#aggregating-network-requests)
1. [Easy integration](#easy-integration)
1. [Compositional disposal](#compositional-disposal)
1. [Delegates](#delegates)
1. [Notifications](#notifications)
1. [KVO](#kvo)
1. [Benefits](#benefits)
1. [It's not all or nothing](#its-not-all-or-nothing)
1. [Build / Install / Run](#build--install--run)
1. [Comparison with ReactiveCocoa](#comparison-with-reactivecocoa)
1. [Playgrounds](#playgrounds)
1. [RxExamples](#rxexamples)
1. [References](#references)
###### ... hack around
## Why
* with example app. [Running Example App](Documentation/ExampleApp.md)
* with operators in playgrounds. [Playgrounds](Documentation/Playgrounds.md)
Producing stable code fast is usually unexpectedly hard using just your vanilla language of choice.
###### ... interact
There are many unexpected pitfalls that can ruin all of your hard work and halt development of new features.
* All of this is great, but it would be nice to talk with other people using RxSwift and exchange experiences. <br />[![Slack channel](http://slack.rxswift.org/badge.svg)](http://slack.rxswift.org) [Join Slack Channel](http://slack.rxswift.org/)
* Report a problem using the library. [Open an Issue With Bug Template](Documentation/IssueTemplate.md)
* Request a new feature. [Open an Issue With Feature Request Template](Documentation/NewFeatureRequestTemplate.md)
### State
Languages that allow mutation make it easy to access global state and mutate it. Uncontrolled mutations of shared global state can easily cause [combinatorial explosion] (https://en.wikipedia.org/wiki/Combinatorial_explosion#Computing).
###### ... compare
But on the other hand, when used in smart way, imperative languages can enable writing more efficient code closer to hardware.
* [with other libraries](Documentation/ComparisonWithOtherLibraries.md).
The usual way to battle combinatorial explosion is to keep state as simple as possible, and use [unidirectional data flows](https://developer.apple.com/videos/play/wwdc2014-229) to model derived data.
This is what Rx really shines at.
###### ... find compatible
Rx is that sweet spot between functional and imperative world. It enables you to use immutable definitions and pure functions to process snapshots of mutable state in a reliable composable way.
* libraries from [RxSwiftCommunity](https://github.com/RxSwiftCommunity).
* [Pods using RxSwift](https://cocoapods.org/?q=uses%3Arxswift).
So what are some of the practical examples?
###### ... see the broader vision
### Bindings
* Does this exist for Android? [RxJava](https://github.com/ReactiveX/RxJava)
* Where is all of this going, what is the future, what about reactive architectures, how do you design entire apps this way? [Cycle.js](https://github.com/cyclejs/cycle-core) - this is javascript, but [RxJS](https://github.com/Reactive-Extensions/RxJS) is javascript version of Rx.
When writing embedded UI applications you would ideally want your program interface to be just a [pure function](https://en.wikipedia.org/wiki/Pure_function) of the [truth of the system](https://developer.apple.com/videos/play/wwdc2014-229). In that way user interface could be optimally redrawn only when truth changes, and there wouldn't be any inconsistencies.
These are so called bindings and Rx can help you model your system that way.
```swift
Observable.combineLatest(firstName.rx_text, lastName.rx_text) { $0 + " " + $1 }
.map { "Greeting \($0)" }
.bindTo(greetingLabel.rx_text)
```
** Official suggestion is to always use `.addDisposableTo(disposeBag)` even though that's not necessary for simple bindings.**
### Retries
It would be great if APIs wouldn't fail, but unfortunately they do. Let's say there is an API method
```swift
func doSomethingIncredible(forWho: String) throws -> IncredibleThing
```
If you are using this function as it is, it's really hard to do retries in case it fails. Not to mention complexities modelling [exponential backoffs](https://en.wikipedia.org/wiki/Exponential_backoff). Sure it's possible, but code would probably contain a lot of transient states that you really don't care about, and it won't be reusable.
You would ideally want to capture the essence of retrying, and to be able to apply it to any operation.
This is how you can do simple retries with Rx
```swift
doSomethingIncredible("me")
.retry(3)
```
You can also easily create custom retry operators.
### Transient state
There is also a lot of problems with transient state when writing async programs. Typical example is autocomplete search box.
If you were to write the autocomplete code without Rx, first problem that probably needs to be solved is when `c` in `abc` is typed, and there is a pending request for `ab`, pending request gets cancelled. Ok, that shouldn't be too hard to solve, you just create additional variable to hold reference to pending request.
The next problem is if the request fails, you need to do that messy retry logic. But ok, a couple of more fields that capture number of retries that need to be cleaned up.
It would be great if program would wait for some time before firing that request to server, after all, we don't want to spam our servers in case somebody is in the process of fast typing something very long. Additional timer field maybe?
There is also a question of what needs to be shown on screen while that search is executing, and also what needs to be shown in case we fail even with all of the retries.
Writing all of this and properly testing it would be tedious. This is that same logic written with Rx.
```swift
searchTextField.rx_text
.throttle(0.3, scheduler: MainScheduler.instance)
.distinctUntilChanged()
.flatMapLatest { query in
API.getSearchResults(query)
.retry(3)
.startWith([]) // clears results on new search term
.catchErrorJustReturn([])
}
.subscribeNext { results in
// bind to ui
}
```
There is no additional flags or fields required. Rx takes care of all that transient mess.
### Aggregating network requests
What if you need to fire two requests, and aggregate results when they have both finished?
Well, there is of course `zip` operator
```swift
let userRequest: Observable<User> = API.getUser("me")
let friendsRequest: Observable<Friends> = API.getFriends("me")
Observable.zip(userRequest, friendsRequest) { user, friends in
return (user, friends)
}
.subscribeNext { user, friends in
// bind them to user interface
}
```
So what if those APIs return results on a background thread, and binding has to happen on main UI thread? There is `observeOn`.
```swift
let userRequest: Observable<User> = API.getUser("me")
let friendsRequest: Observable<[Friend]> = API.getFriends("me")
Observable.zip(userRequest, friendsRequest) { user, friends in
return (user, friends)
}
.observeOn(MainScheduler.instance)
.subscribeNext { user, friends in
// bind them to user interface
}
```
There are many more practical use cases where Rx really shines.
### Easy integration
And what if you need to create your own observable? It's pretty easy. This code is taken from RxCocoa and that's all you need to wrap HTTP requests with `NSURLSession`
```swift
extension NSURLSession {
public func rx_response(request: NSURLRequest) -> Observable<(NSData, NSURLResponse)> {
return Observable.create { observer in
let task = self.dataTaskWithRequest(request) { (data, response, error) in
guard let response = response, data = data else {
observer.on(.Error(error ?? RxCocoaURLError.Unknown))
return
}
guard let httpResponse = response as? NSHTTPURLResponse else {
observer.on(.Error(RxCocoaURLError.NonHTTPResponse(response: response)))
return
}
observer.on(.Next(data, httpResponse))
observer.on(.Completed)
}
task.resume()
return AnonymousDisposable {
task.cancel()
}
}
}
}
```
### Compositional disposal
Lets assume that there is a scenario where you want to display blurred images in a table view. The images should be first fetched from URL, then decoded and then blurred.
It would also be nice if that entire process could be cancelled if a cell exits the visible table view area because bandwidth and processor time for blurring are expensive.
It would also be nice if we didn't just immediately start to fetch image once the cell enters visible area because if user swipes really fast there could be a lot of requests fired and cancelled.
It would be also nice if we could limit the number of concurrent image operations because blurring images is an expensive operation.
This is how we can do it using Rx.
```swift
// this is conceptual solution
let imageSubscription = imageURLs
.throttle(0.2, scheduler: MainScheduler.instance)
.flatMapLatest { imageURL in
API.fetchImage(imageURL)
}
.observeOn(operationScheduler)
.map { imageData in
return decodeAndBlurImage(imageData)
}
.observeOn(MainScheduler.instance)
.subscribeNext { blurredImage in
imageView.image = blurredImage
}
.addDisposableTo(reuseDisposeBag)
```
This code will do all that, and when `imageSubscription` is disposed it will cancel all dependent async operations and make sure no rogue image is bound to UI.
### Delegates
Delegates can be used both as a hook for customizing behavior and as an observing mechanism.
Each usage has it's drawbacks, but Rx can help remedy some of the problem with using delegates as an observing mechanism.
Using delegates and optional methods to report changes can be problematic because there can be usually only one delegate registered, so there is no way to register multiple observers.
Also, delegates usually don't fire initial value upon invoking delegate setter, so you'll also need to read that initial value in some other way. That is kind of tedious.
RxCocoa not only provides wrappers for popular UIKit/Cocoa classes, but it also provides a generic mechanism called `DelegateProxy` that enables wrapping your own delegates and exposing them as observable sequences.
This is real code taken from `UISearchBar` integration.
It uses delegate as a notification mechanism to create an `Observable<String>` that immediately returns current search text upon subscription, and then emits changed search values.
```swift
extension UISearchBar {
public var rx_delegate: DelegateProxy {
return proxyForObject(RxSearchBarDelegateProxy.self, self)
}
public var rx_text: Observable<String> {
return Observable.deferred { [weak self] in
let text = self?.text ?? ""
return self?.rx_delegate.observe("searchBar:textDidChange:") ?? empty()
.map { a in
return a[1] as? String ?? ""
}
.startWith(text)
}
}
}
```
Definition of `RxSearchBarDelegateProxy` can be found [here](RxCocoa/iOS/Proxies/RxSearchBarDelegateProxy.swift)
This is how that API can be now used
```swift
searchBar.rx_text
.subscribeNext { searchText in
print("Current search text '\(searchText)'")
}
```
### Notifications
Notifications enable registering multiple observers easily, but they are also untyped. Values need to be extracted from either `userInfo` or original target once they fire.
They are just a notification mechanism, and initial value usually has to be acquired in some other way.
That leads to this tedious pattern:
```swift
let initialText = object.text
doSomething(initialText)
// ....
func controlTextDidChange(notification: NSNotification) {
doSomething(object.text)
}
```
You can use `rx_notification` to create an observable sequence with wanted properties in a similar fashion like `searchText` was constructed in delegate example, and thus reduce scattering of logic and duplication of code.
### KVO
KVO is a handy observing mechanism, but not without flaws. It's biggest flaw is confusing memory management.
In case of observing a property on some object, the object has to outlive the KVO observer registration otherwise your system will crash with an exception.
```
`TickTock` was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object.
```
There are some rules that you can follow when observing some object that is a direct descendant or ancestor in ownership chain, but if that relation is unknown, then it becomes tricky.
It also has a really awkward callback method that needs to be implemented
```objc
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
```
RxCocoa provides a really convenient observable sequence that solves those issues called [`rx_observe` and `rx_observeWeakly`](Documentation/GettingStarted.md#kvo)
This is how they can be used:
```swift
view.rx_observe(CGRect.self, "frame")
.subscribeNext { (frame: CGRect?) in
print("Got new frame \(frame)")
}
```
or
```swift
someSuspiciousViewController.rx_observeWeakly(Bool.self, "behavingOk")
.subscribeNext { (behavingOk: Bool?) in
print("Cats can purr? \(behavingOk)")
}
```
### Benefits
In short using Rx will make your code:
* composable <- because Rx is composition's nick name
* reusable <- because it's composable
* declarative <- because definitions are immutable and only data changes
* understandable and concise <- raising level of abstraction and removing transient states
* stable <- because Rx code is thoroughly unit tested
* less stateful <- because you are modeling application as unidirectional data flows
* without leaks <- because resource management is easy
### It's not all or nothing
It is usually a good idea to model as much of your application as possible using Rx.
But what if you don't know all of the operators and does there even exist some operator that models your particular case?
Well, all of the Rx operators are based on math and should be intuitive.
The good news is that about 10-15 operators cover most typical use cases. And that list already includes some of the familiar ones like `map`, `filter`, `zip`, `observeOn` ...
There is a huge list of [all Rx operators](http://reactivex.io/documentation/operators.html) and list of all of the [currently supported RxSwift operators](Documentation/API.md).
For each operator there is [marble diagram](http://reactivex.io/documentation/operators/retry.html) that helps to explain how does it work.
But what if you need some operator that isn't on that list? Well, you can make your own operator.
What if creating that kind of operator is really hard for some reason, or you have some legacy stateful piece of code that you need to work with? Well, you've got yourself in a mess, but you can [jump out of Rx monad](Documentation/GettingStarted.md#life-happens) easily, process the data, and return back into it.
## Build / Install / Run
Rx doesn't contain any external dependencies.
These are currently supported options:
### Manual
Open Rx.xcworkspace, choose `RxExample` and hit run. This method will build everything and run sample app
### [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html)
**:warning: IMPORTANT! For tvOS support CocoaPods `0.39` is required. :warning:**
```
# Podfile
use_frameworks!
target 'YOUR_TARGET_NAME' do
pod 'RxSwift', '~> 2.0'
pod 'RxCocoa', '~> 2.0'
pod 'RxBlocking', '~> 2.0'
pod 'RxTests', '~> 2.0'
end
```
replace `YOUR_TARGET_NAME`, then type in the `Podfile` directory:
```
$ pod install
```
### [Carthage](https://github.com/Carthage/Carthage)
**Xcode 7.1 required**
Add this to `Cartfile`
```
github "ReactiveX/RxSwift" ~> 2.0
```
```
$ carthage update
```
### Manually using git submodules
* Add RxSwift as a submodule
```
$ git submodule add git@github.com:ReactiveX/RxSwift.git
```
* Drag `Rx.xcodeproj` into Project Navigator
* Go to `Project > Targets > Build Phases > Link Binary With Libraries`, click `+` and select `RxSwift-[Platform]` and `RxCocoa-[Platform]` targets
## Comparison with ReactiveCocoa
RxSwift is somewhat similar to ReactiveCocoa since ReactiveCocoa borrows a large number of concepts from Rx.
One of the main goals of this project was to create a significantly simpler interface that is more aligned with other Rx implementations, offers richer concurrency model, offers more optimization opportunities and is more aligned with built-in Swift error handling mechanisms.
We've also decided to only rely on Swift/llvm compiler and not introduce any external dependencies.
Probably the main difference between these projects is the difference of approach in building abstractions.
The main goal of RxSwift project is to provide environment agnostic compositional computation glue abstracted in the form of observable sequences.
We then aim to improve the experience of using RxSwift on specific platforms. To do this RxCocoa uses those generic computations to build more practical abstractions and wrap Foundation/Cocoa/UKit frameworks. That means that other libraries give context and semantics to the generic computation engine RxSwift provides such as `Driver`, `ControlProperty`, `ControlEvent`s and more.
One of the benefits to representing all these abstractions as a single concept; _observable sequences_, is that all computation abstractions built on top of them are also composable in the same fundamental way. They all follow the same contract and implement the same interface.
It is also easy to create flexible subscription (resource) sharing strategies or use one of the built-in ones: `share`, `shareReplay`, `publish`, `multicast`, `shareReplayLatestWhileConnected`...
This library also offers fine-tunable concurrency model. In case concurrent schedulers are used, observable sequence operators will preserve sequence properties. The same observable sequence operators will also know how to detect and optimally use known serial schedulers. ReactiveCocoa has a more limited concurrency model and only allows serial schedulers.
Multithreaded programming is really hard and detecting non trivial loops is even harder. That's why all operators are built in a fault tolerant way. Even in case element generation occurs during element processing (recursion), operators will try to handle that situation and prevent deadlocks. That means that in worst possible case programming error will cause stack overflow, but users won't have to manually kill the app, and you will get crash report in error reporting systems so you can find and fix the problem.
## Playgrounds
To use playgrounds:
* Open `Rx.xcworkspace`
* Build `RxSwift-OSX` scheme
* And then open `Rx` playground in `Rx.xcworkspace` tree view.
* Choose `View > Show Debug Area`
## RxExamples
To use playgrounds:
* Open `Rx.xcworkspace`
* Choose one of example schemes and hit `Run`.
## References
##### References
* [http://reactivex.io/](http://reactivex.io/)
* [Reactive Extensions GitHub (GitHub)](https://github.com/Reactive-Extensions)
* [Erik Meijer (Wikipedia)](http://en.wikipedia.org/wiki/Erik_Meijer_%28computer_scientist%29)
* [Expert to Expert: Brian Beckman and Erik Meijer - Inside the .NET Reactive Framework (Rx) (video)](https://youtu.be/looJcaeboBY)
* [Reactive Programming Overview (Jafar Husain from Netflix)](https://www.youtube.com/watch?v=dwP1TNXE6fc)
* [Subject/Observer is Dual to Iterator (paper)](http://csl.stanford.edu/~christos/pldi2010.fit/meijer.duality.pdf)
* [Rx standard sequence operators visualized (visualization tool)](http://rxmarbles.com/)
* [Haskell](https://www.haskell.org/)

View File

@ -2,6 +2,14 @@
import RxSwift
/*:
## Below every example there is a commented method call that runs that example. To run the example just uncomment that part.
E.g. `//sampleWithoutConnectableOperators()`
*/
/*:
## Connectable Observable Operators

View File

@ -35,6 +35,8 @@
9BA1CBFD1C0F84A10044B50A /* UIActivityIndicatorView+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BA1CBD11C0F7C0A0044B50A /* UIActivityIndicatorView+Rx.swift */; };
9BA1CBFE1C0F84C40044B50A /* UIActivityIndicatorView+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BA1CBD11C0F7C0A0044B50A /* UIActivityIndicatorView+Rx.swift */; };
9D71C4D21BF08191006E8F59 /* UIButton+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = C88254061B8A752B00B02D69 /* UIButton+Rx.swift */; };
AAE623761C82475700FC7801 /* UIProgressView+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE623751C82475700FC7801 /* UIProgressView+Rx.swift */; };
AAE623771C82475700FC7801 /* UIProgressView+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE623751C82475700FC7801 /* UIProgressView+Rx.swift */; };
B1B7C3BD1BDD39DB0076934E /* TakeLast.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B7C3BC1BDD39DB0076934E /* TakeLast.swift */; };
B1B7C3BE1BDD39DB0076934E /* TakeLast.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B7C3BC1BDD39DB0076934E /* TakeLast.swift */; };
B1B7C3BF1BDD39DB0076934E /* TakeLast.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B7C3BC1BDD39DB0076934E /* TakeLast.swift */; };
@ -507,6 +509,7 @@
C8350A231C38756B0027C24C /* VirtualSchedulerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C835091C1C38706D0027C24C /* VirtualSchedulerTest.swift */; };
C8350A2A1C3875B50027C24C /* RxMutableBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8093CB01B8A72BE0088E94D /* RxMutableBox.swift */; };
C8350A2B1C3875B60027C24C /* RxMutableBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8093CB01B8A72BE0088E94D /* RxMutableBox.swift */; };
C839365F1C70E02200A9A09E /* UIApplication+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = C839365E1C70E02200A9A09E /* UIApplication+Rx.swift */; };
C83D73B81C1DBAEE003DC470 /* AnonymousInvocable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C83D73B31C1DBAEE003DC470 /* AnonymousInvocable.swift */; };
C83D73B91C1DBAEE003DC470 /* AnonymousInvocable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C83D73B31C1DBAEE003DC470 /* AnonymousInvocable.swift */; };
C83D73BA1C1DBAEE003DC470 /* AnonymousInvocable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C83D73B31C1DBAEE003DC470 /* AnonymousInvocable.swift */; };
@ -912,6 +915,10 @@
C8FA89201C30424000CD3A17 /* VirtualTimeConverterType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8FA89121C30405400CD3A17 /* VirtualTimeConverterType.swift */; };
C8FA89211C30424000CD3A17 /* VirtualTimeConverterType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8FA89121C30405400CD3A17 /* VirtualTimeConverterType.swift */; };
C8FA89221C30424100CD3A17 /* VirtualTimeConverterType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8FA89121C30405400CD3A17 /* VirtualTimeConverterType.swift */; };
C8FD21AE1C67E14C00863EC3 /* UIBindingObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8FD21AD1C67E14C00863EC3 /* UIBindingObserver.swift */; };
C8FD21AF1C67E14C00863EC3 /* UIBindingObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8FD21AD1C67E14C00863EC3 /* UIBindingObserver.swift */; };
C8FD21B01C67E14C00863EC3 /* UIBindingObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8FD21AD1C67E14C00863EC3 /* UIBindingObserver.swift */; };
C8FD21B11C67E14C00863EC3 /* UIBindingObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8FD21AD1C67E14C00863EC3 /* UIBindingObserver.swift */; };
CB255BD71BC46A9C00798A4C /* RetryWhen.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB255BD61BC46A9C00798A4C /* RetryWhen.swift */; };
CB255BD81BC46A9C00798A4C /* RetryWhen.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB255BD61BC46A9C00798A4C /* RetryWhen.swift */; };
CB255BD91BC46A9C00798A4C /* RetryWhen.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB255BD61BC46A9C00798A4C /* RetryWhen.swift */; };
@ -1324,6 +1331,7 @@
84C225A21C33F00B008724EC /* RxTextStorageDelegateProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxTextStorageDelegateProxy.swift; sourceTree = "<group>"; };
9BA1CBD11C0F7C0A0044B50A /* UIActivityIndicatorView+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIActivityIndicatorView+Rx.swift"; sourceTree = "<group>"; };
A111CE961B91C97C00D0DCEE /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
AAE623751C82475700FC7801 /* UIProgressView+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIProgressView+Rx.swift"; sourceTree = "<group>"; };
B1B7C3BC1BDD39DB0076934E /* TakeLast.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TakeLast.swift; sourceTree = "<group>"; };
B1D8998E1BF653410027B05C /* Timeout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Timeout.swift; sourceTree = "<group>"; };
C807F3611C2ACED300017910 /* TestSchedulerVirtualTimeConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestSchedulerVirtualTimeConverter.swift; sourceTree = "<group>"; };
@ -1541,6 +1549,7 @@
C83509231C38706E0027C24C /* XCTest+AllTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "XCTest+AllTests.swift"; sourceTree = "<group>"; };
C83509841C38740E0027C24C /* AllTests-tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "AllTests-tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
C83509941C38742C0027C24C /* AllTests-OSX.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "AllTests-OSX.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
C839365E1C70E02200A9A09E /* UIApplication+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIApplication+Rx.swift"; sourceTree = "<group>"; };
C83D73B31C1DBAEE003DC470 /* AnonymousInvocable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnonymousInvocable.swift; sourceTree = "<group>"; };
C83D73B41C1DBAEE003DC470 /* InvocableScheduledItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InvocableScheduledItem.swift; sourceTree = "<group>"; };
C83D73B51C1DBAEE003DC470 /* InvocableType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InvocableType.swift; sourceTree = "<group>"; };
@ -1643,6 +1652,7 @@
C8FA89131C30405400CD3A17 /* VirtualTimeScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VirtualTimeScheduler.swift; sourceTree = "<group>"; };
C8FA89161C30409900CD3A17 /* HistoricalScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoricalScheduler.swift; sourceTree = "<group>"; };
C8FA891B1C30412A00CD3A17 /* HistoricalSchedulerTimeConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoricalSchedulerTimeConverter.swift; sourceTree = "<group>"; };
C8FD21AD1C67E14C00863EC3 /* UIBindingObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIBindingObserver.swift; sourceTree = "<group>"; };
CB255BD61BC46A9C00798A4C /* RetryWhen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RetryWhen.swift; sourceTree = "<group>"; };
CB30D9E81BF0E3500084C1C0 /* SingleAsync.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleAsync.swift; sourceTree = "<group>"; };
CB883B3A1BE24355000AC2EE /* Window.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Window.swift; sourceTree = "<group>"; };
@ -2125,6 +2135,7 @@
C80DDE8C1BCE69BA006A1832 /* Driver */,
C80D33931B922FB00014629D /* ControlEvent.swift */,
C80D33941B922FB00014629D /* ControlProperty.swift */,
C8FD21AD1C67E14C00863EC3 /* UIBindingObserver.swift */,
);
path = CocoaUnits;
sourceTree = "<group>";
@ -2312,6 +2323,7 @@
C88253F61B8A752B00B02D69 /* Protocols */,
C88253F91B8A752B00B02D69 /* Proxies */,
C88254051B8A752B00B02D69 /* UIBarButtonItem+Rx.swift */,
C839365E1C70E02200A9A09E /* UIApplication+Rx.swift */,
C88254061B8A752B00B02D69 /* UIButton+Rx.swift */,
C88254071B8A752B00B02D69 /* UICollectionView+Rx.swift */,
C88254081B8A752B00B02D69 /* UIControl+Rx.swift */,
@ -2319,6 +2331,7 @@
C882540A1B8A752B00B02D69 /* UIGestureRecognizer+Rx.swift */,
C882540B1B8A752B00B02D69 /* UIImageView+Rx.swift */,
C882540C1B8A752B00B02D69 /* UILabel+Rx.swift */,
AAE623751C82475700FC7801 /* UIProgressView+Rx.swift */,
7F600F3D1C5D0C0100535B1D /* UIRefreshControl+Rx.swift */,
C882540D1B8A752B00B02D69 /* UIScrollView+Rx.swift */,
C882540E1B8A752B00B02D69 /* UISearchBar+Rx.swift */,
@ -3241,12 +3254,14 @@
C882542E1B8A752B00B02D69 /* UILabel+Rx.swift in Sources */,
C88254211B8A752B00B02D69 /* RxSearchBarDelegateProxy.swift in Sources */,
C80DDEA71BCE69BA006A1832 /* ObservableConvertibleType+Driver.swift in Sources */,
C839365F1C70E02200A9A09E /* UIApplication+Rx.swift in Sources */,
C80DDE9F1BCE69BA006A1832 /* Driver+Subscription.swift in Sources */,
C811C89D1C24D80100A2DDD4 /* DeallocObservable.swift in Sources */,
C8BCD3ED1C14B5FB005F1280 /* UIView+Rx.swift in Sources */,
C80D338F1B91EF9E0014629D /* Observable+Bind.swift in Sources */,
C88254311B8A752B00B02D69 /* UISegmentedControl+Rx.swift in Sources */,
C8093EED1B8A732E0088E94D /* KVOObservable.swift in Sources */,
AAE623761C82475700FC7801 /* UIProgressView+Rx.swift in Sources */,
C8DB968D1BF7595D0084BD53 /* KVORepresentable+Swift.swift in Sources */,
C80DDEB11BCE8CA3006A1832 /* Driver+Operators+arity.swift in Sources */,
C88254281B8A752B00B02D69 /* UIButton+Rx.swift in Sources */,
@ -3279,6 +3294,7 @@
C88254301B8A752B00B02D69 /* UISearchBar+Rx.swift in Sources */,
C88254181B8A752B00B02D69 /* ItemEvents.swift in Sources */,
C8DB96881BF756F40084BD53 /* KVORepresentable+CoreGraphics.swift in Sources */,
C8FD21AE1C67E14C00863EC3 /* UIBindingObserver.swift in Sources */,
C882541B1B8A752B00B02D69 /* RxTableViewDataSourceType.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -3297,6 +3313,7 @@
C8DB967F1BF7496C0084BD53 /* KVORepresentable.swift in Sources */,
C8093EFE1B8A732E0088E94D /* RxTarget.swift in Sources */,
C8093ED21B8A732E0088E94D /* _RX.m in Sources */,
C8FD21AF1C67E14C00863EC3 /* UIBindingObserver.swift in Sources */,
C8093EFC1B8A732E0088E94D /* RxCocoa.swift in Sources */,
C8DB968E1BF7595D0084BD53 /* KVORepresentable+Swift.swift in Sources */,
C80D33991B922FB00014629D /* ControlEvent.swift in Sources */,
@ -4171,6 +4188,7 @@
C8F0C03B1BBBFBB9001B112F /* UISearchBar+Rx.swift in Sources */,
C8F0C03C1BBBFBB9001B112F /* ItemEvents.swift in Sources */,
C8DB968B1BF756F40084BD53 /* KVORepresentable+CoreGraphics.swift in Sources */,
C8FD21B11C67E14C00863EC3 /* UIBindingObserver.swift in Sources */,
C8F0C03D1BBBFBB9001B112F /* RxTableViewDataSourceType.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -4244,6 +4262,7 @@
D203C50D1BB9C53E00D02D00 /* UISegmentedControl+Rx.swift in Sources */,
C8C4B4C41C17727000828BD5 /* MessageSentObserver.swift in Sources */,
D2138C861BB9BEBE00339B5C /* Observable+Bind.swift in Sources */,
AAE623771C82475700FC7801 /* UIProgressView+Rx.swift in Sources */,
D203C50A1BB9C53E00D02D00 /* UILabel+Rx.swift in Sources */,
D203C4F51BB9C52900D02D00 /* ItemEvents.swift in Sources */,
C8BCD3F61C14B6D1005F1280 /* NSLayoutConstraint+Rx.swift in Sources */,
@ -4262,6 +4281,7 @@
D203C4FD1BB9C53700D02D00 /* RxSearchBarDelegateProxy.swift in Sources */,
D2138C8A1BB9BEBE00339B5C /* Logging.swift in Sources */,
C8DB968A1BF756F40084BD53 /* KVORepresentable+CoreGraphics.swift in Sources */,
C8FD21B01C67E14C00863EC3 /* UIBindingObserver.swift in Sources */,
D203C50F1BB9C53E00D02D00 /* UIStepper+Rx.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;

View File

@ -103,6 +103,12 @@
<FileRef
location = "group:HotAndColdObservables.md">
</FileRef>
<FileRef
location = "group:IssueTemplate.md">
</FileRef>
<FileRef
location = "group:Linux.md">
</FileRef>
<FileRef
location = "group:MathBehindRx.md">
</FileRef>
@ -121,9 +127,6 @@
<FileRef
location = "group:Warnings.md">
</FileRef>
<FileRef
location = "group:Linux.md">
</FileRef>
</Group>
<FileRef
location = "group:Rx.playground">

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "RxBlocking"
s.version = "2.1.0"
s.version = "2.2.0"
s.summary = "RxSwift Blocking operatos"
s.description = <<-DESC
Set of blocking operators for RxSwift. These operators are mostly intended for unit/integration tests
@ -24,5 +24,5 @@ Waiting for observable sequence to complete before exiting command line applicat
s.source_files = 'RxBlocking/**/*.swift'
s.dependency 'RxSwift', '~> 2.0'
s.dependency 'RxSwift', '~> 2.2'
end

View File

@ -11,11 +11,28 @@ import Foundation
import RxSwift
#endif
typealias AtomicInt = Int32
#if os(Linux)
func AtomicIncrement(increment: UnsafeMutablePointer<AtomicInt>) -> AtomicInt {
increment.memory = increment.memory + 1
return increment.memory
}
func AtomicDecrement(increment: UnsafeMutablePointer<AtomicInt>) -> AtomicInt {
increment.memory = increment.memory - 1
return increment.memory
}
#else
let AtomicIncrement = OSAtomicIncrement32
let AtomicDecrement = OSAtomicDecrement32
#endif
class RunLoopLock {
let currentRunLoop: CFRunLoopRef
var calledRun: Int32 = 0
var calledStop: Int32 = 0
var calledRun: AtomicInt = 0
var calledStop: AtomicInt = 0
init() {
currentRunLoop = CFRunLoopGetCurrent()
@ -37,7 +54,7 @@ class RunLoopLock {
}
func stop() {
if OSAtomicIncrement32(&calledStop) != 1 {
if AtomicIncrement(&calledStop) != 1 {
return
}
CFRunLoopPerformBlock(currentRunLoop, kCFRunLoopDefaultMode) {
@ -47,7 +64,7 @@ class RunLoopLock {
}
func run() {
if OSAtomicIncrement32(&calledRun) != 1 {
if AtomicIncrement(&calledRun) != 1 {
fatalError("Run can be only called once")
}
CFRunLoopRun()

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "RxCocoa"
s.version = "2.1.0"
s.version = "2.2.0"
s.summary = "RxSwift Cocoa extensions"
s.description = <<-DESC
* UI extensions
@ -25,5 +25,5 @@ Pod::Spec.new do |s|
s.watchos.source_files = 'RxCocoa/iOS/**/*.swift'
s.tvos.source_files = 'RxCocoa/iOS/**/*.swift'
s.dependency 'RxSwift', '~> 2.0'
s.dependency 'RxSwift', '~> 2.2'
end

View File

@ -49,10 +49,10 @@ extension CLLocationManager {
/**
Reactive wrapper for `delegate` message.
*/
public var rx_didFinishDeferredUpdatesWithError: Observable<NSError> {
public var rx_didFinishDeferredUpdatesWithError: Observable<NSError?> {
return rx_delegate.observe("locationManager:didFinishDeferredUpdatesWithError:")
.map { a in
return try castOrThrow(NSError.self, a[1])
return try castOptionalOrThrow(NSError.self, a[1])
}
}
#endif

View File

@ -0,0 +1,63 @@
//
// UIBindingObserver.swift
// Rx
//
// Created by Krunoslav Zaher on 2/7/16.
// Copyright © 2016 Krunoslav Zaher. All rights reserved.
//
import Foundation
#if !RX_NO_MODULE
import RxSwift
#endif
/**
Observer that enforces interface binding rules:
* can't bind errors (in debug builds binding of errors causes `fatalError` in release builds errors are being logged)
* ensures binding is performed on main thread
`InterfaceBindingObserver` doesn't retain target interface and in case owned interface element is released, element isn't bound.
*/
public class UIBindingObserver<UIElementType, Value where UIElementType: AnyObject> : ObserverType {
public typealias E = Value
weak var UIElement: UIElementType?
let binding: (UIElementType, Value) -> Void
/**
Initializes `ViewBindingObserver` using
*/
public init(UIElement: UIElementType, binding: (UIElementType, Value) -> Void) {
self.UIElement = UIElement
self.binding = binding
}
/**
Binds next element to owner view as described in `binding`.
*/
public func on(event: Event<Value>) {
MainScheduler.ensureExecutingOnScheduler()
switch event {
case .Next(let element):
if let view = self.UIElement {
binding(view, element)
}
case .Error(let error):
bindingErrorToInterface(error)
break
case .Completed:
break
}
}
/**
Erases type of observer.
- returns: type erased observer.
*/
public func asObserver() -> AnyObserver<Value> {
return AnyObserver(eventHandler: on)
}
}

View File

@ -196,7 +196,7 @@ public func proxyForObject<P: DelegateProxyType>(type: P.Type, _ object: AnyObje
func installDelegate<P: DelegateProxyType>(proxy: P, delegate: AnyObject, retainDelegate: Bool, onProxyForObject object: AnyObject) -> Disposable {
weak var weakDelegate: AnyObject? = delegate
assert(proxy.forwardToDelegate() === nil, "There is already a set delegate \(proxy.forwardToDelegate())")
assert(proxy.forwardToDelegate() === nil, "There is already a delegate set -> `\(proxy.forwardToDelegate())` for object -> `\(object)`.\nMaybe delegate was already set in `xib` or `storyboard` and now it's being overwritten in code.")
proxy.setForwardToDelegate(delegate, retainDelegate: retainDelegate)
@ -223,7 +223,7 @@ extension ObservableType {
-> Disposable {
let proxy = proxyForObject(P.self, object)
let disposable = installDelegate(proxy, delegate: dataSource, retainDelegate: retainDataSource, onProxyForObject: object)
let subscription = self.asObservable()
// source can't ever end, otherwise it will release the subscriber
.concat(Observable.never())

View File

@ -24,19 +24,9 @@ extension NSLayoutConstraint {
Bindable sink for `constant` property.
*/
public var rx_constant: AnyObserver<CGFloat> {
return AnyObserver { [weak self] event in
MainScheduler.ensureExecutingOnScheduler()
switch event {
case .Next(let value):
self?.constant = value
case .Error(let error):
bindingErrorToInterface(error)
break
case .Completed:
break
}
}
return UIBindingObserver(UIElement: self) { constraint, constant in
constraint.constant = constant
}.asObserver()
}
/**
@ -44,19 +34,9 @@ extension NSLayoutConstraint {
*/
@available(iOS 8, OSX 10.10, *)
public var rx_active: AnyObserver<Bool> {
return AnyObserver { [weak self] event in
MainScheduler.ensureExecutingOnScheduler()
switch event {
case .Next(let value):
self?.active = value
case .Error(let error):
bindingErrorToInterface(error)
break
case .Completed:
break
}
}
return UIBindingObserver(UIElement: self) { constraint, value in
constraint.active = value
}.asObserver()
}
}

View File

@ -20,7 +20,7 @@ extension NSNotificationCenter {
- returns: Observable sequence of posted notifications.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func rx_notification(name: String, object: AnyObject? = nil) -> Observable<NSNotification> {
public func rx_notification(name: String?, object: AnyObject? = nil) -> Observable<NSNotification> {
return Observable.create { [weak object] observer in
let nsObserver = self.addObserverForName(name, object: object, queue: nil) { notification in
observer.on(.Next(notification))

View File

@ -59,7 +59,7 @@ func escapeTerminalString(value: String) -> String {
func convertURLRequestToCurlCommand(request: NSURLRequest) -> String {
let method = request.HTTPMethod ?? "GET"
var returnValue = "curl -i -v -X \(method) "
var returnValue = "curl -X \(method) "
if request.HTTPMethod == "POST" && request.HTTPBody != nil {
let maybeBody = NSString(data: request.HTTPBody!, encoding: NSUTF8StringEncoding) as? String
@ -71,12 +71,14 @@ func convertURLRequestToCurlCommand(request: NSURLRequest) -> String {
for (key, value) in request.allHTTPHeaderFields ?? [:] {
let escapedKey = escapeTerminalString((key as String) ?? "")
let escapedValue = escapeTerminalString((value as String) ?? "")
returnValue += "-H \"\(escapedKey): \(escapedValue)\" "
returnValue += "\n -H \"\(escapedKey): \(escapedValue)\" "
}
let URLString = request.URL?.absoluteString ?? "<unknown url>"
returnValue += "\"\(escapeTerminalString(URLString))\""
returnValue += "\n\"\(escapeTerminalString(URLString))\""
returnValue += " -i -v"
return returnValue
}

View File

@ -278,7 +278,7 @@ func castOrFatalError<T>(value: AnyObject!, message: String) -> T {
return result
}
func castOrFatalError<T>(value: AnyObject!) -> T {
func castOrFatalError<T>(value: Any!) -> T {
let maybeResult: T? = value as? T
guard let result = maybeResult else {
rxFatalError("Failure converting from \(value) to \(T.self)")

View File

@ -41,6 +41,8 @@ static NSMutableDictionary *forwardableSelectorsPerClass = nil;
for (unsigned int i = 0; i < numberOfBaseProtocols; ++i) {
[selectors unionSet:[self collectSelectorsForProtocol:pSubprotocols[i]]];
}
free(pSubprotocols);
return selectors;
}

View File

@ -25,10 +25,13 @@ extension NSButton {
Reactive wrapper for `state` property`.
*/
public var rx_state: ControlProperty<Int> {
return rx_value(getter: { [weak self] in
return self?.state ?? 0
}, setter: { [weak self] state in
self?.state = state
})
return NSButton.rx_value(
self,
getter: { control in
return control.state
}, setter: { control, state in
control.state = state
}
)
}
}

View File

@ -43,57 +43,48 @@ extension NSControl {
return ControlEvent(events: source)
}
func rx_value<T: Equatable>(getter getter: () -> T, setter: T -> Void) -> ControlProperty<T> {
/**
You might be wondering why the ugly `as!` casts etc, well, for some reason if
Swift compiler knows C is UIControl type and optimizations are turned on, it will crash.
Can somebody offer poor Swift compiler writers some other better job maybe, this is becoming
ridiculous. So much time wasted ...
*/
static func rx_value<C: AnyObject, T: Equatable>(control: C, getter: (C) -> T, setter: (C, T) -> Void) -> ControlProperty<T> {
MainScheduler.ensureExecutingOnScheduler()
let source = rx_lazyInstanceObservable(&rx_value_key) { () -> Observable<T> in
return Observable.create { [weak self] observer in
guard let control = self else {
let source = (control as! NSObject).rx_lazyInstanceObservable(&rx_value_key) { () -> Observable<T> in
return Observable.create { [weak weakControl = control] (observer: AnyObserver<T>) in
guard let control = weakControl else {
observer.on(.Completed)
return NopDisposable.instance
}
observer.on(.Next(getter()))
observer.on(.Next(getter(control)))
let observer = ControlTarget(control: control) { control in
observer.on(.Next(getter()))
let observer = ControlTarget(control: control as! NSControl) { _ in
if let control = weakControl {
observer.on(.Next(getter(control)))
}
}
return observer
}
.distinctUntilChanged()
.takeUntil(self.rx_deallocated)
.distinctUntilChanged()
.takeUntil((control as! NSObject).rx_deallocated)
}
let bindingObserver = UIBindingObserver(UIElement: control, binding: setter)
return ControlProperty(values: source, valueSink: AnyObserver { event in
switch event {
case .Next(let value):
setter(value)
case .Error(let error):
bindingErrorToInterface(error)
case .Completed:
break
}
})
return ControlProperty(values: source, valueSink: bindingObserver)
}
/**
Bindable sink for `enabled` property.
*/
public var rx_enabled: AnyObserver<Bool> {
return AnyObserver { [weak self] event in
MainScheduler.ensureExecutingOnScheduler()
switch event {
case .Next(let value):
self?.enabled = value
case .Error(let error):
bindingErrorToInterface(error)
break
case .Completed:
break
}
}
return UIBindingObserver(UIElement: self) { (owner, value) in
owner.enabled = value
}.asObserver()
}
}

View File

@ -27,31 +27,21 @@ extension NSImageView {
- parameter transitionType: Optional transition type while setting the image (kCATransitionFade, kCATransitionMoveIn, ...)
*/
public func rx_imageAnimated(transitionType: String?) -> AnyObserver<NSImage?> {
return AnyObserver { [weak self] event in
MainScheduler.ensureExecutingOnScheduler()
switch event {
case .Next(let value):
if let transitionType = transitionType {
if value != nil {
let transition = CATransition()
transition.duration = 0.25
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = transitionType
self?.layer?.addAnimation(transition, forKey: kCATransition)
}
return UIBindingObserver(UIElement: self) { control, value in
if let transitionType = transitionType {
if value != nil {
let transition = CATransition()
transition.duration = 0.25
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = transitionType
control.layer?.addAnimation(transition, forKey: kCATransition)
}
else {
self?.layer?.removeAllAnimations()
}
self?.image = value
case .Error(let error):
bindingErrorToInterface(error)
break
case .Completed:
break
}
}
else {
control.layer?.removeAllAnimations()
}
control.image = value
}.asObserver()
}
}

View File

@ -18,11 +18,15 @@ extension NSSlider {
Reactive wrapper for `value` property.
*/
public var rx_value: ControlProperty<Double> {
return rx_value(getter: { [weak self] in
return self?.doubleValue ?? 0
}, setter: { [weak self] value in
self?.doubleValue = value
})
return NSControl.rx_value(
self,
getter: { control in
return control.doubleValue
},
setter: { control, value in
control.doubleValue = value
}
)
}
}

View File

@ -105,20 +105,12 @@ extension NSTextField {
let source = Observable.deferred { [weak self] in
delegate.textSubject.startWith(self?.stringValue ?? "")
}.takeUntil(rx_deallocated)
return ControlProperty(values: source, valueSink: AnyObserver { [weak self] event in
MainScheduler.ensureExecutingOnScheduler()
switch event {
case .Next(let value):
self?.stringValue = value
case .Error(let error):
bindingErrorToInterface(error)
break
case .Completed:
break
}
})
let observer = UIBindingObserver(UIElement: self) { control, value in
control.stringValue = value
}
return ControlProperty(values: source, valueSink: observer.asObserver())
}
}

View File

@ -17,37 +17,17 @@ extension NSView {
Bindable sink for `hidden` property.
*/
public var rx_hidden: AnyObserver<Bool> {
return AnyObserver { [weak self] event in
MainScheduler.ensureExecutingOnScheduler()
switch event {
case .Next(let value):
self?.hidden = value
case .Error(let error):
bindingErrorToInterface(error)
break
case .Completed:
break
}
}
return UIBindingObserver(UIElement: self) { view, value in
view.hidden = value
}.asObserver()
}
/**
Bindable sink for `alphaValue` property.
*/
public var rx_alpha: AnyObserver<CGFloat> {
return AnyObserver { [weak self] event in
MainScheduler.ensureExecutingOnScheduler()
switch event {
case .Next(let value):
self?.alphaValue = value
case .Error(let error):
bindingErrorToInterface(error)
break
case .Completed:
break
}
}
return UIBindingObserver(UIElement: self) { view, value in
view.alphaValue = value
}.asObserver()
}
}

View File

@ -50,15 +50,10 @@ class RxCollectionViewReactiveArrayDataSourceSequenceWrapper<S: SequenceType>
}
func collectionView(collectionView: UICollectionView, observedEvent: Event<S>) {
switch observedEvent {
case .Next(let value):
super.collectionView(collectionView, observedElements: Array(value))
self.itemModels = Array(value)
case .Error(let error):
bindingErrorToInterface(error)
case .Completed:
break
}
UIBindingObserver(UIElement: self) { collectionViewDataSource, sectionModels in
let sections = Array(sectionModels)
collectionViewDataSource.collectionView(collectionView, observedElements: sections)
}.on(observedEvent)
}
}

View File

@ -51,14 +51,10 @@ class RxTableViewReactiveArrayDataSourceSequenceWrapper<S: SequenceType>
}
func tableView(tableView: UITableView, observedEvent: Event<S>) {
switch observedEvent {
case .Next(let value):
super.tableView(tableView, observedElements: Array(value))
case .Error(let error):
bindingErrorToInterface(error)
case .Completed:
break
}
UIBindingObserver(UIElement: self) { tableViewDataSource, sectionModels in
let sections = Array(sectionModels)
tableViewDataSource.tableView(tableView, observedElements: sections)
}.on(observedEvent)
}
}

View File

@ -19,22 +19,13 @@ extension UIActivityIndicatorView {
Bindable sink for `startAnimating()`, `stopAnimating()` methods.
*/
public var rx_animating: AnyObserver<Bool> {
return AnyObserver {event in
MainScheduler.ensureExecutingOnScheduler()
switch (event) {
case .Next(let value):
if value {
self.startAnimating()
} else {
self.stopAnimating()
}
case .Error(let error):
bindingErrorToInterface(error)
case .Completed:
break
return UIBindingObserver(UIElement: self) { activityIndicator, active in
if active {
self.startAnimating()
} else {
self.stopAnimating()
}
}
}.asObserver()
}
}

View File

@ -8,30 +8,23 @@
import Foundation
#if os(iOS) || os(tvOS)
#if os(iOS)
import UIKit
#if !RX_NO_MODULE
import RxSwift
#endif
extension UIApplication {
/**
Bindable sink for `networkActivityIndicatorVisible`.
*/
public var rx_networkActivityIndicatorVisible: AnyObserver<Bool> {
return AnyObserver { event in
MainScheduler.ensureExecutingOnScheduler()
switch event {
case .Next(let value):
self.networkActivityIndicatorVisible = value
case .Error(let error):
bindingErrorToInterface(error)
case .Completed:
break
}
}
return UIBindingObserver(UIElement: self) { application, active in
application.networkActivityIndicatorVisible = active
}.asObserver()
}
}
#endif
#endif

View File

@ -21,19 +21,9 @@ extension UIBarButtonItem {
Bindable sink for `enabled` property.
*/
public var rx_enabled: AnyObserver<Bool> {
return AnyObserver { [weak self] event in
MainScheduler.ensureExecutingOnScheduler()
switch event {
case .Next(let value):
self?.enabled = value
case .Error(let error):
bindingErrorToInterface(error)
break
case .Completed:
break
}
}
return UIBindingObserver(UIElement: self) { UIElement, value in
UIElement.enabled = value
}.asObserver()
}
/**

View File

@ -27,10 +27,13 @@ extension UICollectionView {
*/
public func rx_itemsWithCellFactory<S: SequenceType, O: ObservableType where O.E == S>
(source: O)
(cellFactory: (UICollectionView, Int, S.Generator.Element) -> UICollectionViewCell)
-> (cellFactory: (UICollectionView, Int, S.Generator.Element) -> UICollectionViewCell)
-> Disposable {
let dataSource = RxCollectionViewReactiveArrayDataSourceSequenceWrapper<S>(cellFactory: cellFactory)
return self.rx_itemsWithDataSource(dataSource)(source: source)
return { cellFactory in
let dataSource = RxCollectionViewReactiveArrayDataSourceSequenceWrapper<S>(cellFactory: cellFactory)
return self.rx_itemsWithDataSource(dataSource)(source: source)
}
}
/**
@ -44,17 +47,21 @@ extension UICollectionView {
*/
public func rx_itemsWithCellIdentifier<S: SequenceType, Cell: UICollectionViewCell, O : ObservableType where O.E == S>
(cellIdentifier: String, cellType: Cell.Type = Cell.self)
(source: O)
(configureCell: (Int, S.Generator.Element, Cell) -> Void)
-> (source: O)
-> (configureCell: (Int, S.Generator.Element, Cell) -> Void)
-> Disposable {
let dataSource = RxCollectionViewReactiveArrayDataSourceSequenceWrapper<S> { (cv, i, item) in
let indexPath = NSIndexPath(forItem: i, inSection: 0)
let cell = cv.dequeueReusableCellWithReuseIdentifier(cellIdentifier, forIndexPath: indexPath) as! Cell
configureCell(i, item, cell)
return cell
return { source in
return { configureCell in
let dataSource = RxCollectionViewReactiveArrayDataSourceSequenceWrapper<S> { (cv, i, item) in
let indexPath = NSIndexPath(forItem: i, inSection: 0)
let cell = cv.dequeueReusableCellWithReuseIdentifier(cellIdentifier, forIndexPath: indexPath) as! Cell
configureCell(i, item, cell)
return cell
}
return self.rx_itemsWithDataSource(dataSource)(source: source)
}
}
return self.rx_itemsWithDataSource(dataSource)(source: source)
}
/**
@ -66,13 +73,15 @@ extension UICollectionView {
*/
public func rx_itemsWithDataSource<DataSource: protocol<RxCollectionViewDataSourceType, UICollectionViewDataSource>, S: SequenceType, O: ObservableType where DataSource.Element == S, O.E == S>
(dataSource: DataSource)
(source: O)
-> (source: O)
-> Disposable {
return source.subscribeProxyDataSourceForObject(self, dataSource: dataSource, retainDataSource: false) { [weak self] (_: RxCollectionViewDataSourceProxy, event) -> Void in
guard let collectionView = self else {
return
return { source in
return source.subscribeProxyDataSourceForObject(self, dataSource: dataSource, retainDataSource: false) { [weak self] (_: RxCollectionViewDataSourceProxy, event) -> Void in
guard let collectionView = self else {
return
}
dataSource.collectionView(collectionView, observedEvent: event)
}
dataSource.collectionView(collectionView, observedEvent: event)
}
}
}

View File

@ -20,19 +20,18 @@ extension UIControl {
Bindable sink for `enabled` property.
*/
public var rx_enabled: AnyObserver<Bool> {
return AnyObserver { [weak self] event in
MainScheduler.ensureExecutingOnScheduler()
switch event {
case .Next(let value):
self?.enabled = value
case .Error(let error):
bindingErrorToInterface(error)
break
case .Completed:
break
}
}
return UIBindingObserver(UIElement: self) { control, value in
control.enabled = value
}.asObserver()
}
/**
Bindable sink for `selected` property.
*/
public var rx_selected: AnyObserver<Bool> {
return UIBindingObserver(UIElement: self) { control, selected in
control.selected = selected
}.asObserver()
}
/**
@ -62,39 +61,38 @@ extension UIControl {
return ControlEvent(events: source)
}
func rx_value<T: Equatable>(getter getter: () -> T, setter: T -> Void) -> ControlProperty<T> {
let source: Observable<T> = Observable.create { [weak self] observer in
guard let control = self else {
observer.on(.Completed)
return NopDisposable.instance
}
/**
You might be wondering why the ugly `as!` casts etc, well, for some reason if
Swift compiler knows C is UIControl type and optimizations are turned on, it will crash.
observer.on(.Next(getter()))
Can somebody offer poor Swift compiler writers some other better job maybe, this is becoming
ridiculous. So much time wasted ...
*/
static func rx_value<C: AnyObject, T: Equatable>(control: C, getter: (C) -> T, setter: (C, T) -> Void) -> ControlProperty<T> {
let source: Observable<T> = Observable.create { [weak weakControl = control] observer in
guard let control = weakControl else {
observer.on(.Completed)
return NopDisposable.instance
}
let controlTarget = ControlTarget(control: control, controlEvents: [.AllEditingEvents, .ValueChanged]) { control in
observer.on(.Next(getter()))
observer.on(.Next(getter(control)))
let controlTarget = ControlTarget(control: control as! UIControl, controlEvents: [.AllEditingEvents, .ValueChanged]) { _ in
if let control = weakControl {
observer.on(.Next(getter(control)))
}
}
return AnonymousDisposable {
controlTarget.dispose()
}
}
return AnonymousDisposable {
controlTarget.dispose()
}
}
.distinctUntilChanged()
.takeUntil(rx_deallocated)
return ControlProperty<T>(values: source, valueSink: AnyObserver { event in
MainScheduler.ensureExecutingOnScheduler()
.takeUntil((control as! NSObject).rx_deallocated)
switch event {
case .Next(let value):
setter(value)
case .Error(let error):
bindingErrorToInterface(error)
break
case .Completed:
break
}
})
let bindingObserver = UIBindingObserver(UIElement: control, binding: setter)
return ControlProperty<T>(values: source, valueSink: bindingObserver)
}
}

View File

@ -20,11 +20,14 @@ extension UIDatePicker {
Reactive wrapper for `date` property.
*/
public var rx_date: ControlProperty<NSDate> {
return rx_value(getter: { [weak self] in
self?.date ?? NSDate()
}, setter: { [weak self] value in
self?.date = value
})
return UIControl.rx_value(
self,
getter: { datePicker in
datePicker.date
}, setter: { datePicker, value in
datePicker.date = value
}
)
}
}

View File

@ -29,31 +29,21 @@ extension UIImageView {
- parameter transitionType: Optional transition type while setting the image (kCATransitionFade, kCATransitionMoveIn, ...)
*/
public func rx_imageAnimated(transitionType: String?) -> AnyObserver<UIImage?> {
return AnyObserver { [weak self] event in
MainScheduler.ensureExecutingOnScheduler()
switch event {
case .Next(let value):
if let transitionType = transitionType {
if value != nil {
let transition = CATransition()
transition.duration = 0.25
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = transitionType
self?.layer.addAnimation(transition, forKey: kCATransition)
}
return UIBindingObserver(UIElement: self) { imageView, image in
if let transitionType = transitionType {
if image != nil {
let transition = CATransition()
transition.duration = 0.25
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = transitionType
imageView.layer.addAnimation(transition, forKey: kCATransition)
}
else {
self?.layer.removeAllAnimations()
}
self?.image = value
case .Error(let error):
bindingErrorToInterface(error)
break
case .Completed:
break
}
}
else {
imageView.layer.removeAllAnimations()
}
imageView.image = image
}.asObserver()
}
}

View File

@ -20,38 +20,18 @@ extension UILabel {
Bindable sink for `text` property.
*/
public var rx_text: AnyObserver<String> {
return AnyObserver { [weak self] event in
MainScheduler.ensureExecutingOnScheduler()
switch event {
case .Next(let value):
self?.text = value
case .Error(let error):
bindingErrorToInterface(error)
break
case .Completed:
break
}
}
return UIBindingObserver(UIElement: self) { label, text in
label.text = text
}.asObserver()
}
/**
Bindable sink for `attributedText` property.
*/
public var rx_attributedText: AnyObserver<NSAttributedString?> {
return AnyObserver { [weak self] event in
MainScheduler.ensureExecutingOnScheduler()
switch event {
case .Next(let value):
self?.attributedText = value
case .Error(let error):
bindingErrorToInterface(error)
break
case .Completed:
break
}
}
return UIBindingObserver(UIElement: self) { label, text in
label.attributedText = text
}.asObserver()
}
}

View File

@ -0,0 +1,30 @@
//
// UIProgressView+Rx.swift
// Rx
//
// Created by Samuel Bae on 2/27/16.
// Copyright © 2016 Krunoslav Zaher. All rights reserved.
//
#if os(iOS) || os(tvOS)
import Foundation
#if !RX_NO_MODULE
import RxSwift
#endif
import UIKit
extension UIProgressView {
/**
Bindable sink for `progress` property
*/
public var rx_progress: AnyObserver<Float> {
return UIBindingObserver(UIElement: self) { progressView, progress in
progressView.progress = progress
}.asObserver()
}
}
#endif

View File

@ -19,23 +19,13 @@ extension UIRefreshControl {
Bindable sink for `beginRefreshing()`, `endRefreshing()` methods.
*/
public var rx_refreshing: AnyObserver<Bool> {
return AnyObserver {event in
MainScheduler.ensureExecutingOnScheduler()
switch (event) {
case .Next(let value):
if value {
self.beginRefreshing()
} else {
self.endRefreshing()
}
case .Error(let error):
bindingErrorToInterface(error)
break
case .Completed:
break
return UIBindingObserver(UIElement: self) { refreshControl, refresh in
if refresh {
refreshControl.beginRefreshing()
} else {
refreshControl.endRefreshing()
}
}
}.asObserver()
}
}

View File

@ -39,17 +39,12 @@ extension UIScrollView {
*/
public var rx_contentOffset: ControlProperty<CGPoint> {
let proxy = proxyForObject(RxScrollViewDelegateProxy.self, self)
return ControlProperty(values: proxy.contentOffsetSubject, valueSink: AnyObserver { [weak self] event in
switch event {
case .Next(let value):
self?.contentOffset = value
case .Error(let error):
bindingErrorToInterface(error)
case .Completed:
break
}
})
let bindingObserver = UIBindingObserver(UIElement: self) { scrollView, contentOffset in
scrollView.contentOffset = contentOffset
}
return ControlProperty(values: proxy.contentOffsetSubject, valueSink: bindingObserver)
}
/**

View File

@ -40,17 +40,12 @@ extension UISearchBar {
}
.startWith(text)
}
let bindingObserver = UIBindingObserver(UIElement: self) { (searchBar, text: String) in
searchBar.text = text
}
return ControlProperty(values: source, valueSink: AnyObserver { [weak self] event in
switch event {
case .Next(let value):
self?.text = value
case .Error(let error):
bindingErrorToInterface(error)
case .Completed:
break
}
})
return ControlProperty(values: source, valueSink: bindingObserver)
}
}

View File

@ -20,11 +20,14 @@ extension UISegmentedControl {
Reactive wrapper for `selectedSegmentIndex` property.
*/
public var rx_value: ControlProperty<Int> {
return rx_value(getter: { [weak self] in
self?.selectedSegmentIndex ?? 0
}, setter: { [weak self] value in
self?.selectedSegmentIndex = value
})
return UIControl.rx_value(
self,
getter: { segmentedControl in
segmentedControl.selectedSegmentIndex
}, setter: { segmentedControl, value in
segmentedControl.selectedSegmentIndex = value
}
)
}
}

View File

@ -20,11 +20,14 @@ extension UISlider {
Reactive wrapper for `value` property.
*/
public var rx_value: ControlProperty<Float> {
return rx_value(getter: { [weak self] in
self?.value ?? 0.0
}, setter: { [weak self] value in
self?.value = value
})
return UIControl.rx_value(
self,
getter: { slider in
slider.value
}, setter: { slider, value in
slider.value = value
}
)
}
}

View File

@ -20,11 +20,14 @@ extension UIStepper {
Reactive wrapper for `value` property.
*/
public var rx_value: ControlProperty<Double> {
return rx_value(getter: { [weak self] in
self?.value ?? 0
}, setter: { [weak self] value in
self?.value = value
})
return UIControl.rx_value(
self,
getter: { stepper in
stepper.value
}, setter: { stepper, value in
stepper.value = value
}
)
}
}

View File

@ -20,11 +20,14 @@ extension UISwitch {
Reactive wrapper for `on` property.
*/
public var rx_value: ControlProperty<Bool> {
return rx_value(getter: { [weak self] in
self?.on ?? false
}, setter: { [weak self] value in
self?.on = value
})
return UIControl.rx_value(
self,
getter: { uiSwitch in
uiSwitch.on
}, setter: { uiSwitch, value in
uiSwitch.on = value
}
)
}
}

View File

@ -27,11 +27,13 @@ extension UITableView {
*/
public func rx_itemsWithCellFactory<S: SequenceType, O: ObservableType where O.E == S>
(source: O)
(cellFactory: (UITableView, Int, S.Generator.Element) -> UITableViewCell)
-> (cellFactory: (UITableView, Int, S.Generator.Element) -> UITableViewCell)
-> Disposable {
let dataSource = RxTableViewReactiveArrayDataSourceSequenceWrapper<S>(cellFactory: cellFactory)
return self.rx_itemsWithDataSource(dataSource)(source: source)
return { cellFactory in
let dataSource = RxTableViewReactiveArrayDataSourceSequenceWrapper<S>(cellFactory: cellFactory)
return self.rx_itemsWithDataSource(dataSource)(source: source)
}
}
/**
@ -45,17 +47,20 @@ extension UITableView {
*/
public func rx_itemsWithCellIdentifier<S: SequenceType, Cell: UITableViewCell, O : ObservableType where O.E == S>
(cellIdentifier: String, cellType: Cell.Type = Cell.self)
(source: O)
(configureCell: (Int, S.Generator.Element, Cell) -> Void)
-> (source: O)
-> (configureCell: (Int, S.Generator.Element, Cell) -> Void)
-> Disposable {
let dataSource = RxTableViewReactiveArrayDataSourceSequenceWrapper<S> { (tv, i, item) in
let indexPath = NSIndexPath(forItem: i, inSection: 0)
let cell = tv.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! Cell
configureCell(i, item, cell)
return cell
return { source in
return { configureCell in
let dataSource = RxTableViewReactiveArrayDataSourceSequenceWrapper<S> { (tv, i, item) in
let indexPath = NSIndexPath(forItem: i, inSection: 0)
let cell = tv.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! Cell
configureCell(i, item, cell)
return cell
}
return self.rx_itemsWithDataSource(dataSource)(source: source)
}
}
return self.rx_itemsWithDataSource(dataSource)(source: source)
}
/**
@ -67,13 +72,15 @@ extension UITableView {
*/
public func rx_itemsWithDataSource<DataSource: protocol<RxTableViewDataSourceType, UITableViewDataSource>, S: SequenceType, O: ObservableType where DataSource.Element == S, O.E == S>
(dataSource: DataSource)
(source: O)
-> (source: O)
-> Disposable {
return source.subscribeProxyDataSourceForObject(self, dataSource: dataSource, retainDataSource: false) { [weak self] (_: RxTableViewDataSourceProxy, event) -> Void in
guard let tableView = self else {
return
return { source in
return source.subscribeProxyDataSourceForObject(self, dataSource: dataSource, retainDataSource: false) { [weak self] (_: RxTableViewDataSourceProxy, event) -> Void in
guard let tableView = self else {
return
}
dataSource.tableView(tableView, observedEvent: event)
}
dataSource.tableView(tableView, observedEvent: event)
}
}
}
@ -148,6 +155,18 @@ extension UITableView {
return ControlEvent(events: source)
}
/**
Reactive wrapper for `delegate` message `tableView:accessoryButtonTappedForRowWithIndexPath:`.
*/
public var rx_itemAccessoryButtonTapped: ControlEvent<NSIndexPath> {
let source: Observable<NSIndexPath> = rx_delegate.observe("tableView:accessoryButtonTappedForRowWithIndexPath:")
.map { a in
return a[1] as! NSIndexPath
}
return ControlEvent(events: source)
}
/**
Reactive wrapper for `delegate` message `tableView:commitEditingStyle:forRowAtIndexPath:`.
*/
@ -244,7 +263,7 @@ extension UITableView {
let element = try dataSource.modelAtIndexPath(indexPath)
return element as! T
return castOrFatalError(element)
}
}

View File

@ -20,11 +20,14 @@ extension UITextField {
Reactive wrapper for `text` property.
*/
public var rx_text: ControlProperty<String> {
return rx_value(getter: { [weak self] in
self?.text ?? ""
}, setter: { [weak self] value in
self?.text = value
})
return UIControl.rx_value(
self,
getter: { textField in
textField.text ?? ""
}, setter: { textField, value in
textField.text = value
}
)
}
}

View File

@ -45,17 +45,12 @@ extension UITextView {
.startWith(text)
.distinctUntilChanged()
}
let bindingObserver = UIBindingObserver(UIElement: self) { (textView, text: String) in
textView.text = text
}
return ControlProperty(values: source, valueSink: AnyObserver { [weak self] event in
switch event {
case .Next(let value):
self?.text = value
case .Error(let error):
bindingErrorToInterface(error)
case .Completed:
break
}
})
return ControlProperty(values: source, valueSink: bindingObserver)
}
}

View File

@ -19,38 +19,18 @@ extension UIView {
Bindable sink for `hidden` property.
*/
public var rx_hidden: AnyObserver<Bool> {
return AnyObserver { [weak self] event in
MainScheduler.ensureExecutingOnScheduler()
switch event {
case .Next(let value):
self?.hidden = value
case .Error(let error):
bindingErrorToInterface(error)
break
case .Completed:
break
}
}
return UIBindingObserver(UIElement: self) { view, hidden in
view.hidden = hidden
}.asObserver()
}
/**
Bindable sink for `alpha` property.
*/
public var rx_alpha: AnyObserver<CGFloat> {
return AnyObserver { [weak self] event in
MainScheduler.ensureExecutingOnScheduler()
switch event {
case .Next(let value):
self?.alpha = value
case .Error(let error):
bindingErrorToInterface(error)
break
case .Completed:
break
}
}
return UIBindingObserver(UIElement: self) { view, alpha in
view.alpha = alpha
}.asObserver()
}
}

View File

@ -22,24 +22,17 @@ class RxCollectionViewSectionedAnimatedDataSource<S: SectionModelType> : RxColle
var set = false
func collectionView(collectionView: UICollectionView, observedEvent: Event<Element>) {
switch observedEvent {
case .Next(let element):
UIBindingObserver(UIElement: self) { ds, element in
for c in element {
//print("Animating ==============================\n\(c)\n===============================\n")
if !set {
setSections(c.finalSections)
if !ds.set {
ds.setSections(c.finalSections)
collectionView.reloadData()
set = true
ds.set = true
return
}
setSections(c.finalSections)
ds.setSections(c.finalSections)
collectionView.performBatchUpdates(c)
}
case .Error(let error):
bindingErrorToInterface(error)
case .Completed:
break
}
}.on(observedEvent)
}
}

View File

@ -18,14 +18,9 @@ class RxCollectionViewSectionedReloadDataSource<S: SectionModelType> : RxCollect
typealias Element = [S]
func collectionView(collectionView: UICollectionView, observedEvent: Event<Element>) {
switch observedEvent {
case .Next(let element):
setSections(element)
UIBindingObserver(UIElement: self) { dataSource, element in
dataSource.setSections(element)
collectionView.reloadData()
case .Error(let error):
bindingErrorToInterface(error)
case .Completed:
break
}
}.on(observedEvent)
}
}

View File

@ -21,11 +21,10 @@ class RxTableViewSectionedAnimatedDataSource<S: SectionModelType> : RxTableViewS
typealias Element = [Changeset<S>]
func tableView(tableView: UITableView, observedEvent: Event<Element>) {
switch observedEvent {
case .Next(let element):
UIBindingObserver(UIElement: self) { dataSource, element in
for c in element {
//print("Animating ==============================\n\(c)\n===============================\n")
setSections(c.finalSections)
dataSource.setSections(c.finalSections)
if c.reloadData {
tableView.reloadData()
}
@ -33,10 +32,6 @@ class RxTableViewSectionedAnimatedDataSource<S: SectionModelType> : RxTableViewS
tableView.performBatchUpdates(c)
}
}
case .Error(let error):
bindingErrorToInterface(error)
case .Completed:
break
}
}.on(observedEvent)
}
}

View File

@ -21,14 +21,9 @@ class RxTableViewSectionedReloadDataSource<S: SectionModelType> : RxTableViewSec
typealias Element = [S]
func tableView(tableView: UITableView, observedEvent: Event<Element>) {
switch observedEvent {
case .Next(let element):
setSections(element)
UIBindingObserver(UIElement: self) { dataSource, element in
dataSource.setSections(element)
tableView.reloadData()
case .Error(let error):
bindingErrorToInterface(error)
case .Completed:
break
}
}.on(observedEvent)
}
}

View File

@ -8,14 +8,3 @@
import Foundation
#if !RX_NO_MODULE
func bindingErrorToInterface(error: ErrorType) {
let error = "Binding error to UI: \(error)"
#if DEBUG
fatalError(error)
#else
print(error)
#endif
}
#endif

View File

@ -402,6 +402,8 @@
C8DF92EA1B0B38C0009BCF9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C8DF92E91B0B38C0009BCF9A /* Images.xcassets */; };
C8DF92EB1B0B38C0009BCF9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C8DF92E91B0B38C0009BCF9A /* Images.xcassets */; };
C8E9D2AF1BD3FD960079D0DB /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C80397391BD3E17D009D8B26 /* ActivityIndicator.swift */; };
C8F3FFF11C6FD2FA00E60EEC /* UIApplication+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EE18D11C4D68F900834224 /* UIApplication+Rx.swift */; };
C8F3FFF51C6FD62E00E60EEC /* UIBindingObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8F3FFF41C6FD62E00E60EEC /* UIBindingObserver.swift */; };
C8F6A12B1BEF9DA3007DF367 /* ConcurrentDispatchQueueScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C894648E1BC6C2B00055219D /* ConcurrentDispatchQueueScheduler.swift */; };
C8F6A12C1BEF9DA3007DF367 /* ConcurrentMainScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B145051BD2E45200267DCE /* ConcurrentMainScheduler.swift */; };
C8F6A12D1BEF9DA3007DF367 /* CurrentThreadScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C894648F1BC6C2B00055219D /* CurrentThreadScheduler.swift */; };
@ -923,6 +925,7 @@
C8DF92E91B0B38C0009BCF9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
C8DF92F01B0B3E67009BCF9A /* Info-OSX.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-OSX.plist"; sourceTree = "<group>"; };
C8DF92F21B0B3E71009BCF9A /* Info-iOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-iOS.plist"; sourceTree = "<group>"; };
C8F3FFF41C6FD62E00E60EEC /* UIBindingObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIBindingObserver.swift; sourceTree = "<group>"; };
C8F6A1361BEF9DD4007DF367 /* RetryWhen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RetryWhen.swift; sourceTree = "<group>"; };
C8F8C4891C277F460047640B /* CalculatorState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CalculatorState.swift; sourceTree = "<group>"; };
C8F8C49C1C277F4F0047640B /* CalculatorAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CalculatorAction.swift; sourceTree = "<group>"; };
@ -1550,6 +1553,7 @@
C89465171BC6C2BC0055219D /* CocoaUnits */ = {
isa = PBXGroup;
children = (
C8F3FFF41C6FD62E00E60EEC /* UIBindingObserver.swift */,
C80DDEC11BCE9041006A1832 /* Driver */,
C89465191BC6C2BC0055219D /* ControlEvent.swift */,
C894651B1BC6C2BC0055219D /* ControlProperty.swift */,
@ -2246,6 +2250,7 @@
C8F6A1311BEF9DA3007DF367 /* OperationQueueScheduler.swift in Sources */,
CBEE77541BD8C7B700AD584C /* ToArray.swift in Sources */,
C89465771BC6C2BC0055219D /* NSNotificationCenter+Rx.swift in Sources */,
C8F3FFF51C6FD62E00E60EEC /* UIBindingObserver.swift in Sources */,
C89465091BC6C2B00055219D /* ReplaySubject.swift in Sources */,
C8BCD3E01C1480E9005F1280 /* Operators.swift in Sources */,
C84CC58E1BDD486300E06A64 /* SynchronizedSubscribeType.swift in Sources */,
@ -2286,6 +2291,7 @@
C89465901BC6C2BC0055219D /* UIButton+Rx.swift in Sources */,
C89464DD1BC6C2B00055219D /* Sink.swift in Sources */,
C89464BE1BC6C2B00055219D /* Catch.swift in Sources */,
C8F3FFF11C6FD2FA00E60EEC /* UIApplication+Rx.swift in Sources */,
C89CDB721BCC45EE002063D9 /* SkipUntil.swift in Sources */,
B1604CCB1BE5BC45002E1279 /* UIImageView+DownloadableImage.swift in Sources */,
C8297E471B6CF905000589EA /* ViewController.swift in Sources */,
@ -2429,7 +2435,6 @@
C8984C491C36A579001E4272 /* SectionModel.swift in Sources */,
C83367251AD029AE00C668A7 /* ImageService.swift in Sources */,
C86E2F471AE5A0CA00C31024 /* WikipediaSearchResult.swift in Sources */,
E3EE18D21C4D68F900834224 /* UIApplication+Rx.swift in Sources */,
C8984CD11C36BC3E001E4272 /* NumberCell.swift in Sources */,
C8A2A2C81B4049E300F11F09 /* PseudoRandomGenerator.swift in Sources */,
C8D132151C42B54B00B59FFF /* UIImagePickerController+RxCreate.swift in Sources */,

View File

@ -15,36 +15,24 @@ import CoreLocation
private extension UILabel {
var rx_driveCoordinates: AnyObserver<CLLocationCoordinate2D> {
return AnyObserver { [weak self] event in
guard let _self = self else { return }
switch event {
case let .Next(location):
_self.text = "Lat: \(location.latitude)\nLon: \(location.longitude)"
default:
break
}
}
return UIBindingObserver(UIElement: self) { label, location in
label.text = "Lat: \(location.latitude)\nLon: \(location.longitude)"
}.asObserver()
}
}
private extension UIView {
var rx_driveAuthorization: AnyObserver<Bool> {
return AnyObserver { [weak self] event in
guard let _self = self else { return }
switch event {
case let .Next(autorized):
if autorized {
_self.hidden = true
_self.superview?.sendSubviewToBack(_self)
}
else {
_self.hidden = false
_self.superview?.bringSubviewToFront(_self)
}
default:
break
return UIBindingObserver(UIElement: self) { label, authorized in
if authorized {
label.hidden = true
label.superview?.sendSubviewToBack(label)
}
}
else {
label.hidden = false
label.superview?.bringSubviewToFront(label)
}
}.asObserver()
}
}
@ -59,15 +47,16 @@ class GeolocationViewController: ViewController {
super.viewDidLoad()
let geolocationService = GeolocationService.instance
geolocationService.autorized
.drive(noGeolocationView.rx_driveAuthorization)
.addDisposableTo(disposeBag)
/*
geolocationService.location
.drive(label.rx_driveCoordinates)
.addDisposableTo(disposeBag)
button.rx_tap
.bindNext { [weak self] in
self?.openAppPreferences()
@ -79,7 +68,7 @@ class GeolocationViewController: ViewController {
self?.openAppPreferences()
}
.addDisposableTo(disposeBag)
*/
}
private func openAppPreferences() {

View File

@ -95,9 +95,7 @@ class GitHubSearchRepositoriesViewController: ViewController, UITableViewDelegat
// activity indicator in status bar
// {
GitHubSearchRepositoriesAPI.sharedAPI.activityIndicator
.driveNext { active in
UIApplication.sharedApplication().networkActivityIndicatorVisible = active
}
.drive(UIApplication.sharedApplication().rx_networkActivityIndicatorVisible)
.addDisposableTo(disposeBag)
// }
}

View File

@ -20,22 +20,15 @@ struct Colors {
extension UINavigationController {
var rx_serviceState: AnyObserver<ServiceState?> {
return AnyObserver { event in
switch event {
case .Next(let maybeServiceState):
// if nil is being bound, then don't change color, it's not perfect, but :)
if let serviceState = maybeServiceState {
let isOffline = serviceState ?? .Online == .Offline
return UIBindingObserver(UIElement: self) { navigationController, maybeServiceState in
// if nil is being bound, then don't change color, it's not perfect, but :)
if let serviceState = maybeServiceState {
let isOffline = serviceState ?? .Online == .Offline
self.navigationBar.backgroundColor = isOffline
? Colors.OfflineColor
: Colors.OnlineColor
}
case .Error(let error):
bindingErrorToInterface(error)
case .Completed:
break
self.navigationBar.backgroundColor = isOffline
? Colors.OfflineColor
: Colors.OnlineColor
}
}
}.asObserver()
}
}

View File

@ -50,16 +50,9 @@ extension ValidationResult {
extension UILabel {
var ex_validationResult: AnyObserver<ValidationResult> {
return AnyObserver { [weak self] event in
switch event {
case let .Next(result):
self?.textColor = result.textColor
self?.text = result.description
case let .Error(error):
bindingErrorToInterface(error)
case .Completed:
break
}
}
return UIBindingObserver(UIElement: self) { label, result in
label.textColor = result.textColor
label.text = result.description
}.asObserver()
}
}

View File

@ -74,7 +74,12 @@ class GitHubSignupViewController2 : ViewController {
.addDisposableTo(disposeBag)
//}
let tapBackground = UITapGestureRecognizer(target: self, action: Selector("dismissKeyboard:"))
let tapBackground = UITapGestureRecognizer()
tapBackground.rx_event
.subscribeNext { [weak self] _ in
self?.view.endEditing(true)
}
.addDisposableTo(disposeBag)
view.addGestureRecognizer(tapBackground)
}
@ -95,8 +100,4 @@ class GitHubSignupViewController2 : ViewController {
}
}
func dismissKeyboard(gr: UITapGestureRecognizer) {
view.endEditing(true)
}
}

View File

@ -74,7 +74,12 @@ class GitHubSignupViewController1 : ViewController {
.addDisposableTo(disposeBag)
//}
let tapBackground = UITapGestureRecognizer(target: self, action: Selector("dismissKeyboard:"))
let tapBackground = UITapGestureRecognizer()
tapBackground.rx_event
.subscribeNext { [weak self] _ in
self?.view.endEditing(true)
}
.addDisposableTo(disposeBag)
view.addGestureRecognizer(tapBackground)
}
@ -95,8 +100,4 @@ class GitHubSignupViewController1 : ViewController {
}
}
func dismissKeyboard(gr: UITapGestureRecognizer) {
view.endEditing(true)
}
}

View File

@ -38,5 +38,13 @@ class SimpleTableViewExampleViewController : ViewController {
DefaultWireframe.presentAlert("Tapped `\(value)`")
}
.addDisposableTo(disposeBag)
tableView
.rx_itemAccessoryButtonTapped
.subscribeNext { indexPath in
DefaultWireframe.presentAlert("Tapped Detail @ \(indexPath.section),\(indexPath.row)")
}
.addDisposableTo(disposeBag)
}
}

View File

@ -30,7 +30,7 @@ class SimpleValidationViewController : ViewController {
super.viewDidLoad()
usernameValidOutlet.text = "Username has to be at least \(minimalUsernameLength) characters"
passwordValidOutlet.text = "Username has to be at least \(minimalPasswordLength) characters"
passwordValidOutlet.text = "Password has to be at least \(minimalPasswordLength) characters"
let usernameValid = usernameOutlet.rx_text
.map { $0.characters.count >= minimalUsernameLength }

View File

@ -16,7 +16,7 @@ import RxCocoa
public class CollectionViewImageCell: UICollectionViewCell {
@IBOutlet var imageOutlet: UIImageView!
var disposeBag: DisposeBag!
var disposeBag: DisposeBag?
var downloadableImage: Observable<DownloadableImage>?{
didSet{

View File

@ -19,7 +19,7 @@ public class WikipediaSearchCell: UITableViewCell {
@IBOutlet var URLOutlet: UILabel!
@IBOutlet var imagesOutlet: UICollectionView!
var disposeBag: DisposeBag!
var disposeBag: DisposeBag?
let imageService = DefaultImageService.sharedImageService

View File

@ -94,9 +94,7 @@ class WikipediaSearchViewController: ViewController {
DefaultImageService.sharedImageService.loadingImage
) { $0 || $1 }
.distinctUntilChanged()
.driveNext { active in
UIApplication.sharedApplication().networkActivityIndicatorVisible = active
}
.drive(UIApplication.sharedApplication().rx_networkActivityIndicatorVisible)
.addDisposableTo(disposeBag)
}
}

View File

@ -21,34 +21,21 @@ extension UIImageView{
}
func rxex_downloadableImageAnimated(transitionType:String?) -> AnyObserver<DownloadableImage> {
return AnyObserver { [weak self] event in
guard let strongSelf = self else { return }
MainScheduler.ensureExecutingOnScheduler()
switch event{
case .Next(let value):
for subview in strongSelf.subviews {
subview.removeFromSuperview()
}
switch value{
case .Content(let image):
strongSelf.rx_image.onNext(image)
case .OfflinePlaceholder:
let label = UILabel(frame: strongSelf.bounds)
label.textAlignment = .Center
label.font = UIFont.systemFontOfSize(35)
label.text = "⚠️"
strongSelf.addSubview(label)
}
case .Error(let error):
bindingErrorToInterface(error)
break
case .Completed:
break
return UIBindingObserver(UIElement: self) { imageView, image in
for subview in imageView.subviews {
subview.removeFromSuperview()
}
}
switch image {
case .Content(let image):
imageView.rx_image.onNext(image)
case .OfflinePlaceholder:
let label = UILabel(frame: imageView.bounds)
label.textAlignment = .Center
label.font = UIFont.systemFontOfSize(35)
label.text = "⚠️"
imageView.addSubview(label)
}
}.asObserver()
}
}
#endif

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9531" systemVersion="15E27e" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" initialViewController="E5v-jn-n2n">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9531" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" initialViewController="E5v-jn-n2n">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
@ -41,7 +41,7 @@
<textInputTraits key="textInputTraits"/>
</textField>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="9IA-eg-Rhf">
<rect key="frame" x="16" y="215" width="288" height="30"/>
<rect key="frame" x="16" y="214" width="288" height="30"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
</textField>
@ -52,13 +52,13 @@
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Password" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="w3Q-7d-PAK">
<rect key="frame" x="16" y="186" width="288" height="21"/>
<rect key="frame" x="16" y="185" width="288" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="nZR-mS-TBY">
<rect key="frame" x="16" y="282" width="288" height="44"/>
<rect key="frame" x="16" y="280" width="288" height="44"/>
<color key="backgroundColor" red="0.45835767445182407" green="1" blue="0.50599050155048486" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="Twt-zJ-ndu"/>
@ -71,13 +71,13 @@
</state>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="IGP-gu-Gxt">
<rect key="frame" x="16" y="253" width="288" height="21"/>
<rect key="frame" x="16" y="252" width="288" height="20"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.98726278540000001" green="0.23694899680000001" blue="0.26975026730000001" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5Qq-LW-zbl">
<rect key="frame" x="16" y="157" width="288" height="21"/>
<rect key="frame" x="16" y="157" width="288" height="20"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.9872627854347229" green="0.23694899678230286" blue="0.26975026726722717" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
@ -139,18 +139,18 @@
<rect key="frame" x="0.0" y="136" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="rON-a5-sdH" id="Vgz-Eb-3T3">
<rect key="frame" x="0.0" y="0.0" width="320" height="43"/>
<rect key="frame" x="0.0" y="0.0" width="320" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="fND-de-kcO">
<rect key="frame" x="15" y="5" width="32" height="20"/>
<rect key="frame" x="15" y="6" width="31.5" height="19.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Subtitle" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="QDp-55-cWc">
<rect key="frame" x="15" y="25" width="41" height="14"/>
<rect key="frame" x="15" y="25.5" width="40.5" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
@ -187,15 +187,15 @@
<rect key="frame" x="0.0" y="64" width="320" height="504"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" textLabel="9Fo-Rp-Wwk" style="IBUITableViewCellStyleDefault" id="kaN-dy-Y5z">
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="detailButton" indentationWidth="10" reuseIdentifier="Cell" textLabel="9Fo-Rp-Wwk" style="IBUITableViewCellStyleDefault" id="kaN-dy-Y5z">
<rect key="frame" x="0.0" y="92" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="kaN-dy-Y5z" id="KPQ-ob-TwJ">
<rect key="frame" x="0.0" y="0.0" width="320" height="43"/>
<rect key="frame" x="0.0" y="0.0" width="273" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="9Fo-Rp-Wwk">
<rect key="frame" x="15" y="0.0" width="290" height="43"/>
<rect key="frame" x="15" y="0.0" width="258" height="43.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
@ -244,7 +244,7 @@
<rect key="frame" x="0.0" y="86" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="Pbx-dk-7Jc" id="gCd-uh-Y2z">
<rect key="frame" x="0.0" y="0.0" width="320" height="43"/>
<rect key="frame" x="0.0" y="0.0" width="320" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView>
<accessibility key="accessibilityConfiguration">
@ -474,7 +474,7 @@ To do this automatically, check out the corresponding `Driver` example.</string>
</collectionViewFlowLayout>
<cells>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Cell" id="aNq-h7-r3z" customClass="NumberCell" customModule="RxExample_iOS" customModuleProvider="target">
<rect key="frame" x="26" y="25" width="55" height="35"/>
<rect key="frame" x="25.5" y="25" width="55" height="35"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="55" height="35"/>
@ -502,7 +502,7 @@ To do this automatically, check out the corresponding `Driver` example.</string>
<rect key="frame" x="0.0" y="0.0" width="106" height="25"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Dob-Ct-qBk">
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Dob-Ct-qBk">
<rect key="frame" x="32" y="3" width="42" height="20"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.98431372549999996" green="0.98431372549999996" blue="0.94901960780000005" alpha="1" colorSpace="calibratedRGB"/>
@ -569,21 +569,21 @@ To do this automatically, check out the corresponding `Driver` example.</string>
<tableViewSection headerTitle="iPhone Examples" id="QC3-bK-dI7" userLabel="Simple iPhone Examples">
<cells>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="XlM-5z-et7" detailTextLabel="qzf-wg-hsM" style="IBUITableViewCellStyleSubtitle" id="zvA-oc-vHk">
<rect key="frame" x="0.0" y="198" width="320" height="44"/>
<rect key="frame" x="0.0" y="197.5" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="zvA-oc-vHk" id="JoK-c2-Jg5">
<rect key="frame" x="0.0" y="0.0" width="287" height="43"/>
<rect key="frame" x="0.0" y="0.0" width="287" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Adding numbers" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="XlM-5z-et7">
<rect key="frame" x="15" y="5" width="120" height="20"/>
<rect key="frame" x="15" y="6" width="119.5" height="19.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Bindings" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="qzf-wg-hsM">
<rect key="frame" x="15" y="25" width="46" height="14"/>
<rect key="frame" x="15" y="25.5" width="45.5" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
@ -596,21 +596,21 @@ To do this automatically, check out the corresponding `Driver` example.</string>
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="XEn-YS-GnU" detailTextLabel="zBe-n6-i5V" style="IBUITableViewCellStyleSubtitle" id="BQL-5R-fty">
<rect key="frame" x="0.0" y="242" width="320" height="44"/>
<rect key="frame" x="0.0" y="241.5" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="BQL-5R-fty" id="nj7-Ko-iPj">
<rect key="frame" x="0.0" y="0.0" width="287" height="43"/>
<rect key="frame" x="0.0" y="0.0" width="287" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Simple validation" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="XEn-YS-GnU">
<rect key="frame" x="15" y="5" width="123" height="20"/>
<rect key="frame" x="15" y="6" width="123" height="19.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Bindings" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="zBe-n6-i5V">
<rect key="frame" x="15" y="25" width="46" height="14"/>
<rect key="frame" x="15" y="25.5" width="45.5" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
@ -623,21 +623,21 @@ To do this automatically, check out the corresponding `Driver` example.</string>
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="j1q-2d-R02" detailTextLabel="pxC-nY-kap" style="IBUITableViewCellStyleSubtitle" id="0UH-Rf-apt">
<rect key="frame" x="0.0" y="286" width="320" height="44"/>
<rect key="frame" x="0.0" y="285.5" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="0UH-Rf-apt" id="nDe-UH-zD4">
<rect key="frame" x="0.0" y="0.0" width="287" height="43"/>
<rect key="frame" x="0.0" y="0.0" width="287" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Geolocation Subscription" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="j1q-2d-R02">
<rect key="frame" x="15" y="5" width="183" height="20"/>
<rect key="frame" x="15" y="6" width="183" height="19.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Observers, service and Drive example" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="pxC-nY-kap">
<rect key="frame" x="15" y="25" width="199" height="14"/>
<rect key="frame" x="15" y="25.5" width="198.5" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
@ -650,21 +650,21 @@ To do this automatically, check out the corresponding `Driver` example.</string>
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="LN4-l3-ara" detailTextLabel="BaB-5r-hmY" style="IBUITableViewCellStyleSubtitle" id="Hab-23-dUs">
<rect key="frame" x="0.0" y="330" width="320" height="44"/>
<rect key="frame" x="0.0" y="329.5" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="Hab-23-dUs" id="5ox-J8-FhR">
<rect key="frame" x="0.0" y="0.0" width="287" height="43"/>
<rect key="frame" x="0.0" y="0.0" width="287" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="GitHub Signup - Vanilla Observables" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="LN4-l3-ara">
<rect key="frame" x="15" y="5" width="264" height="20"/>
<rect key="frame" x="15" y="6" width="263.5" height="19.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Simple MVVM example " lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="BaB-5r-hmY">
<rect key="frame" x="15" y="25" width="123" height="14"/>
<rect key="frame" x="15" y="25.5" width="123" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
@ -677,21 +677,21 @@ To do this automatically, check out the corresponding `Driver` example.</string>
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="xR6-uz-2HS" detailTextLabel="kFo-00-iCj" style="IBUITableViewCellStyleSubtitle" id="bd6-tg-GeQ">
<rect key="frame" x="0.0" y="374" width="320" height="44"/>
<rect key="frame" x="0.0" y="373.5" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="bd6-tg-GeQ" id="5Rg-v9-4ee">
<rect key="frame" x="0.0" y="0.0" width="287" height="43"/>
<rect key="frame" x="0.0" y="0.0" width="287" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="GitHub Signup - Using Driver" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="xR6-uz-2HS">
<rect key="frame" x="15" y="5" width="211" height="20"/>
<rect key="frame" x="15" y="6" width="211" height="19.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Simple MVVM example " lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="kFo-00-iCj">
<rect key="frame" x="15" y="25" width="123" height="14"/>
<rect key="frame" x="15" y="25.5" width="123" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
@ -704,21 +704,21 @@ To do this automatically, check out the corresponding `Driver` example.</string>
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="siT-mr-b8A" detailTextLabel="BSH-sG-bpY" style="IBUITableViewCellStyleSubtitle" id="VBq-7j-4vQ">
<rect key="frame" x="0.0" y="418" width="320" height="44"/>
<rect key="frame" x="0.0" y="417.5" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="VBq-7j-4vQ" id="m7h-NW-UnD">
<rect key="frame" x="0.0" y="0.0" width="287" height="43"/>
<rect key="frame" x="0.0" y="0.0" width="287" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="API wrappers" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="siT-mr-b8A">
<rect key="frame" x="15" y="5" width="97" height="20"/>
<rect key="frame" x="15" y="6" width="96.5" height="19.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="API wrappers Example" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="BSH-sG-bpY">
<rect key="frame" x="15" y="25" width="117" height="14"/>
<rect key="frame" x="15" y="25.5" width="117" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
@ -731,21 +731,21 @@ To do this automatically, check out the corresponding `Driver` example.</string>
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="ufL-YX-dKF" detailTextLabel="efq-eT-ETM" style="IBUITableViewCellStyleSubtitle" id="Egb-OL-S5e">
<rect key="frame" x="0.0" y="462" width="320" height="44"/>
<rect key="frame" x="0.0" y="461.5" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="Egb-OL-S5e" id="Y1z-Y7-dLh">
<rect key="frame" x="0.0" y="0.0" width="287" height="43"/>
<rect key="frame" x="0.0" y="0.0" width="287" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Calculator" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="ufL-YX-dKF">
<rect key="frame" x="15" y="5" width="74" height="20"/>
<rect key="frame" x="15" y="6" width="74" height="19.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Stateless calculator example" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="efq-eT-ETM">
<rect key="frame" x="15" y="25" width="151" height="14"/>
<rect key="frame" x="15" y="25.5" width="150.5" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
@ -758,21 +758,21 @@ To do this automatically, check out the corresponding `Driver` example.</string>
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="Bxx-sK-jOc" detailTextLabel="DjH-cD-3C4" style="IBUITableViewCellStyleSubtitle" id="692-oJ-jzR">
<rect key="frame" x="0.0" y="506" width="320" height="44"/>
<rect key="frame" x="0.0" y="505.5" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="692-oJ-jzR" id="Mv8-yU-G0e">
<rect key="frame" x="0.0" y="0.0" width="287" height="43"/>
<rect key="frame" x="0.0" y="0.0" width="287" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="ImagePicker" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Bxx-sK-jOc">
<rect key="frame" x="15" y="5" width="90" height="20"/>
<rect key="frame" x="15" y="6" width="89.5" height="19.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="UIImagePickerController example" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="DjH-cD-3C4">
<rect key="frame" x="15" y="25" width="175" height="14"/>
<rect key="frame" x="15" y="25.5" width="174.5" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
@ -789,21 +789,21 @@ To do this automatically, check out the corresponding `Driver` example.</string>
<tableViewSection headerTitle="Table/Collection view examples" id="guj-4N-WZg" userLabel="TableView/CollectionView example">
<cells>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="kRJ-td-Wjf" detailTextLabel="ctN-th-VB8" style="IBUITableViewCellStyleSubtitle" id="YE3-Dn-DSf">
<rect key="frame" x="0.0" y="593" width="320" height="44"/>
<rect key="frame" x="0.0" y="591.5" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="YE3-Dn-DSf" id="SOl-vI-kLK">
<rect key="frame" x="0.0" y="0.0" width="287" height="43"/>
<rect key="frame" x="0.0" y="0.0" width="287" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Simplest table view example" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="kRJ-td-Wjf">
<rect key="frame" x="15" y="5" width="205" height="20"/>
<rect key="frame" x="15" y="6" width="205" height="19.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Basic" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="ctN-th-VB8">
<rect key="frame" x="15" y="25" width="29" height="14"/>
<rect key="frame" x="15" y="25.5" width="28.5" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
@ -816,21 +816,21 @@ To do this automatically, check out the corresponding `Driver` example.</string>
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="MxU-oS-7a8" detailTextLabel="wQk-uP-pgO" style="IBUITableViewCellStyleSubtitle" id="1YT-MH-mkD">
<rect key="frame" x="0.0" y="637" width="320" height="44"/>
<rect key="frame" x="0.0" y="635.5" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="1YT-MH-mkD" id="O4c-r4-rcr">
<rect key="frame" x="0.0" y="0.0" width="287" height="43"/>
<rect key="frame" x="0.0" y="0.0" width="287" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Simplest table view example with sections" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="MxU-oS-7a8">
<rect key="frame" x="15" y="5" width="270" height="20"/>
<rect key="frame" x="15" y="6" width="270" height="19.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Basic" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="wQk-uP-pgO">
<rect key="frame" x="15" y="25" width="29" height="14"/>
<rect key="frame" x="15" y="25.5" width="28.5" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
@ -843,21 +843,21 @@ To do this automatically, check out the corresponding `Driver` example.</string>
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="x4u-zK-muO" detailTextLabel="nuf-1K-ITV" style="IBUITableViewCellStyleSubtitle" id="HTx-Ei-Vlj">
<rect key="frame" x="0.0" y="681" width="320" height="44"/>
<rect key="frame" x="0.0" y="679.5" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="HTx-Ei-Vlj" id="kc9-g3-Zfl">
<rect key="frame" x="0.0" y="0.0" width="287" height="43"/>
<rect key="frame" x="0.0" y="0.0" width="287" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="TableView with editing" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="x4u-zK-muO">
<rect key="frame" x="15" y="5" width="162" height="20"/>
<rect key="frame" x="15" y="6" width="162" height="19.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Model editing using observable sequences, master/detail" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="nuf-1K-ITV">
<rect key="frame" x="15" y="25" width="270" height="14"/>
<rect key="frame" x="15" y="25.5" width="270" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
@ -870,21 +870,21 @@ To do this automatically, check out the corresponding `Driver` example.</string>
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="1M3-wJ-NDW" detailTextLabel="To0-TG-0Fv" style="IBUITableViewCellStyleSubtitle" id="kbO-Dk-wEU">
<rect key="frame" x="0.0" y="725" width="320" height="44"/>
<rect key="frame" x="0.0" y="723.5" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="kbO-Dk-wEU" id="55g-Qh-WS3">
<rect key="frame" x="0.0" y="0.0" width="287" height="43"/>
<rect key="frame" x="0.0" y="0.0" width="287" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Table/CollectionView partial updates" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="1M3-wJ-NDW">
<rect key="frame" x="15" y="5" width="265" height="20"/>
<rect key="frame" x="15" y="6" width="264.5" height="19.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Table and Collection view with partial updates" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="To0-TG-0Fv">
<rect key="frame" x="15" y="25" width="241" height="14"/>
<rect key="frame" x="15" y="25.5" width="241" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
@ -901,21 +901,21 @@ To do this automatically, check out the corresponding `Driver` example.</string>
<tableViewSection headerTitle="Complex examples" id="lxd-sR-NIF" userLabel="Complex examples">
<cells>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="pxT-4B-gDc" detailTextLabel="xZJ-Xt-MqD" style="IBUITableViewCellStyleSubtitle" id="yAf-eB-mRo">
<rect key="frame" x="0.0" y="812" width="320" height="44"/>
<rect key="frame" x="0.0" y="809.5" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="yAf-eB-mRo" id="M3Y-AJ-f8i">
<rect key="frame" x="0.0" y="0.0" width="287" height="43"/>
<rect key="frame" x="0.0" y="0.0" width="287" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Search Wikipedia" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="pxT-4B-gDc">
<rect key="frame" x="15" y="5" width="126" height="20"/>
<rect key="frame" x="15" y="6" width="126" height="19.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Complex async, activity indicator" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="xZJ-Xt-MqD">
<rect key="frame" x="15" y="25" width="174" height="14"/>
<rect key="frame" x="15" y="25.5" width="173.5" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
@ -928,21 +928,21 @@ To do this automatically, check out the corresponding `Driver` example.</string>
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="8FC-s3-ejV" detailTextLabel="ECT-7x-66c" style="IBUITableViewCellStyleSubtitle" id="0Xj-JL-bdb">
<rect key="frame" x="0.0" y="856" width="320" height="44"/>
<rect key="frame" x="0.0" y="853.5" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="0Xj-JL-bdb" id="buE-3J-RLs">
<rect key="frame" x="0.0" y="0.0" width="287" height="43"/>
<rect key="frame" x="0.0" y="0.0" width="287" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="GitHub Search Repositories" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="8FC-s3-ejV">
<rect key="frame" x="15" y="5" width="200" height="20"/>
<rect key="frame" x="15" y="6" width="200" height="19.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Paging, activity indicator" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="ECT-7x-66c">
<rect key="frame" x="15" y="25" width="130" height="14"/>
<rect key="frame" x="15" y="25.5" width="130" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
@ -989,7 +989,8 @@ To do this automatically, check out the corresponding `Driver` example.</string>
</searchBar>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="8" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FeZ-zt-ZeK">
<rect key="frame" x="29" y="174" width="263" height="126"/>
<string key="text">This app transforms Wikipedia into image search engine.It uses Wikipedia search API to find content and scrapes the HTML of those pages for image URLs.
<string key="text">This app transforms Wikipedia into image search engine.
It uses Wikipedia search API to find content and scrapes the HTML of those pages for image URLs.
This is only showcase app, not intended for production purposes.</string>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
@ -1256,7 +1257,7 @@ This is only showcase app, not intended for production purposes.</string>
<textInputTraits key="textInputTraits"/>
</textField>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="+" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="X9s-MA-NXj">
<rect key="frame" x="93" y="274" width="11" height="21"/>
<rect key="frame" x="93" y="274" width="11" height="20"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
@ -1877,11 +1878,11 @@ This is only showcase app, not intended for production purposes.</string>
<rect key="frame" x="0.0" y="92" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="xzT-oa-UhT" id="wH7-Gy-rvk">
<rect key="frame" x="0.0" y="0.0" width="320" height="43"/>
<rect key="frame" x="0.0" y="0.0" width="320" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Z3B-kH-AMX">
<rect key="frame" x="15" y="0.0" width="290" height="43"/>
<rect key="frame" x="15" y="0.0" width="290" height="43.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>

View File

@ -23,6 +23,12 @@ public class RootViewController : UITableViewController {
DefaultWireframe.sharedInstance
MainScheduler.instance
ReachabilityService.sharedReachabilityService
GeolocationService.instance
let geoService = GeolocationService.instance
geoService.autorized.driveNext { _ in
}.dispose()
geoService.location.driveNext { _ in
}.dispose()
}
}

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "RxSwift"
s.version = "2.1.0"
s.version = "2.2.0"
s.summary = "RxSwift is a Swift implementation of Reactive Extensions"
s.description = <<-DESC
This is a Swift port of [ReactiveX.io](https://github.com/ReactiveX)

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "RxTests"
s.version = "2.1.0"
s.version = "2.2.0"
s.summary = "RxSwift Testing extensions"
s.description = <<-DESC
Unit testing extensions for RxSwift. This library contains mock schedulers, observables, and observers
@ -55,5 +55,5 @@ func testMap() {
s.framework = 'XCTest'
s.dependency 'RxSwift', '~> 2.0'
s.dependency 'RxSwift', '~> 2.2'
end

View File

@ -84,6 +84,26 @@ extension CLLocationManagerTests {
XCTAssertTrue(completed)
}
func testDidFinishDeferredUpdatesWithError_noError() {
var completed = false
var error: NSError?
autoreleasepool {
let manager = CLLocationManager()
_ = manager.rx_didFinishDeferredUpdatesWithError.subscribe(onNext: { e in
error = e
}, onCompleted: {
completed = true
})
manager.delegate!.locationManager!(manager, didFinishDeferredUpdatesWithError: nil)
}
XCTAssertEqual(error, nil)
XCTAssertTrue(completed)
}
#endif
#if os(iOS)

View File

@ -27,6 +27,22 @@ extension ControlTests {
XCTAssert(subject.enabled == false, "Expected enabled set to false")
}
func testSubscribedSelectedToTrue() {
let subject = UIControl()
let disposable = Observable.just(true).subscribe(subject.rx_selected)
defer { disposable.dispose() }
XCTAssert(subject.selected == true, "Expected selected set to true")
}
func testSubscribeSelectedToFalse() {
let subject = UIControl()
let disposable = Observable.just(false).subscribe(subject.rx_selected)
defer { disposable.dispose() }
XCTAssert(subject.selected == false, "Expected selected set to false")
}
}
// UITextField
@ -271,6 +287,23 @@ extension ControlTests {
}
}
// UIProgressView
extension ControlTests {
func testProgressView_HasWeakReference() {
ensureControlObserverHasWeakReference(UIProgressView(), { (progressView: UIProgressView) -> AnyObserver<Float> in progressView.rx_progress }, { Variable<Float>(0.0).asObservable() })
}
func testProgressView_NextElementsSetsValue() {
let subject = UIProgressView()
let progressSequence = Variable<Float>(0.0)
let disposable = progressSequence.asObservable().bindTo(subject.rx_progress)
defer { disposable.dispose() }
progressSequence.value = 1.0
XCTAssert(subject.progress == progressSequence.value, "Expected progress to have been set")
}
}
// UITableView
extension ControlTests {
func testTableView_DelegateEventCompletesOnDealloc() {
@ -278,6 +311,7 @@ extension ControlTests {
ensureEventDeallocated(createView) { (view: UITableView) in view.rx_itemSelected }
ensureEventDeallocated(createView) { (view: UITableView) in view.rx_itemDeselected }
ensureEventDeallocated(createView) { (view: UITableView) in view.rx_itemAccessoryButtonTapped }
ensureEventDeallocated(createView) { (view: UITableView) in view.rx_modelSelected(Int.self) }
ensureEventDeallocated(createView) { (view: UITableView) in view.rx_itemDeleted }
ensureEventDeallocated(createView) { (view: UITableView) in view.rx_itemMoved }
@ -332,6 +366,35 @@ extension ControlTests {
func testTableView_ModelSelected_rx_itemsWithCellFactory() {
let items: Observable<[Int]> = Observable.just([1, 2, 3])
let createView: () -> (UITableView, Disposable) = {
let tableView = UITableView(frame: CGRectMake(0, 0, 1, 1))
let dataSourceSubscription = items.bindTo(tableView.rx_itemsWithCellFactory) { (tv, index: Int, item: Int) -> UITableViewCell in
return UITableViewCell(style: .Default, reuseIdentifier: "Identity")
}
return (tableView, dataSourceSubscription)
}
let (tableView, dataSourceSubscription) = createView()
var selectedItem: Int? = nil
let s = tableView.rx_modelSelected(Int.self)
.subscribeNext { item in
selectedItem = item
}
tableView.delegate!.tableView!(tableView, didSelectRowAtIndexPath: NSIndexPath(forRow: 1, inSection: 0))
XCTAssertEqual(selectedItem, 2)
dataSourceSubscription.dispose()
s.dispose()
}
func testTableView_IndexPath_rx_itemAccessoryButtonTapped() {
let items: Observable<[Int]> = Observable.just([1, 2, 3])
let createView: () -> (UITableView, Disposable) = {
let tableView = UITableView(frame: CGRectMake(0, 0, 1, 1))
@ -344,16 +407,17 @@ extension ControlTests {
let (tableView, dataSourceSubscription) = createView()
var selectedItem: Int? = nil
var selectedItem: NSIndexPath? = nil
let s = tableView.rx_modelSelected(Int.self)
let s = tableView.rx_itemAccessoryButtonTapped
.subscribeNext { item in
selectedItem = item
}
tableView.delegate!.tableView!(tableView, didSelectRowAtIndexPath: NSIndexPath(forRow: 1, inSection: 0))
let testRow = NSIndexPath(forRow: 1, inSection: 0)
tableView.delegate!.tableView!(tableView, accessoryButtonTappedForRowWithIndexPath: testRow)
XCTAssertEqual(selectedItem, 2)
XCTAssertEqual(selectedItem, testRow)
dataSourceSubscription.dispose()
s.dispose()

File diff suppressed because it is too large Load Diff

View File

@ -30,7 +30,7 @@ class RxTest
: XCTestCase {
#if os(Linux)
var allTests : [(String, () -> Void)] = []
var allTests : [(String, () throws -> Void)] = []
#endif
private var startResourceCount: Int32 = 0

View File

@ -86,6 +86,7 @@ function runAutomation() {
}
AUTOMATION_SIMULATORS=("Krunoslav Zahers iPhone" ${DEFAULT_IOS9_SIMULATOR})
#AUTOMATION_SIMULATORS=(${DEFAULT_IOS9_SIMULATOR})
IFS=""
for simulator in ${AUTOMATION_SIMULATORS[@]}

View File

@ -14,7 +14,7 @@ test("----- githubSignUp -----", function (check, pass) {
return false;
}
target.frontMostApp().mainWindow().tableViews()[0].cells()[2].tap();
target.frontMostApp().mainWindow().tableViews()[0].cells()[3].tap();
target.frontMostApp().mainWindow().textFields()[0].tap();
target.frontMostApp().keyboard().typeString("rxrevolution")

View File

@ -6,7 +6,7 @@ test("----- searchWikipedia -----", function (check, pass) {
var width = target.frontMostApp().mainWindow().rect().size.width
target.frontMostApp().mainWindow().tableViews()[0].cells()[11].tap();
target.frontMostApp().mainWindow().tableViews()[0].cells()[12].tap();
target.delay(2);

View File

@ -8,7 +8,7 @@ test("----- masterDetail -----", function (check, pass) {
var target = UIATarget.localTarget()
target.frontMostApp().mainWindow().tableViews()[0].cells()[9].tap();
target.frontMostApp().mainWindow().tableViews()[0].cells()[10].tap();
target.frontMostApp().navigationBar().rightButton().tap();
target.frontMostApp().mainWindow().dragInsideWithOptions({startOffset:{x:0.93, y:yOffset(300)}, endOffset:{x:0.95, y:yOffset(200)}, duration:1.5});
target.frontMostApp().mainWindow().dragInsideWithOptions({startOffset:{x:0.93, y:yOffset(300)}, endOffset:{x:0.95, y:yOffset(100)}, duration:1.5});

View File

@ -1,5 +1,5 @@
var apiTestIndex = 4
var apiTestIndex = 5
var firstScrollView = function () {
return UIATarget.localTarget().frontMostApp().mainWindow().scrollViews()[0]

View File

@ -3,7 +3,7 @@
test("----- reactivePartialUpdates -----", function (check, pass) {
var target = UIATarget.localTarget()
target.frontMostApp().mainWindow().tableViews()[0].cells()[10].tap();
target.frontMostApp().mainWindow().tableViews()[0].cells()[11].tap();
var rightButton = target.frontMostApp().navigationBar().rightButton();
rightButton.tap();

View File

@ -9,7 +9,7 @@
var target = UIATarget.localTarget();
// open all screens
for (var i = 0; i < 12; ++i) {
for (var i = 0; i < 14; ++i) {
log(i);
target.delay( 0.5 );
target.frontMostApp().mainWindow().tableViews()[0].cells()[i].tap();

View File

@ -1,6 +1,5 @@
#!/bin/bash
set -e
#set -o xtrace
RESET="\033[0m"
BLACK="\033[30m"
@ -134,12 +133,14 @@ function action() {
fi
STATUS=""
set -x
xcodebuild -workspace "${WORKSPACE}" \
-scheme "${SCHEME}" \
-configuration "${CONFIGURATION}" \
-derivedDataPath "${BUILD_DIRECTORY}" \
-destination "$DESTINATION" \
$ACTION | xcpretty -c; STATUS=${PIPESTATUS[0]}
set +x
if [ $STATUS -ne 0 ]; then
echo $STATUS