1. RxSwift
Rx里面有列表的扩展,支持基本的列表展示
● 有下面两种方式
let items = Observable.just([
"First Item",
"Second Item",
"Third Item"
])
items
.bind(to: tableView.rx.items) { (tableView, row, element) in
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!
cell.textLabel?.text = "\(element) @ row \(row)"
return cell
}
.disposed(by: disposeBag)
items
.bind(to: tableView.rx.items(cellIdentifier: "Cell", cellType: UITableViewCell.self)) { (row, element, cell) in
cell.textLabel?.text = "\(element) @ row \(row)"
}.disposed(by: disposeBag)
// 要实现 添加 删除 ,修改
// 直接修改Observable
看上面的方式,只支持简单的列表, 有多个section的情况就不行了
2. 复杂的列表 使用RxDataSource
上面说了Rx只能用来展示简单的列表,复杂的列表 以及列表的添加删除动画过程如何实现呢,就是RxDataSource 提供了一些高级的方法实现。
RxTableViewSectionedReloadDataSource 和RxTableViewSectionedAnimatedDataSource
比如要实现下图的列表,
2.1 RxTableViewSectionedReloadDataSource
要实现上面的列表,要先定义sectionmodel和cellmodel,并且sectionmodel 要实现 SectionModelType 协议
struct SectionModel {
var header: String
var numbers: [CellModel]
var updated: Date
init(header: String, numbers: [Item], updated: Date) {
self.header = header
self.numbers = numbers
self.updated = updated
}
}
struct CellModel {
let number: Int
let date: Date
}
extension NumberSection: SectionModelType {
init(original: NumberSection, items: [IntItem]) {
self = original
self.numbers = items
}
typealias Item = IntItem
var items: [IntItem] {
return numbers
}
}
二, 要实现RxTableViewSectionedReloadDataSource, 泛型使用刚才定义的sectionmode
let dataSource = RxTableViewSectionedReloadDataSource(
configureCell: { ds, tv, _, i in
let cell = tv.dequeueReusableCell(withIdentifier: "Cell") ?? UITableViewCell(style: .default, reuseIdentifier: "Cell")
cell.textLabel?.text = "\(i)"
return cell
},
titleForHeaderInSection: { ds, index in
return ds.sectionModels[index].header
}
)
三, 创建列表数据 Observal ,把datasour和tableview 绑定起来
Observal.just([数据源])
.bind(to: tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)`
通过上面步骤 一个包含的多个section的列表就展示出来了。
2.2 RxTableViewSectionedAnimatedDataSource
如果要实现添加 移动 删除 动态效果,使用这个DataSource,
- 首先 sectionmodel 要实现AnimatableSectionModelType协议,
cellModel 要遵循 IdentifiableType + Equatable 协议
extension SectionModel
: AnimatableSectionModelType, Equatable {
typealias Identity = String
var identity: String {
return header
}
}
extension CellModel
: IdentifiableType
, Equatable {
typealias Identity = Int
var identity: Int {
return number
}
}
func == (lhs: CellModel, rhs: CellModel) -> Bool {
return lhs.number == rhs.number && lhs.date == rhs.date
}
func == (lhs: SectionModel, rhs: SectionModel) -> Bool {
return lhs.header == rhs.header && lhs.items == rhs.items && lhs.updated == rhs.updated
}
实现这些协议 可以更好的比较不同sectionmodel 和cellmodel 的变化,来实现列表的刷新。
3. 一个demo 实现了列表的添加删除移动
// redux like editing example
class EditingExampleViewController: UIViewController {
@IBOutlet private weak var addButton: UIBarButtonItem!
@IBOutlet private weak var tableView: UITableView!
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
let dataSource = EditingExampleViewController.dataSource()
let sections: [NumberSection] = [NumberSection(header: "Section 1", numbers: [], updated: Date()),
NumberSection(header: "Section 2", numbers: [], updated: Date()),
NumberSection(header: "Section 3", numbers: [], updated: Date())]
let initialState = SectionedTableViewState(sections: sections)
let add3ItemsAddStart = Observable.of((), (), ())
let addCommand = Observable.of(addButton.rx.tap.asObservable(), add3ItemsAddStart)
.merge()
.map(TableViewEditingCommand.addRandomItem)
let deleteCommand = tableView.rx.itemDeleted.asObservable()
.map(TableViewEditingCommand.DeleteItem)
let movedCommand = tableView.rx.itemMoved
.map(TableViewEditingCommand.MoveItem)
Observable.of(addCommand, deleteCommand, movedCommand)
.merge()
.scan(initialState) { (state: SectionedTableViewState, command: TableViewEditingCommand) -> SectionedTableViewState in
return state.execute(command: command)
}
.startWith(initialState)
.map {
$0.sections
}
.share(replay: 1)
.bind(to: tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
tableView.setEditing(true, animated: true)
}
}
extension EditingExampleViewController {
static func dataSource() -> RxTableViewSectionedAnimatedDataSource {
return RxTableViewSectionedAnimatedDataSource(
animationConfiguration: AnimationConfiguration(insertAnimation: .top,
reloadAnimation: .fade,
deleteAnimation: .left),
configureCell: { _, table, idxPath, item in
let cell = table.dequeueReusableCell(withIdentifier: "Cell", for: idxPath)
cell.textLabel?.text = "\(item)"
return cell
},
titleForHeaderInSection: { ds, section -> String? in
return ds[section].header
},
canEditRowAtIndexPath: { _, _ in
return true
},
canMoveRowAtIndexPath: { _, _ in
return true
}
)
}
}
enum TableViewEditingCommand {
case AppendItem(item: IntItem, section: Int)
case MoveItem(sourceIndex: IndexPath, destinationIndex: IndexPath)
case DeleteItem(IndexPath)
}
// This is the part
struct SectionedTableViewState {
fileprivate var sections: [NumberSection]
init(sections: [NumberSection]) {
self.sections = sections
}
func execute(command: TableViewEditingCommand) -> SectionedTableViewState {
switch command {
case .AppendItem(let appendEvent):
var sections = self.sections
let items = sections[appendEvent.section].items + appendEvent.item
sections[appendEvent.section] = NumberSection(original: sections[appendEvent.section], items: items)
return SectionedTableViewState(sections: sections)
case .DeleteItem(let indexPath):
var sections = self.sections
var items = sections[indexPath.section].items
items.remove(at: indexPath.row)
sections[indexPath.section] = NumberSection(original: sections[indexPath.section], items: items)
return SectionedTableViewState(sections: sections)
case .MoveItem(let moveEvent):
var sections = self.sections
var sourceItems = sections[moveEvent.sourceIndex.section].items
var destinationItems = sections[moveEvent.destinationIndex.section].items
if moveEvent.sourceIndex.section == moveEvent.destinationIndex.section {
destinationItems.insert(destinationItems.remove(at: moveEvent.sourceIndex.row),
at: moveEvent.destinationIndex.row)
let destinationSection = NumberSection(original: sections[moveEvent.destinationIndex.section], items: destinationItems)
sections[moveEvent.sourceIndex.section] = destinationSection
return SectionedTableViewState(sections: sections)
} else {
let item = sourceItems.remove(at: moveEvent.sourceIndex.row)
destinationItems.insert(item, at: moveEvent.destinationIndex.row)
let sourceSection = NumberSection(original: sections[moveEvent.sourceIndex.section], items: sourceItems)
let destinationSection = NumberSection(original: sections[moveEvent.destinationIndex.section], items: destinationItems)
sections[moveEvent.sourceIndex.section] = sourceSection
sections[moveEvent.destinationIndex.section] = destinationSection
return SectionedTableViewState(sections: sections)
}
}
}
}
extension TableViewEditingCommand {
static var nextNumber = 0
static func addRandomItem() -> TableViewEditingCommand {
let randSection = Int.random(in: 0...2)
let number = nextNumber
defer { nextNumber = nextNumber + 1 }
let item = IntItem(number: number, date: Date())
return TableViewEditingCommand.AppendItem(item: item, section: randSection)
}
}
func + (lhs: [T], rhs: T) -> [T] {
var copy = lhs
copy.append(rhs)
return copy
}