原在公司git上, 因为与其他公司存在交接, 为了方便交流, 迁移一部分到Github
结构
PTBaseKit/
|
+- Application/
| |
| +- AppDelegate.swift <-- 执行了一个配置
|
+- Source/ <-- 源码
| | |
| +- UI <-- View和ViewController轮子
| | |
| | +- List <-- 列表封装(TableView)
| | |
| | +- Base <-- ViewController基类, View构造工厂
| | |
| | +- Web <-- WKWebView的Hybrid界面
| | .
| | .
| | .
| +- Foundation <-- 基础数据处理轮子
| |
| +- Map <-- 地图业务协议
| | .
| | .
| | .
|
+- Example/ <-- 用例
| |
| +- Views <-- demo用的UI组件
| |
| +- Map <-- 地图业务协议具体实现
| |
| +- Controllers <-- demo页面
CssKit
一般给UILabel赋值的做法:
let customLabel: UILabel = UILabel() label.font = 13.customFont label.textColor = UIColor.tk.main label.text = "custom label"
使用CssKit给UILabel赋值的做法:
let customLabel: UILabel = UILabel() + 13.customFont.css + UIColor.tk.main.textColorCss + "custom label".css
TableController
TableController是针对列表功能抽象出来的协议, 与之关联的还有TableCell, TableSectionViewModel和TableCellViewModel协议. 整体上是一个MVVM的设计, 把TableCell的适配从Controller分离出来, TableController专注于Table的加载动作, TableCellViewModel用于中转Model->Cell的数据以及持有Cell的一些交互回调, 而TableCell则提供更新函数.
CommonTableController
thinker vc开发项目中的列表页面几乎都是CommonTableController类, 它遵守TableController协议. 通过TableView和RJRefresh组合成基本的上下拉加载逻辑, 而内部处理和UITableViewCell有关的操作都是面向TableCell以及TableCellViewModel协议的, 所以它与TableViewCell的实现以及适配不存在耦合.
- 使用者需要创建一个列表界面的时候可以直接使用
CommonTableController, 它提供的接口可以应付大部分使用场景. 这样有利于避免创建多个ViewController带来的维护高成本和低代码复用. - 专注
SectionViewModel/TableCellViewModel的产生和变化, 它们以数组的形式传入CommonTableController, 根据数组中元素顺序的不同,CommonTableController的显示内容就会有相应变化. - 它的 数据加载逻辑/组件加载逻辑/组件刷新逻辑 都经过了thinker(其实就是本人)验证.
- 提供简单的配置函数来给使用者对页面的顶部和底部添加UI组件, 以及数据为空时候的UI提示.
UIKitTableController.swift文件中有一个调用CommonTableController的例子:
CommonTableController() .setupTableView(with: .sepratorStyle(.singleLine)) .performWhenReload { (_table) in // 进入global队列获取数据, 并回到主线程结束刷新动画, 更新列表 DispatchQueue.global() .rx_async(with: ()) .flatMap { Observable.just(fakeFetchData()) } .flatMap { DispatchQueue.main.rx_async(with: $0) } .subscribe(onNext: { _table.reload(withSectionViewModels: $0) }) .disposed(by: _table) } .performWhenLoadMore { (_table) in DispatchQueue.global() .rx_async(with: ()) .flatMap { Observable.just(fakeFetchData()) } .flatMap { DispatchQueue.main.rx_async(with: $0) } .subscribe(onNext: { _table.loadMore(withSectionViewModels: $0, isLast: true) }) .disposed(by: _table) }
其中performWhenReload和performWhenLoadMore分别用于传入加载操作, 需要调用者自己结束加载. 回调闭包中有一个参数正是CommonTableController本身.例子中使用了RxSwift三方框架配合展示了这个加载过程, fakeFetchData替代了项目中的网络请求以及Model->ViewModel的操作, 实际上thinker的项目中就是按照这个方式实现, 实现这一步操作的是各个业务模块对应的Service.
CommonTableCell
CommonTableCell是在TableController协议基础上实现的一个UITableViewCell子类, 根据thinker项目总结而来.
- 它已经几乎兼容thinker目前所有列表的显示需要, 订单, 用户信息, 活动, 设置, 消息等.
- 只需要传入不同的参数来创建ViewModel, 并传入
CommonTableController, 就可以得到各种自适应高度的界面, 这个高度是其ViewModel初始化的时候实现的, 并不需要使用者自己计算. - 使用了比较好理解的fram计算来实现layout. 这样除了提供不错的滑动性能, 也让使用者根据项目变更的情况较快修改, 比起ASDK这种滑动性能极佳可是又难上手的框架, 或者直接使用
SnapKit来牺牲性能要来得好.
为了遵守高内聚低耦合, 在使用的时候应该把Model->ViewModel这一步操作抽出, 不要添加init函数到CommonTableCell的ViewModel代码中, 用例里面由于没有业务Model, 只是写了一个单独函数产生ViewModel:
private func createCellViewModels() -> [TableCellViewModel] { return (0...Int(arc4random_uniform(4))) .map { index -> TableCellViewModel in let imageIndex = Int(arc4random_uniform(4)) // let cellHiehgt = (images[imageIndex]?.size.height ?? 45) + 20 var viewModel = CommonTableCellViewModel(head: images[imageIndex], title: cellTitles[Int(arc4random_uniform(4))].appending(subTitles[Int(arc4random_uniform(4))]), tail: genButtonContentOptions(), accessorable: index%2 == 1, boundsOption: .fitsToWidth(kScreenWidth)) // .constant(CGSize(width: kScreenWidth, height: cellHiehgt)) viewModel.tailClicked = {_ = UIApplication.shared.keyWindow?.rootViewController?.presentAlert(title: "cell tail button clicked", message: "", force: true)} viewModel.performWhenSelect = {(table, indexpath) in table.deselectRow(at: indexpath, animated: true)} return viewModel } }
实际操作可以是这样:
class SomeModel { ... ... ... } extension SomeModel { func toCommonTableCellViewModel() -> CommonTableCellViewModel { // 实行self -> CommonTableCellViewModel的转换 let head = ... let title = ... let tail = ... let background = ... let accessorable = ... let boundsOption = ... return CommonTableCellViewModel(head: head, title: title, tail: tail, backgroundCss: background, accessorable: accessorable, boundsOption: boundsOption) } }
并且每个Model对ViewModel的转换, 最好都是在子线程中完成, 就像用例里的一样.