RXSwift --UITableView之初探

對於RXSwift中的一些基本概念和說明請參看其餘文章,接下來咱們使用RXSwift一步一步去構建TableView,從簡單到複雜。iOS開發過程當中tableView的使用率是最高的,他的一些代理方法功能強大,添加起來也比較繁瑣。今天就使用RXSwift來建立tableViewreact

一.開始以前的準備

1.項目使用Xcode9開發,
2.新建項目,使用pod下載所需類庫 git

    pod 'RxSwift', '~> 3.6.1' #本次的主角
    pod 'RxCocoa', '~> 3.6.1' #對RXSwift的一下拓展
    pod 'NSObject+Rx'          #NSObject的RX拓展
    pod 'Alamofire', '~> 4.5.0'      #網絡請求
    pod 'RxDataSources', '~> 1.0' #對tableview和Collection的一下拓展實現
    pod 'Then', '~> 2.1.0'#簡化對象初始化
    pod 'Kingfisher', '~> 3.10.2'    #網絡圖片緩存
    你們能夠根據本身的需求進行添加

 

3.直接使用storyBoard快速開始github

二.下面開始開車了,請你們作好🤣🤣🤣( ̄▽ ̄)~*

在storyBoard中添加好tableView和對應的關聯屬性json

tableView添加
tableView添加
cell的屬性設置
cell的屬性設置

tableView在關聯屬性的時候沒有設置代理,只是最簡單的設置。在ViewController中引入swift

import RxSwift
import RxCocoa

 

下面直接貼上代碼,再解釋api

class ZJSimpleTableViewController: UIViewController ,UITableViewDelegate{
    @IBOutlet weak var myTableView: UITableView!
    //在函數執行完以後就釋放 
    let disposeBag = DisposeBag()
    override func viewDidLoad() {
        super.viewDidLoad() 
        // 建立一個sequence 能夠發出多種事件信號,此地建立了數據源
        let items = Observable.just(
            (0..<20).map { "\($0)" }
        )
        //將數據源綁定到tableView的rx.items事件流中
        items
            .bind(to: myTableView.rx.items(cellIdentifier: "Cell", cellType: UITableViewCell.self)) { (row, element, cell) in
                cell.textLabel?.text = "\(element) @ row \(row)"
            }
            .disposed(by: disposeBag)
        //tableview的didSelected方法
        myTableView.rx
            .modelSelected(String.self)
            .subscribe(onNext:  { value in
                print("開始選中====\(value)")
            })
            .disposed(by: disposeBag)
        //tableviewcell中的點擊感嘆號
        myTableView.rx
            .itemAccessoryButtonTapped
            .subscribe(onNext: { indexPath in
                print("顯示詳情===\(indexPath.row)")
            })
            .disposed(by: disposeBag)
    }
}

 

設置以後顯示的tableview以下數組

simpleTableView.gif
simpleTableView.gif

有沒有很簡單啊。設置代理,不須要,添加代理方法,不須要。看到這裏你已經成功一小步了 。緩存

三.添加帶有Section的tableView

這下咱們要引入網絡

import RxDataSources

 

stroryBoard實現部分和上面的基本同樣,在接下來的請求中只是修改了數據源,app

    //tableview綁定的數據源 定義數據源裏面的數據類型  String :Double
    let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String,Double>>()
    let disposeBag = DisposeBag()
    override func viewDidLoad() {
        super.viewDidLoad()
        //建立數據源 每一個sectionModel就是一個分組
        let items = Observable.just([
            SectionModel.init(model: "First Section", items: [
                1.0,2.0,3.0]),
            SectionModel.init(model: "Second Section", items: [
                1.0,2.0,3.0]),
            SectionModel.init(model: "Third Section", items: [
                1.0,2.0,3.0])
            ])
        
        dataSource.configureCell = {(_,tableView,indexPath,element) in
            //注意此地的identifier的變化
            let cell = tableView.dequeueReusableCell(withIdentifier: "sectionCell", for: indexPath) as UITableViewCell
            cell.textLabel?.text = "顯示的cell \(element) @ \(indexPath.row)"
            return cell
        }
        //添加頭
        dataSource.titleForHeaderInSection = { data ,sectionIndex in
            return data[sectionIndex].model
        }
        //綁定數據
        items.bind(to: myTableView.rx.items(dataSource: dataSource))
            .disposed(by: disposeBag)
        //設置代理,能夠設置header的高度和cell的高度
        myTableView.rx.setDelegate(self).disposed(by: disposeBag)
        //設置點擊事件  包裝信息 map函數把當前的indexPath和當前的數據包裝傳到subscribe函數中
        myTableView.rx.itemSelected
            .map{ [unowned self] indexPath in
                return (indexPath,self.dataSource[indexPath])
            }.subscribe(onNext: { (indexPath,element) in
                print("當前選中==\(indexPath.row) @ \(element)")
            }).disposed(by: disposeBag)
    }

 

設置以後的tableView以下

sectionTableView.gif
sectionTableView.gif

四.接下來建立一個可編輯的tableView,數據是從網上請求返回的。

先貼上效果圖

editTableView.gif
editTableView.gif

這個實現起來就有點繞了 ,請帶好你的腦子🤣🤣🤣

這個是請求數據的顯示信息,咱們要先實現一個網絡請求數據,新建一個swift文件,不繼承任何類

import Foundation
import RxSwift

import struct Foundation.URL
import class Foundation.URLSession

func exampleError(_ error: String, location: String = "\(#file):\(#line)") -> NSError {
    return NSError(domain: "ExampleError", code: -1, userInfo: [NSLocalizedDescriptionKey: "\(location): \(error)"])
}


class RandomUserAPI {
   //定義一個單例
    static let shareAPI = RandomUserAPI()
    
    init() {}
    
    //獲取用戶信息 URLSession
    func getRandomUserResult() -> Observable<[Users]> {
        let url = URL(string: "http://api.randomuser.me/?results=20")!
        return URLSession.shared.rx.json(url: url).map{
            json in
            guard let json = json as? [String:AnyObject] else{
                throw exampleError("不能轉化成字典")
            }
            return try self.dealWithJSON(json: json)
        }
    }
    
    //處理json的數據結果
    func dealWithJSON(json:[String:AnyObject]) throws -> [Users] {
        guard let result = json["results"] as? [[String:AnyObject]] else {
            throw exampleError("找不到結果")
        }
        
        let userParseError = exampleError("結果處理出錯")
        let userResult:[Users] = try result.map { user in
            let name = user["name"] as? [String:String]
            let imageurl = user["picture"] as? [String:String]
            
            guard let first = name?["first"] ,let last = name?["last"] ,let imageURL = imageurl?["large"] else{
                throw userParseError
            }
            //添加USer類
            let returnUser = Users(first: first, last: last, image: imageURL)
            return returnUser
        }
        return userResult
    }
}

 

接下來是USers類的實現,一樣是新建一個swift文件,定義一個結構體(爲何是結構體我也不知道😂😂😂,swift中見過不少用結構體來定義Model的)

import Foundation

struct Users:Equatable,CustomDebugStringConvertible {
    
    var firstName:String
    var lastName:String
    var imageURL:String
    //初始化對象
    init(first:String,last:String,image:String) {
        firstName = first
        lastName = last
        imageURL = image
    }
}
//CustomDebugStringConvertible 協議必需要實現的方法
extension Users{
    var debugDescription:String{
        return firstName + " " + lastName
    }
}

//Equatable 協議必需要實現的方法
func ==(lhs: Users, rhs: Users) -> Bool {
    return lhs.firstName == rhs.firstName &&
        lhs.lastName == rhs.lastName &&
        lhs.imageURL == rhs.imageURL
}

 

咱們如今有數據源了,想着怎麼把數據放進綁定的tableView中,還有tableView的一寫操做中怎麼處理操做和數據之間的關係

開始處理tableView的操做,新建文件定義宏

//定義tableView的操做
enum TableViewEditingCommand {
    case setUsers(Users:[Users])//設置普通顯示用戶
    case setFavoriteUsers(favoriteUsers:[Users])//設置喜歡顯示用戶數據
    case deleteUser(IndexPath:IndexPath)//刪除數據
    case moveUser(from:IndexPath,to:IndexPath)//移動數據
}

 

接下來就是處理這四個事件所對應的方法

struct TableViewEditComandsViewModel {
    let favoriteUsers:[Users]
    let users :[Users]
    //這裏就是綁定各個枚舉處理事件的方法,根據傳進去的參數修改對應的數據
    //添加方法
    static func executeCommand(state:TableViewEditComandsViewModel,_ command:TableViewEditingCommand) -> TableViewEditComandsViewModel{
        switch command {
        case let .setUsers(Users):
            return TableViewEditComandsViewModel.init(favoriteUsers: state.favoriteUsers, users: Users)
        case let .setFavoriteUsers(favoriteUsers):
            return TableViewEditComandsViewModel.init(favoriteUsers: favoriteUsers, users: state.users)
        case let .deleteUser(IndexPath):
            var all = [state.favoriteUsers,state.users]
            all[IndexPath.section].remove(at: IndexPath.row)
            return TableViewEditComandsViewModel.init(favoriteUsers: all[0], users: all[1])
        case let .moveUser(from, to):
            var all = [state.favoriteUsers,state.users]
            let user = all[from.section][from.row]
            all[from.section].remove(at: from.row)
            all[to.section].insert(user, at: to.row)
            return TableViewEditComandsViewModel.init(favoriteUsers: all[0], users: all[1])
        }
    }
}

 

而後就是數據的綁定和顯示
對tableview的初始化數據抽出一個方法

static func configurationDataSource()-> RxTableViewSectionedReloadDataSource<SectionModel<String,Users>> {
        
        let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String,Users>>()
        dataSource.configureCell = { (_,tv,ip,user:Users) in
            let cell = tv.dequeueReusableCell(withIdentifier: "systemCell")!
            cell.textLabel?.text = user.firstName + " " + user.lastName
            return cell
        }
        //可否編輯
        dataSource.titleForHeaderInSection = {
            data,section in
            return data[section].model
        }
        dataSource.canEditRowAtIndexPath = {(data,indexpath) in
            return true
        }
        dataSource.canMoveRowAtIndexPath = { (data,indexpath) in
            return true
        }
        return dataSource
    }

 

viewController中定義

    var isEdit: Bool = false //是否在編輯狀態
    let dataSource = ZJEditTableViewController.configurationDataSource()//tableView抽出的數據源方法
    let disposeBag = DisposeBag()//Rx釋放

 

咱們將tableView操做命令和上面的枚舉進行綁定

       //初始化兩個普通顯示用戶
       let superMan = Users(
           first: "Super",
           last: "Man",
           image: "http://nerdreactor.com/wp-content/uploads/2015/02/Superman1.jpg"
       )
       
       let watchMan = Users(
           first:"Watch",
           last:"Man",
           image:"http://www.iri.upc.edu/files/project/98/main.GIF"
       )
       
       //請求數據
       let loadFavoriteUsers = RandomUserAPI.shareAPI.getRandomUserResult()
            //將原來的sequence轉化爲一個新的sequence
           .map(TableViewEditingCommand.setUsers)
           //錯誤處理信息  設置爲一個空數組[]
           .catchErrorJustReturn(TableViewEditingCommand.setUsers(Users: []))
       //生成一個sequence 綁定superMan,watchMan ,concat會把多個sequence和併爲一個sequence,而且當前面一個sequence發出了completed事件,纔會開始下一個sequence的事件。
       let initialLoadCommand = Observable.just(TableViewEditingCommand.setFavoriteUsers(favoriteUsers: [superMan,watchMan]))
           .concat(loadFavoriteUsers)
           .observeOn(MainScheduler.instance)
       //綁定刪除
       let deleteUsersCommmand = myTableView.rx.itemDeleted.map(TableViewEditingCommand.deleteUser)
       //綁定移動
       let moveUserCommand = myTableView.rx.itemMoved.map(TableViewEditingCommand.moveUser)
       //初始化信息 都爲空
       let initialState = TableViewEditComandsViewModel.init(favoriteUsers: [], users: [])

       let viewModel = Observable.system(initialState,
                       accumulator: TableViewEditComandsViewModel.executeCommand,
                       scheduler: MainScheduler.instance,
                       feedback: {_ in initialLoadCommand},{_ in deleteUsersCommmand},{ _ in moveUserCommand})
           .shareReplay(1)

       //viewModel綁定到dataSource
       viewModel
           .map{[
              SectionModel(model: "Favorite Users", items: $0.favoriteUsers),
              SectionModel(model: "Normal Users", items: $0.users)
           ]}
           .bind(to: myTableView.rx.items(dataSource: dataSource))
           .disposed(by: disposeBag)
       
       myTableView.rx.itemSelected
           .map{ [unowned self] indexPath in
               return (indexPath,self.dataSource[indexPath])
           }.subscribe(onNext: { [unowned self](indexPath,user) in
               self.showDetailsForUser(user)
           }).disposed(by: disposeBag)
       
       myTableView.rx.setDelegate(self)
           .disposed(by: disposeBag)

 

添加headerView和設置header的高度

    //添加headerSection
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let title = dataSource[section]
        
        let label = UILabel(frame: CGRect.zero)
        label.text = "  \(title)"
        label.textColor = UIColor.white
        label.backgroundColor = UIColor.darkGray
        label.alpha = 0.9
        
        return label
    }
    
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 40
    }
    
    @IBAction func tableViewEditAction(_ sender: Any) {
        isEdit = !isEdit
        myTableView.isEditing = isEdit
    }
    //具體用戶信息的跳轉界面
    private func showDetailsForUser(_ user: Users) {
        let storyboard = UIStoryboard(name: "EditTableView", bundle: nil)
        let viewController = storyboard.instantiateViewController(withIdentifier: "userDetailVC") as! ZJUserDetailViewController
        viewController.user = user
        self.navigationController?.pushViewController(viewController, animated: true)
    }

 

接下來就能夠編譯項目工程了原文地址
源碼鏈接

以上是我的理解 ,若有錯誤信息歡迎指正😁😁😁😁

相關文章
相關標籤/搜索