RxSwift 案例學習(一)

xixinRunBoy發表於2018-03-21

本文是官方案例GitHubSignup-UsingDriver學習筆記

專案實現功能

這個登入頁面實現了下面幾個功能: 1.檢驗使用者名稱是否可用 2.密碼是否符合要求 3.確認密碼是符合密碼一樣 4.上面上面三個都符合要求,登入按鈕才可以點選 5.當使用者正在登入的時候,顯示 activityIndicator,提醒使用者等待,此時按鈕不能被按;當得到登入結果的時候,隱藏 activityIndicator。 6.登入完成顯示登入結果

具體實現

GitHubSignupViewController2.swift

#if !RX_NO_MODULE
import RxSwift
import RxCocoa
#endif
複製程式碼

引用部分RX_NO_MODULE這個巨集的字面意思應該是沒有rx的模組意思,但是沒有找到具體實現在哪裡,如果誰知道麻煩告知

    @IBOutlet weak var usernameOutlet: UITextField!
    @IBOutlet weak var usernameValidationOutlet: UILabel!

    @IBOutlet weak var passwordOutlet: UITextField!
    @IBOutlet weak var passwordValidationOutlet: UILabel!
    
    @IBOutlet weak var repeatedPasswordOutlet: UITextField!
    @IBOutlet weak var repeatedPasswordValidationOutlet: UILabel!
    
    @IBOutlet weak var signupOutlet: UIButton!
    @IBOutlet weak var signingUpOulet: UIActivityIndicatorView!
複製程式碼

首先是各個控制元件的繫結

 let viewModel = GithubSignupViewModel2(
            input: (
                username: usernameOutlet.rx.text.orEmpty.asDriver(),
                password: passwordOutlet.rx.text.orEmpty.asDriver(),
                repeatedPassword: repeatedPasswordOutlet.rx.text.orEmpty.asDriver(),
                loginTaps: signupOutlet.rx.tap.asDriver()
            ),
            dependency: (
                API: GitHubDefaultAPI.sharedAPI,
                validationService: GitHubDefaultValidationService.sharedValidationService,
                wireframe: DefaultWireframe.sharedInstance
            )
        )
複製程式碼

viewModel初始化,rx是RxSwift的域名,text是觀察的屬性,orEmpty是檢驗text是否為nil如果為nil返回"",asDriver()是具體特殊屬性的Observable,如果使用asObservable(),就要額外新增.observeOn(MainScheduler.instance)和.shareReplay(1), Driver是屬於Rxcocoa庫,是對Observable進行了一些封裝,Observable是屬於RxSwift庫

  viewModel.signupEnabled
            .drive(onNext: { [weak self] valid  in
                self?.signupOutlet.isEnabled = valid
                self?.signupOutlet.alpha = valid ? 1.0 : 0.5
            })
            .addDisposableTo(disposeBag)
複製程式碼

監聽viewModel.signupEnabled的值變化,signupEnabled的型別為 Driver,那麼valid的型別就為Bool,根據valid來設定按鈕的狀態

  viewModel.validatedUsername
            .drive(usernameValidationOutlet.rx.validationResult)
            .addDisposableTo(disposeBag)

  viewModel.validatedPassword
            .drive(passwordValidationOutlet.rx.validationResult)
            .addDisposableTo(disposeBag)

viewModel.validatedPasswordRepeated
          .drive(repeatedPasswordValidationOutlet.rx.validationResult)
            .addDisposableTo(disposeBag)

viewModel.signingIn
            .drive(signingUpOulet.rx.isAnimating)
            .addDisposableTo(disposeBag)

複製程式碼

將viewModel.validatedUsername和UILabel的validationResult繫結, validationResult是UILabel自定義的Rx擴充套件,原始碼如下:

extension Reactive where Base: UILabel {
    var validationResult: UIBindingObserver<Base, ValidationResult> {
        return UIBindingObserver(UIElement: base) { label, result in
            label.textColor = result.textColor
            label.text = result.description
        }
    }
}
複製程式碼
   let tapBackground = UITapGestureRecognizer()
        tapBackground.rx.event
            .subscribe(onNext: { [weak self] _ in
                self?.view.endEditing(true)
            })
            .addDisposableTo(disposeBag)
        view.addGestureRecognizer(tapBackground)
複製程式碼

以上是新建一個tap手勢並使用Rx對手勢的監聽來實現相應的功能

GithubSignupViewModel2.swift

   init(
        input: (
            username: Driver<String>,
            password: Driver<String>,
            repeatedPassword: Driver<String>,
            loginTaps: Driver<Void>
        ),
        dependency: (
            API: GitHubAPI,
            validationService: GitHubValidationService,
            wireframe: Wireframe
        )
    )
複製程式碼

首先是初始化接受一個兩個元組作為引數

 validatedUsername = input.username
            .distinctUntilChanged() //demo重複檢查
            .flatMapLatest { username in
                return validationService.validateUsername(username)
                    .asDriver(onErrorJustReturn: .failed(message: "Error contacting server"))
            }

 validatedPassword = input.password
            .map { password in
                return validationService.validatePassword(password)
            }
複製程式碼

對username和password的值進行處理,然後返回Driver的結果為後面處理做準備

validatedPasswordRepeated = Driver.combineLatest(input.password, input.repeatedPassword, resultSelector: validationService.validateRepeatedPassword)
複製程式碼

Driver.combineLatest將兩個流合併成一個流,通過對原始碼的閱讀發現combineLatest最多支援8路流合併成一路流, resultSelector提供多路流合併的方法,這裡可以寫成閉包的形式,也可以直接傳入一個處理函式.

let signingIn = ActivityIndicator()
複製程式碼

ActivityIndicator提供檢測網路訪問狀態的方法

self.signingIn = signingIn.asDriver()
複製程式碼

提供網路訪問的狀態監聽

.trackActivity(signingIn)
複製程式碼

在網路請求時新增到上面的方法,可以監聽網路狀態,網路開始訪問時返回true,網路訪問結束時返回false

signedIn = input.loginTaps.withLatestFrom(usernameAndPassword)
            .flatMapLatest { (username, password) in
                return API.signup(username, password: password)
                    .trackActivity(signingIn)
                    .asDriver(onErrorJustReturn: false)
            }
            .flatMapLatest { loggedIn -> Driver<Bool> in
                let message = loggedIn ? "Mock: Signed in to GitHub." : "Mock: Sign in to GitHub failed"
                return wireframe.promptFor(message, cancelAction: "OK", actions: [])
                    // propagate original value
                    .map { _ in
                        loggedIn
                    }
                    .asDriver(onErrorJustReturn: false)
            }
複製程式碼

每次登入按鈕點選的時候,利用.withLatestFrom(usernameAndPassword)從usernameAndPassword中獲取使用者名稱和密碼,然後傳給網路請求進行訪問,然後顯示登入結果,並返回登入結果給上層處理

     signupEnabled = Driver.combineLatest(
            validatedUsername,
            validatedPassword,
            validatedPasswordRepeated,
            signingIn
        )   { username, password, repeatPassword, signingIn in
                username.isValid &&
                password.isValid &&
                repeatPassword.isValid &&
                !signingIn
            }
            .distinctUntilChanged()
複製程式碼

通過validatedUsername, validatedPassword, validatedPasswordRepeated, signingIn四個事件流的狀態來決定signupEnabled的狀態,也就是決定登入按鈕的狀態

相關文章