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在关联属性的时候没有设置代理,只是最简单的设置。在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如下
有没有很简单啊。设置代理,不需要,添加代理方法,不需要。看到这里你已经成功一小步了 。
三.添加带有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如下
四.接下来创建一个可编辑的tableView,数据是从网上请求返回的。
先贴上效果图
这个实现起来就有点绕了 ,请带好你的脑子🤣🤣🤣
这个是请求数据的显示信息,我们要先实现一个网络请求数据,新建一个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) }
以上是个人理解 ,如有错误信息欢迎指正😁😁😁😁