首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

iOS 基础系统组件UITableViewController框架搭建设计与实现探究

  • 25-04-19 00:08
  • 4620
  • 10922
juejin.cn

2025.01.21 工作变动原因,故将一些工作期间Tapd内部写的Wiki文档转移到个人博客。

在设计base collection类的时候,顺便设计了一份base table,项目里已经有一份已经在用的base table框架了,考虑到取长补短的原因,在这里还是做一次技术设计与实现。 一次iOS组内分享会中分享自己的想法后,在这里把设计思路和源码放上来以作记录。

因为是一边学习Swift一边写的,有一些语法问题上还可以再做一步优化(比如,把一些逻辑! 强制解析 as! 类型改成可选型 as? 防止崩溃,不过使用 as! 也能更好帮助开发阶段调试定位问题),先在这里做大体框架的源码设计与实现分享。

这份一是基于 《阅读类APP》 的demo

一、设计思想与实际作用

1.统一代码开发规范,命名方式

首先,因为每个程序员思考方式不同,想法不同,写出来的代码、文件命名等等,自然会有不一样的地方。如果有一套统一的、能覆盖大多数场景的开发模板(base组件),将会大大减少新员工或者相同开发人员交替开发的阅读成本、开发成本。

2.提高开发效率

有一套设计良好的基础组件模板,可以减少开发中一些基础 UITableViewController 功能的重复代码编写,也可以迅速的复用相似的UI层到相同的新页面中。

3.使代码更加内聚

如果能够把 UITableViewController 常用的功能封装后,一个构造函数完成以前需要好几个代理方法才能写完的功能,这样一个构造函数,对于开发、维护以及更新页面的时候就会变得特别方便。 既能把处理逻辑放到一起,也能让代码顺序对应页面元素顺序,大大提高了代码的可阅读性、可维护性、开发效率。

二、框架搭建与源码实现

base TableViewController 组件 YYYBaseTableViewController

swift
代码解读
复制代码
// Created by Yimmm on 2022/6/2. // Copyright © 2022 xj. All rights reserved. // baseCollectionViewController组件 import UIKit @objc protocol YYYTableViewDelegate: AnyObject { /** didSelect回调 tableViewModel: 数据源TableViewModel didSelectCellModel: 点击Cell的CellModel */ @objc optional func tableViewModel(_ tableViewModel: YYYBaseTableViewModel, didSelectCellModel: YYYBaseTableViewCellModel,didSelectRowAt indexPath: IndexPath) /** heightForRow回调 tableViewModel: 数据源TableViewModel cellModel: 当前Cell的CellModel 返回当前cell的行高(可自定义处理动态cell的高度) */ @objc optional func tableViewModel(_ tableViewModel: YYYBaseTableViewModel, cellModel: YYYBaseTableViewCellModel, heightForRowAt indexPath: IndexPath) -> CGFloat /** viewForHeaderInSection回调 tableViewModel: 数据源TableViewModel sectionModel: 当前secion的sectionModel 返回section中的HeaderView */ @objc optional func tableViewModel(_ tableViewModel: YYYBaseTableViewModel, sectionModel: YYYBaseTableViewSecionModel, viewForHeaderInSection section: Int) -> UIView? /** heightForHeaderInSection回调 tableViewModel: 数据源TableViewModel sectionModel: 当前secion的sectionModel 返回SectionHeader的高度 */ @objc optional func tableViewModel(_ tableViewModel: YYYBaseTableViewModel, sectionModel: YYYBaseTableViewSecionModel, heightForHeaderInSection section: Int) -> CGFloat /** 点击内TableViewCell里按钮的回调 cellModel: 点击Cell的CellModel */ @objc optional func clickTableViewCellInsideButton(_ tableViewModel: YYYBaseTableViewModel, cellModel: YYYBaseTableViewCellModel, senderTag: Int) /** 点击tableViewCell内CollectionViewCell回调 collectionViewModel: 数据源collectionViewModel didSelectCellModel: 点击Cell的CellModel */ @objc optional func clickInsideCollectionViewCell(_ collectionViewModel: YYYBaseCollectionViewModel, didSelectCellModel cellModel: YYYBaseCollectionViewCellModel, didSelectItemAt indexPath: IndexPath) } class YYYBaseTableViewController: UITableViewController, YYYTableViewCellDelegate { /// 数据源viewModel var viewModel: YYYBaseTableViewModel? { didSet { // FIXME: - 业 注册重用 // 注册cell,我也不知道这种方式好不好,待测试(或者可以像TableViewable一样给一个注册方式给VC) for sectionModel in viewModel!.sectionModels { for cellModel in sectionModel.cellModels { tableView.register(cellClassFromString(cellModel.cellClass).self, forCellReuseIdentifier: "\(cellModel.cellClass!)") } } tableView.reloadData() } } weak var delegate: YYYTableViewDelegate? override func viewDidLoad() { super.viewDidLoad() setupTableView() } private func setupTableView() { tableView.dataSource = self tableView.delegate = self tableView.backgroundColor = .background_light tableView.separatorStyle = .none tableView.estimatedRowHeight = 0 tableView.sectionFooterHeight = 0 tableView.sectionHeaderHeight = 0 tableView.estimatedSectionHeaderHeight = 0 tableView.estimatedSectionFooterHeight = 0 tableView.showsVerticalScrollIndicator = false tableView.showsHorizontalScrollIndicator = false if #available(iOS 15.0, *) { tableView.sectionHeaderTopPadding = 0 } } // MARK: - Private Method /// 字符串转类 func cellClassFromString(_ className:String) -> AnyClass { // 1、获swift中的命名空间名 var name = Bundle.main.object(forInfoDictionaryKey: "CFBundleExecutable") as? String // 2、如果包名中有'-'横线这样的字符,在拿到包名后,还需要把包名的'-'转换成'_'下横线 name = name?.replacingOccurrences(of: "-", with: "_") // 3、拼接命名空间和类名,”包名.类名“ let fullClassName = name! + "." + className // 4、如果取不到,给个默认值 let classType: AnyClass = NSClassFromString(fullClassName) ?? YYYTableViewCell.self // 本类type return classType } // MARK: - Table view data source override func numberOfSections(in tableView: UITableView) -> Int { let sections = viewModel?.sectionModels.count ?? 0 return sections } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { let rows = viewModel?.sectionModels[section].cellModels.count ?? 0 return rows } override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let height: CGFloat = self.delegate?.tableViewModel?(viewModel!, cellModel: viewModel!.sectionModels[indexPath.section].cellModels[indexPath.row], heightForRowAt: indexPath) ?? 0 if height != 0 { return height } return viewModel!.sectionModels[indexPath.section].cellModels[indexPath.row].cellHeight } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let str = viewModel!.sectionModels[indexPath.section].cellModels[indexPath.row].cellClass! // 得到AnyClass let anyClass: AnyClass = cellClassFromString(str) // 强转自己需要的类 let classType = anyClass as! YYYTableViewCell.Type // 使用类方法 .cell来得到一个实例对象 let cell = classType.cell(tableView) cell.cellModel = viewModel!.sectionModels[indexPath.section].cellModels[indexPath.row] cell.cellDelagte = self return cell } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { self.delegate?.tableViewModel?(viewModel!, didSelectCellModel: viewModel!.sectionModels[indexPath.section].cellModels[indexPath.row], didSelectRowAt: indexPath) } override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let haeder = self.delegate?.tableViewModel?(viewModel!, sectionModel: viewModel!.sectionModels[section], viewForHeaderInSection: section) ?? nil return haeder } override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { let height = self.delegate?.tableViewModel?(viewModel!, sectionModel: viewModel!.sectionModels[section], heightForHeaderInSection: section) ?? 0 return height } // MARK: - YYYTableViewCell Delegate func clickInsideCollectionViewCell(_ collectionViewModel: YYYBaseCollectionViewModel, didSelectCellModel cellModel: YYYBaseCollectionViewCellModel, didSelectItemAt indexPath: IndexPath) { self.delegate?.clickInsideCollectionViewCell?(collectionViewModel, didSelectCellModel: cellModel, didSelectItemAt: indexPath) } func clickInsideButton(_ cellModel: YYYBaseTableViewCellModel, senderTag: Int) { self.delegate?.clickTableViewCellInsideButton?(viewModel!, cellModel: cellModel, senderTag: senderTag) } }

base TableViewModel 组件 YYYBaseTableViewModel

kotlin
代码解读
复制代码
// baseTableViewModel组件 import UIKit class YYYBaseTableViewModel: NSObject { // inout修饰参数 传地址而不是值 /// 构建函数参数闭包 public typealias sectionModelsClosure = (inout [YYYBaseTableViewSecionModel]) -> Void // 数据源 var sectionModels = [YYYBaseTableViewSecionModel]() init(sectionModelsClosure: sectionModelsClosure) { sectionModelsClosure(§ionModels) } } class YYYBaseTableViewSecionModel: NSObject { /// 构建函数参数闭包 public typealias cellModelsClosure = (inout [YYYBaseTableViewCellModel]) -> Void /// 数据源 var cellModels = [YYYBaseTableViewCellModel]() init(cellModelsClosure: cellModelsClosure) { cellModelsClosure(&cellModels) } } class YYYBaseTableViewCellModel: NSObject { /// Cell类名 var cellClass: String! /// Cell高度 var cellHeight: CGFloat! /// Cell里的collectionViewModel var collectionViewModel: [AnyObject]! /// 标题 var fieldTitle: String! /// 副标题 var fieldSubTitle: String! /// 图片名 var imageName: String! /// 图片URL // var imageURL: UIImage! /// 辅助字段 var others: [String]! /// 是否只可读 var isReadOnly: Bool! /// 辅助属性(存储数据源 - 回调用) var modelValue: AnyObject! override init() { super.init() } } // MARK: - 可扩展的 TabelCellModel // 书本详情cellModel class YYYBaseTableViewInfoCellModel: YYYBaseTableViewCellModel{ }

base TableViewCell 组件 YYYTableViewCell

swift
代码解读
复制代码
// 基础TableViewCell import UIKit @objc protocol YYYTableViewCellDelegate: AnyObject { /** 点击内CollectionViewCell回调 collectionViewModel: 数据源collectionViewModel didSelectCellModel: 点击Cell的CellModel */ @objc optional func clickInsideCollectionViewCell(_ collectionViewModel: YYYBaseCollectionViewModel, didSelectCellModel: YYYBaseCollectionViewCellModel, didSelectItemAt indexPath: IndexPath) /** 点击内TableViewCell里按钮的回调 cellModel: 点击Cell的CellModel */ @objc optional func clickInsideButton(_ cellModel: YYYBaseTableViewCellModel, senderTag: Int) } class YYYTableViewCell: UITableViewCell { /// 数据源cellModel var cellModel: YYYBaseTableViewCellModel! { didSet { setupUI() setCollectionCellModel() } } weak var cellDelagte: YYYTableViewCellDelegate? override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) setup() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } // MARK: - setup func setupUI() { // override } /// 构建内嵌CollectionView的cellModel func setCollectionCellModel() { // 因为这是个阅读类APP,所以会有大量的tableViewCell嵌套collectionViewCell,这里直接提供一个构建方法 // override } class func cell(_ tableView: UITableView) -> YYYTableViewCell { return tableView.dequeueReusableCell(withIdentifier: "\(self)") as! YYYTableViewCell } // MARK: - Private Method /// 创建cell内 UIButton 通用方法 func newButton() -> UIButton { let button = UIButton(type: .custom) button.addTarget(self, action: #selector(clickNewButton), for: .touchUpInside) addSubview(button) return button } @objc func clickNewButton(sender: UIButton) { self.cellDelagte?.clickInsideButton?(cellModel, senderTag: sender.tag) } }

三、具体使用

1.tableViewController,table具体参数由业务决定:

swift
代码解读
复制代码
lazy var tableViewController: YYYBaseTableViewController

2.YYYBaseTableViewModel,并传值给tableViewController.viewModel:

ini
代码解读
复制代码
// 构造tableViewModel func updateTableView() { let viewModel = YYYBaseTableViewModel { sectionModels in for data in dataSource { let sectionModel = YYYBaseTableViewSecionModel { cellModels in if data.title.contains("Best") { let cellModel1 = YYYBaseTableViewCellModel() cellModel1.cellClass = "YYYThreeByThreeGridsStyle1TableCell" cellModel1.fieldTitle = data.title cellModel1.cellHeight = scrW(623.5) cellModel1.imageName = "hometop_bestbg" cellModel1.collectionViewModel = data.bookList cellModels.append(cellModel1) } else { let cellModel1 = YYYBaseTableViewCellModel() cellModel1.cellClass = "YYYOneRowSlidableStyle1TableCell" cellModel1.cellHeight = scrW(190) cellModel1.collectionViewModel = data.bookList cellModels.append(cellModel1) } } sectionModels.append(sectionModel) } } tableViewController.viewModel = viewModel }

3.实现业务需要的一些通用delegate

swift
代码解读
复制代码
// MARK: - YYYTableViewDelegate // 点击里侧嵌套CollectionCell的通用回调,有需要把YYYBaseCollectionViewCellModel替换成自己自定义XXXCellModel的就好了 func clickInsideCollectionViewCell(_ collectionViewModel: YYYBaseCollectionViewModel, didSelectCellModel cellModel: YYYBaseCollectionViewCellModel, didSelectItemAt indexPath: IndexPath) { // 书籍cell样式1 if cellModel.cellClass == "YYYBookCoverStyle1CollectionViewCell" { // 点击跳转 书籍详情页 let bookInfo = BookInfoViewController() // 构造函数的 modelValue: AnyObject来存储数据源属性,到回调里强转使用。 let book = cellModel.modelValue as? HomeTop ?? HomeTop() bookInfo.bookID = book.topId jumpvc(viewController: bookInfo) } } // 测试cell中按钮点击情况 func clickTableViewCellInsideButton(_ tableViewModel: YYYBaseTableViewModel, cellModel: YYYBaseTableViewCellModel, senderTag: Int) { print("AKAK") }

四、优点和缺点

优点一、代码规范

规范常用代码写法,大部份业务都是相似的代码结构,接手后维护成本更低,也能避免不同水平程序员写出来不同风格的代码,可阅读性更好。

优点二、内聚性

使构建常见的table时,代码更加内聚,构建新代码时间成本更低,维护旧代码可一目了然。

优点三、统一cellModel属性,业务model不会渗透通用cell和controller代码,cellModel在回调中拿来即用

构建常见的table时,后台无论怎么变化返回的数据结构,共用tableCell都是用同一个通用的cellModel属性,不会造成引入业务model而渗透cell和controller回调的代码。同时写新UI样式时更方便,属性更统一,开发相似样式cell的时候也更容易做出更改。

缺点一、增加学习成本

熟悉框架(引用,构建ViewModel)时,因为和平常大家熟知的(继承,实现代理)写代码方式不一样,所以会有一定的学习成本。

缺点二、应对动态约束布局的UI需要自己手动刷新

比如无法用 estimatedRowHeight 与 约束布局 实现动态布局列表,框架多用于快速构建实现常见的静态布局。当然,写复杂场景时就不需要引用 lazy var tableViewController: YYYBaseTableViewController 了。如果APP需要初始化一些通用配置, 搭建一个BaseViewController 进行继承即可。

最最最后,完结撒花

告辞.jpeg

注:本文转载自juejin.cn的gla1ve_Yim的文章"https://juejin.cn/post/7462026065898815499"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

141
iOS
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2024 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top