RxSwift by Examples #2 – Observable and the Bind

RxSwift by Examples 分成 4 部分。以下是 PART 2 的學習筆記和翻譯整理。原文在這里。

binding 意思是連接 Observable 和 Subject。

釋義

我們已經學習過 Observable 和 Observer。

  • Subject - 可觀察的和觀察者。它可以觀察和被觀察。
  • BehaviorSubject - 當你訂閱它,你將得到它已發(fā)出的最新的值,以及此后發(fā)出的值。
  • PublishSubject - 當你訂閱它,你只能得到此后它發(fā)出的值。
  • ReplaySubject - 當你訂閱它,你將得到此后發(fā)出的值,但也能得到此前發(fā)出的值??梢缘玫蕉嘣缫郧鞍l(fā)出的值呢?這取決于你所訂閱的 ReplaySubject 的緩存大?。╞uffer size)。

舉例說:你正在舉行生日派對,你要打開你收到的禮物。

你打開了第一個、第二個、第三個禮物。你媽媽正在廚房里準備美味的食物,因此還沒來到派對現(xiàn)場。作為一個媽媽,她想知道你得到了什么禮物。于是你將情況告訴她。在 Rx 的世界中,你發(fā)送可觀察的序列 obserbable sequence(禮物)給觀察者 observer(你媽媽)。有意思的是,她在你已經發(fā)出了若干值之后開始觀察,但是不管怎樣她得到了完整的信息。對她我們是一個 buffer = 3 的 ReplaySubject(我們保留 3 個最新的禮物發(fā)送給每一個新的訂閱者)。

你繼續(xù)打開禮物。這時你看到你的兩個朋友 Jack 和 Andy 也來到派對。Jack 是你的好朋友,所以他問你目前打開了些什么。你對他的遲到有些生氣,所以你只告訴他你最后一次打開的禮物。他并不知道還有其他禮物,所以他很高興。在 Rx 的世界中,你只發(fā)送了最近的一個值給觀察者(Jack)。他還將得到接下來的值當你發(fā)送的時候(接下來你將打開的禮物)。對他而言我們是一個 BehaviorSubject。

還有一個 Andy,他只是一個普通朋友,并不真的在意你已經打開了什么禮物。所以他只是坐下來等待接下來的表演。如你所料,對他而言我們只是一個 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 并根據手勢改變球形的 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ù)據給它,并從它獲取數(shù)據。另一個是 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 產生的新的 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))()
        }
}

分步講解:

  1. 轉換變量成 Observable - 因為 Variable 可以是 Observer 也可以是 Observable,所以我們要決定它是哪一個。又因為我們想觀察它,于是把它轉換成 Observable。
  2. Map 每個新的 CGPoint 到 UIColor。我們會得到 Observable 產生的新的中心位置,經過計算,得到新的 UIColor。
  3. 你可能注意到 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。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 發(fā)現(xiàn) 關注 消息 RxSwift入坑解讀-你所需要知道的各種概念 沸沸騰關注 2016.11.27 19:11*字...
    楓葉1234閱讀 2,937評論 0 2
  • 最近在學習RxSwift相關的內容,在這里記錄一些基本的知識點,以便今后查閱。 Observable 在RxSwi...
    L_Zephyr閱讀 1,896評論 1 4
  • 本文章內部分圖片資源來自RayWenderlich.com 本文結合自己的理解來總結介紹一下RxSwift最基本的...
    FKSky閱讀 3,043評論 4 14
  • 投射心情舒暢開心快樂,美噠噠 投射工作快快來,滿意自在舒適 投射寶寶開心快樂,健康成長 投射有一個愛我的和我愛的人...
    翟美麗閱讀 133評論 0 0
  • 首先要理清一下什么是“高下”,高,就是高雅,指高尚雅致,表現(xiàn)受過良好教養(yǎng)的高尚舉止或情趣,比喻高超雅正,與“平庸邪...
    運劍閱讀 312評論 0 0

友情鏈接更多精彩內容