RxSwift相關基本介紹和用法可參考:html
demo是使用的純MVVM模式,由於RxSwift就是爲MVVM而生。不懂MVVM的猿友可參考MVVM模式快速入門ios
// Swift三方庫
// Rx
pod 'RxSwift' //RxSwift的必備庫
pod 'RxCocoa' //對 UIKit Foundation 進行 Rx 化
pod 'RxDataSources' // 幫助咱們優雅的使用tableView的數據源方法
// 網絡請求
pod 'Moya/RxSwift' // 爲RxSwift專用提供,對Alamofire進行封裝的一個網絡請求庫
// 圖片處理
pod 'Kingfisher' //圖片處理庫
// 數據解析
pod 'ObjectMapper' //json轉模型
// OC庫
// MJRefresh
pod 'MJRefresh' //MJ上拉下拉刷新
pod 'SVProgressHUD' //HUD
複製代碼
extension InputValidator {
//判斷字符串是否符合語法法則
class func isValidEmail(_ email: String) -> Bool {
let regular = try? NSRegularExpression(pattern: "^\\S+@\\S+\\.\\S+$", options: [])
if let re = regular {
let range = NSRange(location: 0, length: email.lengthOfBytes(using: .utf8))
let result = re.matches(in: email, options: [], range: range)
return result.count > 0
}
return false
}
//判斷密碼字符個數>8
class func isValidPassword(_ password: String) -> Bool {
return password.characters.count >= 8
}
//判斷用戶名
class func validateUserName(_ username: String) -> Result {
//判斷字符個數是否正確
if username.characters.count < 6 {
return Result.failure(message: "輸入的字符個數不能少於6個字符")
}
//帳號可用
return Result.success(message: "帳號可用")
}
}
複製代碼
其中Result是一個返回是否成功的枚舉值,可傳入字符串變量git
enum Result {
case success(message: String)
case failure(message: String)
}
複製代碼
var usernameObserable: Observable<Result>
var passwordObserable: Observable<Result>
var repeatPassObserable: Observable<Result>
var registerBtnObserable: Observable<Bool>
init(){
//檢測帳號
usernameObserable = username.asObservable().map({ (username) -> Result in
return InputValidator.validateUserName(username)
})
}
複製代碼
//1. 帳號判斷邏輯
//1-1. 檢測帳號
usernameTextField.rx.text
.orEmpty // 將String? 類型轉爲String型
.bindTo(registerVM.username)
.addDisposableTo(bag)
//1-2. 根據帳號監聽提示字體的狀態
registerVM.usernameObserable
.bindTo(usernameHintLabel.rx.validationResult)
.addDisposableTo(bag)
//1-3. 根據帳號監聽密碼輸入框的狀態
registerVM.usernameObserable
.bindTo(passwordTextField.rx.enableResult)
.addDisposableTo(bag)
複製代碼
fileprivate func getHeroData() -> [HeroModel]{
// 1.獲取路徑
let path = Bundle.main.path(forResource: "heros.plist", ofType: nil)!
// 2.讀取文件內容
let dictArray = NSArray(contentsOfFile: path) as! [[String : Any]]
// 3.遍歷全部的字典而且轉成模型對象
return dictArray.map({ HeroModel(dict: $0) }).reversed()
}
複製代碼
lazy var heroVariable: Variable<[HeroModel]> = {
return Variable(self.getHeroData())
}()
var searchText: Observable<String>
init(searchText: Observable<String>) {
self.searchText = searchText
self.searchText.subscribe(onNext: { (str: String) in
let heros = self.getHeroData().filter({ (hero: HeroModel) -> Bool in
//過濾
if str.isEmpty { return true }
//model是否包含搜索字符串
return hero.name.contains(str)
})
self.heroVariable.value = heros
}).addDisposableTo(bag)
}
複製代碼
var searchText: Observable<String> {
//輸入後間隔0.5秒搜索,在主線程運行
return searchBar.rx.text.orEmpty.throttle(0.5, scheduler: MainScheduler.instance)
}
複製代碼
//2.給tableView綁定數據
//注意: 三個參數:row, model, cell三個順序不能夠搞錯, 不須要的可省略
heroVM.heroVariable.asDriver().drive(rxTableView.rx.items(cellIdentifier: kCellID, cellType: RxTableViewCell.self)) { (_, hero, cell) in
cell.heroModel = hero
}.addDisposableTo(bag)
// 3.監聽UITableView的點擊
rxTableView.rx.modelSelected(HeroModel.self).subscribe { (event: Event<HeroModel>) in
print(event.element?.name ?? "")
}.addDisposableTo(bag)
複製代碼
rxTableView.rx.setDelegate(self).addDisposableTo(bag)
複製代碼
而後在實現相應的代理方法便可,如:github
extension RxTableViewController: UITableViewDelegate{
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
}
複製代碼
這裏再介紹一下ObjectMapperjson
class AnchorModel: Mappable {
var name = "" //名字
var pic51 = "" //頭像
var pic74 = "" //大圖
var live = 0
var push = 0
var focus = 0 //關注量
required init?(map: Map) {
}
func mapping(map: Map) {
name <- map["name"]
pic51 <- map["pic51"]
pic74 <- map["pic74"]
live <- map["live"]
push <- map["push"]
focus <- map["focus"]
}
}
複製代碼
required init?(map: Map) {}
func mapping(map: Map) {}
複製代碼
<-
操做符來處理和映射你的 JSON數據//請求枚舉類型
enum JunNetworkTool {
case getNewList
case getHomeList(page: Int)
}
複製代碼
validate
可不重寫)都必須重寫,不然會報錯//請求參數
extension JunNetworkTool: TargetType {
//統一基本的url
var baseURL: URL {
return (URL(string: "http://qf.56.com/home/v4/moreAnchor.ios"))!
}
//path字段會追加至baseURL後面
var path: String {
return ""
}
//請求的方式
var method: Moya.Method {
return .get
}
//參數編碼方式(這裏使用URL的默認方式)
var parameterEncoding: ParameterEncoding {
return URLEncoding.default
}
//用於單元測試
var sampleData: Data {
return "getList".data(using: .utf8)!
}
//將要被執行的任務(請求:request 下載:upload 上傳:download)
var task: Task {
return .request
}
//請求參數(會在請求時進行編碼)
var parameters: [String: Any]? {
switch self {
case .getHomeList(let index):
return ["index": index]
default:
return ["index": 1]
}
}
//是否執行Alamofire驗證,默認值爲false
var validate: Bool {
return false
}
}
複製代碼
let junNetworkTool = RxMoyaProvider<JunNetworkTool>()
複製代碼
至此,咱們就可使用這個全局變量來請求數據了swift
//MARK: SectionModel
struct AnchorSection {
// items就是rows
var items: [Item]
// 你也能夠這裏加你須要的東西,好比 headerView 的 title
}
extension AnchorSection: SectionModelType {
// 重定義 Item 的類型爲
typealias Item = AnchorModel
init(original: AnchorSection, items: [AnchorSection.Item]) {
self = original
self.items = items
}
}
複製代碼
咱們知道MVVM思想就是將本來在ViewController的視圖顯示邏輯、驗證邏輯、網絡請求等代碼存放於ViewModel中,讓咱們的ViewController瘦身。這些邏輯由ViewModel負責,外界不須要關心,外界只須要結果,ViewModel也只須要將結果給到外界,基於此,咱們定義了一個協議數組
protocol JunViewModelType {
//associatedtype: 關聯類型爲協議中的某個類型提供了一個佔位名(或者說別名),其表明的實際類型在協議被採納時纔會被指定
associatedtype Input
associatedtype Output
//咱們經過 transform 方法將input攜帶的數據進行處理,生成了一個Output
func transform(input: Input) -> Output
}
複製代碼
//刷新的狀態
enum JunRefreshStatus {
case none
case beingHeaderRefresh
case endHeaderRefresh
case beingFooterRefresh
case endFooterRefresh
case noMoreData
}
複製代碼
class BaseViewModel: NSObject {
// 記錄當前的索引值
var index: Int = 1
struct JunInput {
// 網絡請求類型
let category: JunNetworkTool
init(category: JunNetworkTool) {
self.category = category
}
}
struct JunOutput {
// tableView的sections數據
let sections: Driver<[AnchorSection]>
// 外界經過該屬性告訴viewModel加載數據(傳入的值是爲了標誌是否從新加載)
let requestCommond = PublishSubject<Bool>()
// 告訴外界的tableView當前的刷新狀態
let refreshStatus = Variable<JunRefreshStatus>(.none)
//初始化時,section的數據
init(sections: Driver<[AnchorSection]>) {
self.sections = sections
}
}
}
複製代碼
class AnchorViewModel : BaseViewModel{
// 存放着解析完成的模型數組
let anchorArr = Variable<[AnchorModel]>([])
}
複製代碼
extension AnchorViewModel: JunViewModelType {
typealias Input = JunInput
typealias Output = JunOutput
func transform(input: AnchorViewModel.JunInput) -> AnchorViewModel.JunOutput {
let sectionArr = anchorArr.asDriver().map { (models) -> [AnchorSection] in
// 當models的值被改變時會調用
return [AnchorSection(items: models)]
}.asDriver(onErrorJustReturn: [])
let output = JunOutput(sections: sectionArr)
output.requestCommond.subscribe(onNext: { (isReloadData) in
self.index = isReloadData ? 1 : self.index + 1
//開始請求數據
junNetworkTool.request(JunNetworkTool.getHomeList(page: self.index))
.mapObjectArray(AnchorModel.self)
.subscribe({ (event) in
switch event {
case let .next(modelArr):
self.anchorArr.value = isReloadData ? modelArr : (self.anchorArr.value) + modelArr
SVProgressHUD.showSuccess(withStatus: "加載成功")
case let .error(error):
SVProgressHUD.showError(withStatus: error.localizedDescription)
case .completed:
output.refreshStatus.value = isReloadData ? .endHeaderRefresh : .endFooterRefresh
}
}).addDisposableTo(bag)
}).addDisposableTo(bag)
return output
}
}
複製代碼
// 建立一個數據源屬性,類型爲自定義的Section類型
let dataSource = RxCollectionViewSectionedReloadDataSource<AnchorSection>()
複製代碼
dataSource.configureCell = { dataSource, collectionView, indexPath, item in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: kCollecCellID, for: indexPath) as! RxCollectionViewCell
cell.anchorModel = item
return cell
}
複製代碼
let vmInput = AnchorViewModel.JunInput(category: .getNewList)
let vmOutput = anchorVM.transform(input: vmInput)
複製代碼
//4-1. 經過dataSource和section的model數組綁定數據(demo的用法, 推薦)
vmOutput.sections
.asDriver()
.drive(collectionVIew.rx.items(dataSource: dataSource))
.addDisposableTo(bag)
複製代碼
collectionVIew.mj_header = MJRefreshNormalHeader(refreshingBlock: {
vmOutput.requestCommond.onNext(true)
})
collectionVIew.mj_header.beginRefreshing()
collectionVIew.mj_footer = MJRefreshAutoNormalFooter(refreshingBlock: {
vmOutput.requestCommond.onNext(false)
})
複製代碼
// 告訴外界的tableView當前的刷新狀態
let refreshStatus = Variable<JunRefreshStatus>(.none)
複製代碼
//5. 設置刷新狀態
vmOutput.refreshStatus.asObservable().subscribe(onNext: { (status) in
switch status {
case .beingHeaderRefresh:
self.collectionVIew.mj_header.beginRefreshing()
case .endHeaderRefresh:
self.collectionVIew.mj_header.endRefreshing()
case .beingFooterRefresh:
self.collectionVIew.mj_footer.beginRefreshing()
case .endFooterRefresh:
self.collectionVIew.mj_footer.endRefreshing()
case .noMoreData:
self.collectionVIew.mj_footer.endRefreshingWithNoMoreData()
default:
break
}
}).addDisposableTo(bag)
複製代碼
// 外界經過該屬性告訴viewModel加載數據(傳入的值是爲了標誌是否從新加載)
let requestCommond = PublishSubject<Bool>()
複製代碼