RXSwift --UITableView之初探

对于RXSwift中的一些基本概念和说明请参看其他文章,接下来我们使用RXSwift一步一步去构建TableView,从简单到复杂。iOS开发过程中tableView的使用率是最高的,他的一些代理方法功能强大,添加起来也比较繁琐。今天就使用RXSwift来创建tableView

一.开始之前的准备

1.项目使用Xcode9开发,
2.新建项目,使用pod下载所需类库

    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快速开始

二.下面开始开车了,请大家做好🤣🤣🤣( ̄▽ ̄)~*

在storyBoard中添加好tableView和对应的关联属性

tableView添加
tableView添加
cell的属性设置
cell的属性设置

tableView在关联属性的时候没有设置代理,只是最简单的设置。在ViewController中引入

import RxSwift
import RxCocoa

 

下面直接贴上代码,再解释

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实现部分和上面的基本一样,在接下来的请求中只是修改了数据源,

    //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)
    }

 

接下来就可以编译项目工程了原文地址
源码连接

以上是个人理解 ,如有错误信息欢迎指正😁😁😁😁

posted @ 2017-10-13 14:14  蓝色的风1203  阅读(2072)  评论(0编辑  收藏  举报