美文网首页iOS学习iOS学习笔记
RXSwift --UITableView之初探

RXSwift --UITableView之初探

作者: 蓝色的风 | 来源:发表于2017-10-12 16:42 被阅读438次

    对于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添加 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

    有没有很简单啊。设置代理,不需要,添加代理方法,不需要。看到这里你已经成功一小步了 。

    三.添加带有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

    四.接下来创建一个可编辑的tableView,数据是从网上请求返回的。

    先贴上效果图

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

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

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

    相关文章

      网友评论

        本文标题:RXSwift --UITableView之初探

        本文链接:https://www.haomeiwen.com/subject/ikonyxtx.html