RxSwift之UI控件UITableView扩展的使用

一、基本使用

① 单分区的表格

  • 如下所示,单个分区的表格展示:

在这里插入图片描述

  • 示例代码:
import UIKit
import RxSwift
import RxCocoa
 
class ViewController: UIViewController {
    
    
     
    var tableView:UITableView!
    let disposeBag = DisposeBag()
     
    override func viewDidLoad() {
    
    
        super.viewDidLoad()
         
        // 创建表格视图
        self.tableView = UITableView(frame: self.view.frame, style:.plain)
        // 创建一个重用的单元格
        self.tableView!.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
        self.view.addSubview(self.tableView!)
         
        // 初始化数据
        let items = Observable.just([
            "文本输入框的用法",
            "开关按钮的用法",
            "进度条的用法",
            "文本标签的用法",
            ])
         
        // 设置单元格数据(其实就是对 cellForRowAt 的封装)
        items
            .bind(to: tableView.rx.items) {
    
     (tableView, row, element) in
                let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!
                cell.textLabel?.text = "\(row)\(element)"
                return cell
            }
            .disposed(by: disposeBag)
    }
}

② 单元格选中事件响应

  • 当点击某个单元格时将其索引位置,以及对应的标题打印出来:

在这里插入图片描述

选中项的indexPath为:[0, 3]
选中项的标题为:文本标签的用法
  • 业务代码直接放在响应方法内部,示例代码:
// 获取选中项的索引
tableView.rx.itemSelected.subscribe(onNext: {
    
     indexPath in
    print("选中项的indexPath为:\(indexPath)")
}).disposed(by: disposeBag)
 
// 获取选中项的内容
tableView.rx.modelSelected(String.self).subscribe(onNext: {
    
     item in
    print("选中项的标题为:\(item)")
}).disposed(by: disposeBag)
  • 也可以在响应中调用外部的方法:
// 获取选中项的索引
tableView.rx.itemSelected.subscribe(onNext: {
    
     [weak self] indexPath in
	print("选中项的indexPath为:\(indexPath)")
}).disposed(by: disposeBag)
 
// 获取选中项的内容
tableView.rx.modelSelected(String.self).subscribe(onNext: {
    
    [weak self] item in
    print("选中项的标题为:\(item)")
}).disposed(by: disposeBag)

③ 单元格取消选中事件响应

被取消选中项的indexPath为:[0, 2]
被取消选中项的的标题为:进度条的用法
  • 示例代码:
// 获取被取消选中项的索引
tableView.rx.itemDeselected.subscribe(onNext: {
    
     [weak self] indexPath in
	print("被取消选中项的indexPath为:\(indexPath)")
}).disposed(by: disposeBag)
 
// 获取被取消选中项的内容
tableView.rx.modelDeselected(String.self).subscribe(onNext: {
    
    [weak self] item in
	print("被取消选中项的的标题为:\(item)")
}).disposed(by: disposeBag)

④ 单元格删除事件响应

  • 如下所示,左滑删除:
    在这里插入图片描述
  • 示例代码:
// 获取删除项的索引
tableView.rx.itemDeleted.subscribe(onNext: {
    
     indexPath in
    print("删除项的indexPath为:\(indexPath)")
}).disposed(by: disposeBag)
 
// 获取删除项的内容
tableView.rx.modelDeleted(String.self).subscribe(onNext: {
    
    item in
    print("删除项的的标题为:\(item)")
}).disposed(by: disposeBag)

⑤ 单元格移动事件响应

在这里插入图片描述

移动项原来的indexPath为:[0, 0]
移动项现在的indexPath为:[0, 1]
  • 示例代码:
// 获取移动项的索引
tableView.rx.itemMoved.subscribe(onNext: {
    
    
    sourceIndexPath, destinationIndexPath in
	print("移动项原来的indexPath为:\(sourceIndexPath)")
	print("移动项现在的indexPath为:\(destinationIndexPath)")
}).disposed(by: disposeBag)

⑥ 单元格插入事件响应

在这里插入图片描述

插入项的indexPath为:[0, 1]
  • 示例代码:
// 获取插入项的索引
tableView.rx.itemInserted.subscribe(onNext: {
    
     indexPath in
	print("插入项的indexPath为:\(indexPath)")
}).disposed(by: disposeBag)

⑦ 单元格尾部附件(图标)点击事件响应

在这里插入图片描述

尾部项的indexPath为:[0, 1]
  • 示例代码:
// 获取点击的尾部图标的索引
tableView.rx.itemAccessoryButtonTapped.subscribe(onNext: {
    
     indexPath in
	print("尾部项的indexPath为:\(indexPath)")
}).disposed(by: disposeBag)

⑧ 单元格将要显示出来的事件响应

  • 如下所示:
将要显示单元格indexPath为:[0, 0]
将要显示单元格cell为:<UITableViewCell: 0x7fe74e925fd0; frame = (0 0; 428 45); text = '0:文本输入框的用法'; autoresize = W; layer = <CALayer: 0x600003d77560>>

将要显示单元格indexPath为:[0, 1]
将要显示单元格cell为:<UITableViewCell: 0x7fe74eb16080; frame = (0 45; 428 45); text = '1:开关按钮的用法'; autoresize = W; layer = <CALayer: 0x600003d12de0>>

将要显示单元格indexPath为:[0, 2]
将要显示单元格cell为:<UITableViewCell: 0x7fe74ea0b1b0; frame = (0 90; 428 45); text = '2:进度条的用法'; autoresize = W; layer = <CALayer: 0x600003d1a700>>

将要显示单元格indexPath为:[0, 3]
将要显示单元格cell为:<UITableViewCell: 0x7fe74ea0c670; frame = (0 135; 428 45); text = '3:文本标签的用法'; autoresize = W; layer = <CALayer: 0x600003d1a9e0>>
  • 示例代码:
// 获取选中项的索引
tableView.rx.willDisplayCell.subscribe(onNext: {
    
     cell, indexPath in
    print("将要显示单元格indexPath为:\(indexPath)")
    print("将要显示单元格cell为:\(cell)\n")
}).disposed(by: disposeBag)

二、RxDataSources

  • 如果 tableview 需要显示多个 section 或者更加复杂的编辑功能时,可以借助 RxDataSource 这个第三方库来帮我们完成。
  • RxDataSource 的本质就是使用 RxSwift 对 UITableView 和 UICollectionView 的数据源做了一层包装,使用它可以大大减少我们的工作量。

① 安装配置

  • CocoaPods:
pod 'RxDataSources', '~> 3.0'
  • Carthage
github "RxSwiftCommunity/RxDataSources" ~> 3.0
  • 手动安装:
    • RxDataSources 上将 RxDataSources 下载到本地,并引入到项目中来:

在这里插入图片描述

    • 在代码中将其 import 进来即可:
import RxDataSources

② 单分区的 TableView

  • 现在需要实现如下效果:

在这里插入图片描述

  • RxDataSources 是以 section 来做为数据结构的,所以不管 tableView 是单分区还是多分区,在使用 RxDataSources 的过程中,都需要返回一个 section 的数组。
  • 使用自带的 Section:
// 创建表格视图
self.tableView = UITableView(frame: self.view.frame, style:.plain)
// 创建一个重用的单元格
self.tableView!.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
self.view.addSubview(self.tableView!)
         
// 初始化数据
let items = Observable.just([
	SectionModel(model: "", items: [
                "UILable的用法",
                "UIText的用法",
                "UIButton的用法"
			])
		])
         
// 创建数据源
let dataSource = RxTableViewSectionedReloadDataSource
	<SectionModel<String, String>>(configureCell: {
    
    
			(dataSource, tv, indexPath, element) in
			let cell = tv.dequeueReusableCell(withIdentifier: "Cell")!
			cell.textLabel?.text = "\(indexPath.row)\(element)"
			return cell
	})
         
// 绑定单元格数据
items
	.bind(to: tableView.rx.items(dataSource: dataSource))
	.disposed(by: DisposeBag())
  • 使用自定义的 Section:
// 创建表格视图
self.tableView = UITableView(frame: self.view.frame, style:.plain)
// 创建一个重用的单元格
self.tableView!.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
self.view.addSubview(self.tableView!)
 
// 初始化数据
let sections = Observable.just([
    MySection(header: "", items: [
        "UILable的用法",
        "UIText的用法",
        "UIButton的用法"
        ])
    ])
 
// 创建数据源
let dataSource = RxTableViewSectionedAnimatedDataSource<MySection>(
    // 设置单元格
    configureCell: {
    
     ds, tv, ip, item in
        let cell = tv.dequeueReusableCell(withIdentifier: "Cell")
            ?? UITableViewCell(style: .default, reuseIdentifier: "Cell")
        cell.textLabel?.text = "\(ip.row)\(item)"
         
        return cell
})
 
// 绑定单元格数据
sections
    .bind(to: tableView.rx.items(dataSource: dataSource))
    .disposed(by: DisposeBag())

// 自定义Section
struct MySection {
    
    
    var header: String
    var items: [Item]
}
 
extension MySection : AnimatableSectionModelType {
    
    
    typealias Item = String
     
    var identity: String {
    
    
        return header
    }
     
    init(original: MySection, items: [Item]) {
    
    
        self = original
        self.items = items
    }
}

③ 多分区的 UITableView

  • 现在要实现如下效果:

在这里插入图片描述

  • 使用自带的 Section:
// 创建表格视图
self.tableView = UITableView(frame: self.view.frame, style:.plain)
// 创建一个重用的单元格
self.tableView!.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
self.view.addSubview(self.tableView!)
 
// 初始化数据
let items = Observable.just([
    SectionModel(model: "基本控件", items: [
        "UILable的用法",
        "UIText的用法",
        "UIButton的用法"
        ]),
    SectionModel(model: "高级控件", items: [
        "UITableView的用法",
        "UICollectionViews的用法"
        ])
    ])
 
// 创建数据源
let dataSource = RxTableViewSectionedReloadDataSource
    <SectionModel<String, String>>(configureCell: {
    
    
    (dataSource, tv, indexPath, element) in
    let cell = tv.dequeueReusableCell(withIdentifier: "Cell")!
    cell.textLabel?.text = "\(indexPath.row)\(element)"
    return cell
})
 
// 设置分区头标题
dataSource.titleForHeaderInSection = {
    
     ds, index in
    return ds.sectionModels[index].model
}
 
// 设置分区尾标题
//dataSource.titleForFooterInSection = { ds, index in
//    return "footer"
//}
 
// 绑定单元格数据
items
    .bind(to: tableView.rx.items(dataSource: dataSource))
    .disposed(by: disposeBag)
  • 使用自定义的 Section:
// 创建表格视图
self.tableView = UITableView(frame: self.view.frame, style:.plain)
// 创建一个重用的单元格
self.tableView!.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
self.view.addSubview(self.tableView!)
 
// 初始化数据
let sections = Observable.just([
    MySection(header: "基本控件", items: [
        "UILable的用法",
        "UIText的用法",
        "UIButton的用法"
        ]),
    MySection(header: "高级控件", items: [
        "UITableView的用法",
        "UICollectionViews的用法"
        ])
    ])
 
// 创建数据源
let dataSource = RxTableViewSectionedAnimatedDataSource<MySection>(
    // 设置单元格
    configureCell: {
    
     ds, tv, ip, item in
        let cell = tv.dequeueReusableCell(withIdentifier: "Cell")
            ?? UITableViewCell(style: .default, reuseIdentifier: "Cell")
        cell.textLabel?.text = "\(ip.row)\(item)"
         
        return cell
    },
    // 设置分区头标题
    titleForHeaderInSection: {
    
     ds, index in
        return ds.sectionModels[index].header
    }
    
// 自定义Section
struct MySection {
    
    
    var header: String
    var items: [Item]
}
 
extension MySection : AnimatableSectionModelType {
    
    
    typealias Item = String
     
    var identity: String {
    
    
        return header
    }
     
    init(original: MySection, items: [Item]) {
    
    
        self = original
        self.items = items
    }
}

三、刷新表格数据

  • 很多情况下,表格里的数据不是一开始就准备好的、或者固定不变的,可能需要先向服务器请求数据,再将获取到的内容显示在表格中。
  • 要重新加载表格数据,过去的做法就是调用 tableView 的 reloadData() 方法,那么在使用 RxSwift 的情况下,应该如何刷新表格的数据呢?

① 数据刷新

  • 如下所示:
    • 界面初始化完毕后,tableView 默认会加载一些随机数据;
    • 点击右上角的刷新按钮,tableView 会重新加载并显示一批新数据;
    • 为方便演示,每次获取数据不是真的去发起网络请求,而是在本地生成后延迟 2 秒返回,模拟这种异步请求的情况。

在这里插入图片描述

  • 示例代码:
// 创建表格视图
self.tableView = UITableView(frame: self.view.frame, style:.plain)
// 创建一个重用的单元格
self.tableView!.register(UITableViewCell.self,
                         forCellReuseIdentifier: "Cell")
self.view.addSubview(self.tableView!)
 
// 随机的表格数据
let randomResult = refreshButton.rx.tap.asObservable()
    .startWith(()) // 为了一开始就能自动请求一次数据
    .flatMapLatest(getRandomResult)
    .share(replay: 1)
 
// 创建数据源
let dataSource = RxTableViewSectionedReloadDataSource
    <SectionModel<String, Int>>(configureCell: {
    
    
        (dataSource, tv, indexPath, element) in
        let cell = tv.dequeueReusableCell(withIdentifier: "Cell")!
        cell.textLabel?.text = "条目\(indexPath.row)\(element)"
        return cell
    })
 
// 绑定单元格数据
randomResult
    .bind(to: tableView.rx.items(dataSource: dataSource))
    .disposed(by: disposeBag)

// 获取随机数据
func getRandomResult() -> Observable<[SectionModel<String, Int>]> {
    
    
	print("正在请求数据...")
	let items = (0 ..< 5).map {
    
    _ in
		Int(arc4random())
	}
	let observable = Observable.just([SectionModel(model: "S", items: items)])
	return observable.delay(2, scheduler: MainScheduler.instance)
}

② 防止表格多次刷新

  • flatMapLatest 的作用是当在短时间内(上一个请求还没回来)连续点击多次“刷新”按钮,虽然仍会发起多次请求,但表格只会接收并显示最后一次请求,避免表格出现连续刷新的现象:
// 随机的表格数据
let randomResult = refreshButton.rx.tap.asObservable()
    .startWith(()) // 为了一开始就能自动请求一次数据
    .flatMapLatest(getRandomResult)
    .share(replay: 1)
  • 也可以对源头进行限制,即通过 throttle 设置个阀值(比如 1 秒),如果在1秒内有多次点击则只取最后一次,那么自然也就只发送一次数据请求:
// 随机的表格数据
let randomResult = refreshButton.rx.tap.asObservable()
    .throttle(1, scheduler: MainScheduler.instance) // 在主线程中操作,1秒内值若多次改变,取最后一次
    .startWith(()) // 为了一开始就能自动请求一次数据
    .flatMapLatest(getRandomResult)
    .share(replay: 1)

③ 停止数据请求

  • 在实际项目中我们可能会需要对一个未完成的网络请求进行中断操作,比如切换页面或者分类时,如果上一次的请求还未完成就要将其取消掉。那么 RxSwift 如何实现该功能呢?
  • 该功能简单说就是通过 takeUntil 操作符实现,当 takeUntil 中的 Observable 发送一个值时,便会结束对应的 Observable:
// 创建表格视图
self.tableView = UITableView(frame: self.view.frame, style:.plain)
// 创建一个重用的单元格
self.tableView!.register(UITableViewCell.self,
                         forCellReuseIdentifier: "Cell")
self.view.addSubview(self.tableView!)
 
// 随机的表格数据
let randomResult = refreshButton.rx.tap.asObservable()
    .startWith(()) // 为一开始就能自动请求一次数据
    .flatMapLatest{
    
    
        self.getRandomResult().takeUntil(self.cancelButton.rx.tap)
    }
    .share(replay: 1)
 
// 创建数据源
let dataSource = RxTableViewSectionedReloadDataSource
    <SectionModel<String, Int>>(configureCell: {
    
    
        (dataSource, tv, indexPath, element) in
        let cell = tv.dequeueReusableCell(withIdentifier: "Cell")!
        cell.textLabel?.text = "条目\(indexPath.row)\(element)"
        return cell
    })
 
// 绑定单元格数据
randomResult
    .bind(to: tableView.rx.items(dataSource: dataSource))
    .disposed(by: disposeBag)


// 获取随机数据
func getRandomResult() -> Observable<[SectionModel<String, Int>]> {
    
    
    print("正在请求数据......")
    let items = (0 ..< 5).map {
    
    _ in
        Int(arc4random())
    }
    let observable = Observable.just([SectionModel(model: "S", items: items)])
    return observable.delay(2, scheduler: MainScheduler.instance)
}

四、表格数据的搜索过滤

  • 在 tableView 的表头上增加了一个搜索框,tableView 会根据搜索框里输入的内容实时地筛选并显示出符合条件的数据(包含有输入文字的数据条目)。
  • 这个实时搜索是对已获取到的数据进行过滤,即每次输入文字时不会重新发起请求。

在这里插入图片描述
在这里插入图片描述

  • 示例代码:
// 创建表格视图
self.tableView = UITableView(frame: self.view.frame, style:.plain)
// 创建一个重用的单元格
self.tableView!.register(UITableViewCell.self,
                         forCellReuseIdentifier: "Cell")
self.view.addSubview(self.tableView!)

// 创建表头的搜索栏
self.searchBar = UISearchBar(frame: CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: 56))
self.tableView.tableHeaderView = self.searchBar
 
// 随机的表格数据
let randomResult = refreshButton.rx.tap.asObservable()
    .startWith(()) // 为一开始就能自动请求一次数据
    .flatMapLatest(getRandomResult) // 获取数据
    .flatMap(filterResult) // 筛选数据
    .share(replay: 1)
 
// 创建数据源
let dataSource = RxTableViewSectionedReloadDataSource
    <SectionModel<String, Int>>(configureCell: {
    
    
        (dataSource, tv, indexPath, element) in
        let cell = tv.dequeueReusableCell(withIdentifier: "Cell")!
        cell.textLabel?.text = "条目\(indexPath.row)\(element)"
        return cell
    })
 
// 绑定单元格数据
randomResult
    .bind(to: tableView.rx.items(dataSource: dataSource))
    .disposed(by: disposeBag)


// 获取随机数据
func getRandomResult() -> Observable<[SectionModel<String, Int>]> {
    
    
    print("正在请求数据...")
    let items = (0 ..< 5).map {
    
    _ in
        Int(arc4random())
    }
    let observable = Observable.just([SectionModel(model: "S", items: items)])
    return observable.delay(2, scheduler: MainScheduler.instance)
}

// 过滤数据
func filterResult(data:[SectionModel<String, Int>]) -> Observable<[SectionModel<String, Int>]> {
    
    
    return self.searchBar.rx.text.orEmpty
        //.debounce(0.5, scheduler: MainScheduler.instance) //只有间隔超过0.5秒才发送
        .flatMapLatest{
    
    
            query -> Observable<[SectionModel<String, Int>]> in
            print("正在筛选数据(条件为:\(query))")
            // 输入条件为空,则直接返回原始数据
            if query.isEmpty{
    
    
                return Observable.just(data)
            }
            // 输入条件为不空,则只返回包含有该文字的数据
            else{
    
    
                var newData:[SectionModel<String, Int>] = []
                for sectionModel in data {
    
    
                    let items = sectionModel.items.filter{
    
     "\($0)".contains(query) }
                    newData.append(SectionModel(model: sectionModel.model, items: items))
                }
                return Observable.just(newData)
            }
    }
}

五、可编辑表格

① 实现效果

  • 程序启动后表格会自动加载 5 条随机数据,点击“刷新”按钮则又重新生成 5 条数据并显示;
  • 点击“加号”图标后,会在当前数据集的末尾添加一条随机数据并显示;
  • 点击单元格左侧的“减号”图标则可以将该行数据删除;
  • 拖动单元格右侧的控制图标可以改变显示顺序。

在这里插入图片描述
在这里插入图片描述

② 示例代码

  • 由于编辑操作比较多,使用 enum 创建一个命令枚举,里面定义了对 tableView 数据的各种操作:
// 定义各种操作命令
enum TableEditingCommand {
    
    
    case setItems(items: [String])  			  // 设置表格数据
    case addItem(item: String)  				  // 新增数据
    case moveItem(from: IndexPath, to: IndexPath) // 移动数据
    case deleteItem(IndexPath) 					  // 删除数据
}
  • 接着定义 tableView 对应的 ViewModel,这里面除了保存有表格数据外,还包含上面定义的 4 个操作命令的具体实现:
// 定义表格对应的ViewModel
struct TableViewModel {
    
    
    // 表格数据项
    fileprivate var items:[String]
      
    init(items: [String] = []) {
    
    
        self.items = items
    }
      
    // 执行相应的命令,并返回最终的结果
    func execute(command: TableEditingCommand) -> TableViewModel {
    
    
        switch command {
    
    
        case .setItems(let items):
            print("设置表格数据。")
            return TableViewModel(items: items)
        case .addItem(let item):
            print("新增数据项。")
            var items = self.items
            items.append(item)
            return TableViewModel(items: items)
        case .moveItem(let from, let to):
            print("移动数据项。")
            var items = self.items
            items.insert(items.remove(at: from.row), at: to.row)
            return TableViewModel(items: items)
        case .deleteItem(let indexPath):
            print("删除数据项。")
            var items = self.items
            items.remove(at: indexPath.row)
            return TableViewModel(items: items)
        }
    }
}
  • 主视图控制器代码:
class ViewController: UIViewController {
    
    
     
    // 刷新按钮
    @IBOutlet weak var refreshButton: UIBarButtonItem!
    // 新增按钮
    @IBOutlet weak var addButton: UIBarButtonItem!
    // 表格
    var tableView:UITableView!
     
    let disposeBag = DisposeBag()
     
    override func viewDidLoad() {
    
    
        super.viewDidLoad()
         
        // 创建表格视图
        self.tableView = UITableView(frame: self.view.frame, style:.plain)
        // 创建一个重用的单元格
        self.tableView!.register(UITableViewCell.self,
                                 forCellReuseIdentifier: "Cell")
        self.view.addSubview(self.tableView!)
         
        // 表格模型
        let initialVM = TableViewModel()
         
        // 刷新数据命令
        let refreshCommand = refreshButton.rx.tap.asObservable()
            .startWith(())
            .flatMapLatest(getRandomResult)
            .map(TableEditingCommand.setItems)
         
        // 新增条目命令
        let addCommand = addButton.rx.tap.asObservable()
            .map{
    
     "\(arc4random())" }
            .map(TableEditingCommand.addItem)
         
        // 移动位置命令
        let movedCommand = tableView.rx.itemMoved
            .map(TableEditingCommand.moveItem)
         
        // 删除条目命令
        let deleteCommand = tableView.rx.itemDeleted.asObservable()
            .map(TableEditingCommand.deleteItem)
         
        // 绑定单元格数据
        Observable.of(refreshCommand, addCommand, movedCommand, deleteCommand)
            .merge()
            .scan(initialVM) {
    
     (vm: TableViewModel, command: TableEditingCommand)
                -> TableViewModel in
                return vm.execute(command: command)
            }
            .startWith(initialVM)
            .map {
    
    
                [AnimatableSectionModel(model: "", items: $0.items)]
            }
            .share(replay: 1)
            .bind(to: tableView.rx.items(dataSource: ViewController.dataSource()))
            .disposed(by: disposeBag)
    }
     
    override func viewDidAppear(_ animated: Bool) {
    
    
        super.viewDidAppear(animated)
        tableView.setEditing(true, animated: true)
    }
     
    // 获取随机数据
    func getRandomResult() -> Observable<[String]> {
    
    
        print("生成随机数据。")
        let items = (0 ..< 5).map {
    
    _ in
            "\(arc4random())"
        }
        return Observable.just(items)
    }
}
 
extension ViewController {
    
    
    // 创建表格数据源
    static func dataSource() -> RxTableViewSectionedAnimatedDataSource
        <AnimatableSectionModel<String, String>> {
    
    
        return RxTableViewSectionedAnimatedDataSource(
            // 设置插入、删除、移动单元格的动画效果
            animationConfiguration: AnimationConfiguration(insertAnimation: .top,
                                                           reloadAnimation: .fade,
                                                           deleteAnimation: .left),
            configureCell: {
    
    
                (dataSource, tv, indexPath, element) in
                let cell = tv.dequeueReusableCell(withIdentifier: "Cell")!
                cell.textLabel?.text = "条目\(indexPath.row)\(element)"
                return cell
        },
            canEditRowAtIndexPath: {
    
     _, _ in
                return true // 单元格可删除
        },
            canMoveRowAtIndexPath: {
    
     _, _ in
                return true // 单元格可移动
        }
        )
    }
}

六、不同类型的单元格混用

① 效果展示

  • tableView 绑定的数据源中一共有 2 个 section,每个 section 里分别有 3 条数据需要显示。
  • 每个 cell 会根据数据类型的不同,自动选择相应的显示方式:“文字+图片”或“文字+开关按钮”。

在这里插入图片描述

② 示例代码

// 初始化数据
let sections = Observable.just([
   MySection(header: "我是第一个分区", items: [
       .TitleImageSectionItem(title: "图片数据1", image: UIImage(named: "php")!),
       .TitleImageSectionItem(title: "图片数据2", image: UIImage(named: "react")!),
       .TitleSwitchSectionItem(title: "开关数据1", enabled: true)
       ]),
   MySection(header: "我是第二个分区", items: [
       .TitleSwitchSectionItem(title: "开关数据2", enabled: false),
       .TitleSwitchSectionItem(title: "开关数据3", enabled: false),
       .TitleImageSectionItem(title: "图片数据3", image: UIImage(named: "swift")!)
       ])
   ])

// 创建数据源
let dataSource = RxTableViewSectionedReloadDataSource<MySection>(
   // 设置单元格
   configureCell: {
    
     dataSource, tableView, indexPath, item in
       switch dataSource[indexPath] {
    
    
       case let .TitleImageSectionItem(title, image):
           let cell = tableView.dequeueReusableCell(withIdentifier: "titleImageCell",
                                                    for: indexPath)
           (cell.viewWithTag(1) as! UILabel).text = title
           (cell.viewWithTag(2) as! UIImageView).image = image
           return cell
            
       case let .TitleSwitchSectionItem(title, enabled):
           let cell = tableView.dequeueReusableCell(withIdentifier: "titleSwitchCell",
                                                    for: indexPath)
           (cell.viewWithTag(1) as! UILabel).text = title
           (cell.viewWithTag(2) as! UISwitch).isOn = enabled
           return cell
       }
   },
   // 设置分区头标题
   titleForHeaderInSection: {
    
     ds, index in
       return ds.sectionModels[index].header
   }
)

// 绑定单元格数据
sections
   .bind(to: tableView.rx.items(dataSource: dataSource))
   .disposed(by: disposeBag)


// 单元格类型
enum SectionItem {
    
    
    case TitleImageSectionItem(title: String, image: UIImage)
    case TitleSwitchSectionItem(title: String, enabled: Bool)
}
 
// 自定义Section
struct MySection {
    
    
    var header: String
    var items: [SectionItem]
}
 
extension MySection : SectionModelType {
    
    
    typealias Item = SectionItem
     
    init(original: MySection, items: [Item]) {
    
    
        self = original
        self.items = items
    }
}

七、UITableView 相关样式的修改

① 修改单元格高度

在这里插入图片描述

  • 示例代码:
var tableView:UITableView!
var dataSource:RxTableViewSectionedAnimatedDataSource<MySection>?
let disposeBag = DisposeBag()


// 创建表格视图
self.tableView = UITableView(frame: self.view.frame, style:.plain)
// 创建一个重用的单元格
self.tableView!.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
self.view.addSubview(self.tableView!)

// 初始化数据
let sections = Observable.just([
   MySection(header: "基本控件", items: [
       "UILable的用法",
       "UIText的用法",
       "UIButton的用法"
       ]),
   MySection(header: "高级控件", items: [
       "UITableView的用法",
       "UICollectionViews的用法"
       ])
   ])

// 创建数据源
let dataSource = RxTableViewSectionedAnimatedDataSource<MySection>(
   // 设置单元格
   configureCell: {
    
     ds, tv, ip, item in
       let cell = tv.dequeueReusableCell(withIdentifier: "Cell")
           ?? UITableViewCell(style: .default, reuseIdentifier: "Cell")
       cell.textLabel?.text = "\(ip.row)\(item)"
        
       return cell
   },
   // 设置分区头标题
   titleForHeaderInSection: {
    
     ds, index in
       return ds.sectionModels[index].header
   }
)

self.dataSource = dataSource

// 绑定单元格数据
sections
   .bind(to: tableView.rx.items(dataSource: dataSource))
   .disposed(by: disposeBag)

// 设置代理
tableView.rx.setDelegate(self)
   .disposed(by: disposeBag)


// tableView代理实现
extension MainViewController : UITableViewDelegate {
    
    
    // 设置单元格高度
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath)
        -> CGFloat {
    
    
        guard let _ = dataSource?[indexPath],
            let _ = dataSource?[indexPath.section]
            else {
    
    
                return 0.0
        }
     
        return 60
    }
}
 
// 自定义Section
struct MySection {
    
    
    var header: String
    var items: [Item]
}
 
extension MySection : AnimatableSectionModelType {
    
    
    typealias Item = String
     
    var identity: String {
    
    
        return header
    }
     
    init(original: MySection, items: [Item]) {
    
    
        self = original
        self.items = items
    }
}

② 修改分组的头部和尾部

在这里插入图片描述

  • 修改代理实现,示例代码:
// tableView代理实现
extension MainViewController : UITableViewDelegate {
    
    
    // 返回分区头部视图
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int)
        -> UIView? {
    
    
        let headerView = UIView()
        headerView.backgroundColor = UIColor.black
        let titleLabel = UILabel()
        titleLabel.text = self.dataSource?[section].header
        titleLabel.textColor = UIColor.white
        titleLabel.sizeToFit()
        titleLabel.center = CGPoint(x: self.view.frame.width/2, y: 20)
        headerView.addSubview(titleLabel)
        return headerView
    }
     
    // 返回分区头部高度
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int)
        -> CGFloat {
    
    
        return 40
    }
}
 
// 自定义Section
struct MySection {
    
    
    var header: String
    var items: [Item]
}
 
extension MySection : AnimatableSectionModelType {
    
    
    typealias Item = String
     
    var identity: String {
    
    
        return header
    }
     
    init(original: MySection, items: [Item]) {
    
    
        self = original
        self.items = items
    }
}

猜你喜欢

转载自blog.csdn.net/Forever_wj/article/details/121131272