RxSwift by Examples 分成 4 部分激率。以下是 PART 2 的學習筆記和翻譯整理咳燕。原文在這里。
binding 意思是連接 Observable 和 Subject乒躺。
釋義
我們已經(jīng)學習過 Observable 和 Observer招盲。
- Subject - 可觀察的和觀察者。它可以觀察和被觀察聪蘸。
- BehaviorSubject - 當你訂閱它宪肖,你將得到它已發(fā)出的最新的值表制,以及此后發(fā)出的值。
- PublishSubject - 當你訂閱它控乾,你只能得到此后它發(fā)出的值么介。
- ReplaySubject - 當你訂閱它,你將得到此后發(fā)出的值蜕衡,但也能得到此前發(fā)出的值壤短。可以得到多早以前發(fā)出的值呢慨仿?這取決于你所訂閱的 ReplaySubject 的緩存大芯酶(buffer size)。
舉例說:你正在舉行生日派對镰吆,你要打開你收到的禮物帘撰。
你打開了第一個、第二個万皿、第三個禮物摧找。你媽媽正在廚房里準備美味的食物,因此還沒來到派對現(xiàn)場牢硅。作為一個媽媽蹬耘,她想知道你得到了什么禮物。于是你將情況告訴她减余。在 Rx 的世界中综苔,你發(fā)送可觀察的序列 obserbable sequence(禮物)給觀察者 observer(你媽媽)。有意思的是位岔,她在你已經(jīng)發(fā)出了若干值之后開始觀察如筛,但是不管怎樣她得到了完整的信息。對她我們是一個 buffer = 3 的 ReplaySubject(我們保留 3 個最新的禮物發(fā)送給每一個新的訂閱者)赃承。
你繼續(xù)打開禮物妙黍。這時你看到你的兩個朋友 Jack 和 Andy 也來到派對。Jack 是你的好朋友瞧剖,所以他問你目前打開了些什么拭嫁。你對他的遲到有些生氣,所以你只告訴他你最后一次打開的禮物抓于。他并不知道還有其他禮物做粤,所以他很高興。在 Rx 的世界中捉撮,你只發(fā)送了最近的一個值給觀察者(Jack)怕品。他還將得到接下來的值當你發(fā)送的時候(接下來你將打開的禮物)。對他而言我們是一個 BehaviorSubject巾遭。
還有一個 Andy肉康,他只是一個普通朋友闯估,并不真的在意你已經(jīng)打開了什么禮物。所以他只是坐下來等待接下來的表演吼和。如你所料涨薪,對他而言我們只是一個 PublishSubject。他只得到在他訂閱之后發(fā)出的值炫乓。
還有一個概念叫 Variable刚夺。這是一個對 BehaviorSubject 的包裝。你只能提交 .onNext() 事件(當使用 BehaviorSubject 的時候你可以直接發(fā)送 .onError(), .onCompleted())末捣。Variable 自動發(fā)送 .onCompleted() 事件侠姑,當它被注銷的時候。
示例
我們將創(chuàng)建一個簡單的 app箩做,在視圖中連接球的顏色與位置莽红,我們還將連接視圖背景色與球體的顏色。
我們創(chuàng)建項目卒茬,并使用 Cocoapods 引入 RxSwift 和 RxCocoa船老,我們還將使用 Chameleon 來連接顏色咖熟。
Podfile
platform :ios, '9.0'
use_frameworks!
target 'ColourfulBall' do
pod 'RxSwift'
pod 'RxCocoa'
pod 'ChameleonFramework/Swift', :git => 'https://github.com/ViccAlexander/Chameleon.git'
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_TESTABILITY'] = 'YES'
config.build_settings['SWIFT_VERSION'] = '3.0'
end
end
end
在我們的 Controller 的 main view 中畫一個圓形圃酵。
import ChameleonFramework
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
var circleView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
func setup() {
// Add circle view
circleView = UIView(frame: CGRect(origin: view.center, size: CGSize(width: 100.0, height: 100.0)))
circleView.layer.cornerRadius = circleView.frame.width / 2.0
circleView.center = view.center
circleView.backgroundColor = .green
view.addSubview(circleView)
}
}
下一步,添加 UIPanGestureRecognizer 并根據(jù)手勢改變球形的 frame
func setup() {
// Add circle view
circleView = UIView(frame: CGRect(origin: view.center, size: CGSize(width: 100.0, height: 100.0)))
circleView.layer.cornerRadius = circleView.frame.width / 2.0
circleView.center = view.center
circleView.backgroundColor = .green
view.addSubview(circleView)
// Add gesture recognizer
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(circleMoved(_:)))
circleView.addGestureRecognizer(gestureRecognizer)
}
func circleMoved(_ recognizer: UIPanGestureRecognizer) {
let location = recognizer.location(in: view)
UIView.animate(withDuration: 0.1) {
self.circleView.center = location
}
}
Bind
下一步我們將做綁定馍管。連接球體位置與球體顏色郭赐。怎樣做呢?
首先我們將用 rx.observe() 觀察球體的中心位置确沸,然后用 bindTo() 綁定給一個變量 Variable捌锭。在我們的例子中,綁定做了什么事情呢罗捎?每一次一個我們的球體發(fā)送新的位置信息观谦,變量將收到關于它的新的信號。我們的變量是一個觀察者桨菜,因為它觀察位置豁状。
我們將在一個 ViewModel 中創(chuàng)建變量,我們用它來計算 UI 的東西倒得。在這個例子中泻红,每次變量得到一個新的地址,我們將為球體計算新的背景色霞掺。
我們只有兩個屬性:centerVariable 將是我們的 observer 和 observable - 我們保存數(shù)據(jù)給它谊路,并從它獲取數(shù)據(jù)。另一個是 backgroundColorObserable菩彬。它實際上不是一個變量缠劝,只是一個 obserable潮梯。
你可能會問為什么 centerVariable 是一個 Variable 而 backgroundColorObserable 是一個 Obserbable?
我們球體的 observable center 連接了 centerVariable惨恭,這意味著任何時候 center 改變酷麦,centerVAriable 將得到這個改變。因此這是一個觀察者 Observer喉恋。同時我們 ViewModel 中的 centerVariable 作為一個 Obserable沃饶,同時作為 observer 和 Observable 就是一個 Subject。
為什么是 Variable 而不是 PublishSubject 或者 ReplaySubject轻黑?因為我們想確保我們將得到訂閱時的最新的球體中心值糊肤。
backgroundColorObservable 只是一個 Observable,它從未被任何東西所約束氓鄙,所以它只是一個可觀察的 Observable馆揉。
ViewModel
我們的基礎 ViewModel 是這樣
import ChameleonFramework
import Foundation
import RxSwift
import RxCocoa
class CircleViewModel {
var centerVariable = Variable<CGPoint?>(.zero) // Create one variable that will be changed and observed
var backgroundColorObservable: Observable<UIColor>! // Create observable that will change backgroundColor based on center
init() {
setup()
}
func setup() {
}
}
接著我們需要設置 backgroundColorObserable。我們希望它基于由 centerVariable 產(chǎn)生的新的 CGPoint 而改變抖拦。
func setup() {
// When we get new center, emit new UIColor
backgroundColorObservable = centerVariable.asObservable()
.map { center in
guard let center = center else { return UIColor.flatten(.black)() }
let red: CGFloat = (center.x + center.y).truncatingRemainder(dividingBy: 255.0) / 255.0 // We just manipulate red, but you can do w/e
let green: CGFloat = 0.0
let blue: CGFloat = 0.0
return UIColor.flatten(UIColor(red: red, green: green, blue: blue, alpha: 1.0))()
}
}
分步講解:
- 轉換變量成 Observable - 因為 Variable 可以是 Observer 也可以是 Observable升酣,所以我們要決定它是哪一個。又因為我們想觀察它态罪,于是把它轉換成 Observable噩茄。
- Map 每個新的 CGPoint 到 UIColor。我們會得到 Observable 產(chǎn)生的新的中心位置复颈,經(jīng)過計算绩聘,得到新的 UIColor。
- 你可能注意到 Observable 是一個 optional 的 CGPoint耗啦。為什么凿菩?我們需要在得到 nil 的時候保護自己,返回默認值帜讲。
現(xiàn)在我們有了 Observable 的背景色衅谷。我們需要基于新的值更新球體。這非常簡單似将,我們將 subscribe() 這個 Observable获黔。
fileprivate var circleViewModel: CircleViewModel!
fileprivate let disposeBag = DisposeBag()
然后
circleViewModel = CircleViewModel()
// Subscribe to backgroundObservable to get new colors from the ViewModel.
circleViewModel.backgroundColorObservable
.subscribe(onNext: { [weak self] backgroundColor in
UIView.animateWithDuration(0.1) {
self?.circleView.backgroundColor = backgroundColor
// Try to get complementary color for given background color
let viewBackgroundColor = UIColor(complementaryFlatColorOf: backgroundColor)
// If it is different that the color
if viewBackgroundColor != backgroundColor {
// Assign it as a background color of the view
// We only want different color to be able to see that circle in a view
self?.view.backgroundColor = viewBackgroundColor
}
}
})
.addDisposableTo(disposeBag)
我們同時還把視圖背景色變成球體顏色的補色。同時檢查這個補色是否和球體顏色一樣(確保我們看得到球體)玩郊。我們可以把這段代碼放在 setup() 中
func setup() {
// Add circle view
circleView = UIView(frame: CGRect(origin: view.center, size: CGSize(width: 100.0, height: 100.0)))
circleView.layer.cornerRadius = circleView.frame.width / 2.0
circleView.center = view.center
circleView.backgroundColor = .green
view.addSubview(circleView)
circleViewModel = CircleViewModel()
// Bind the center point of the CircleView to the centerObservable
circleView
.rx.observe(CGPoint.self, "center")
.bindTo(circleViewModel.centerVariable)
.addDisposableTo(disposeBag)
// Subscribe to backgroundObservable to get new colors from the ViewModel.
circleViewModel.backgroundColorObservable
.subscribe(onNext: { [weak self] backgroundColor in
UIView.animateWithDuration(0.1) {
self?.circleView.backgroundColor = backgroundColor
// Try to get complementary color for given background color
let viewBackgroundColor = UIColor(complementaryFlatColorOf: backgroundColor)
// If it is different that the color
if viewBackgroundColor != backgroundColor {
// Assign it as a background color of the view
// We only want different color to be able to see that circle in a view
self?.view.backgroundColor = viewBackgroundColor
}
}
})
.addDisposableTo(disposeBag)
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(circleMoved(_:)))
circleView.addGestureRecognizer(gestureRecognizer)
}
完成肢执。整個操縱顏色的任務沒有用到做類似事情時我們通常要用到的 delegate, notification。
也許你可以試著綁定中心位置和球體尺寸译红,試著基于 width 和 height 改變 cornerRadius预茄。