我们每个人通常都必须处理静态表,它们可以是我们的应用程序,授权屏幕,“关于我们”屏幕等的设置。 但是,新手开发人员通常不对此类表应用任何开发模式,而是将所有类全部写成一个不可伸缩的,不灵活的系统。
关于我如何解决这个问题-在削减。
你在说什么
在解决静态表问题之前,您应该了解它是什么。 静态表是您已经知道行数及其中内容的表。 以下类似表格的示例。

问题
首先,应该找出问题所在:为什么我们不能仅创建一个将作为UITableViewDelegate和UITableViewDatasource的ViewController并仅描述您需要的所有单元格? 至少-我们的表格存在5个问题:
- 难以扩展
- 取决于索引
- 不灵活
- 缺乏重用
- 需要大量代码才能初始化
解决方案
解决问题的方法基于以下基础:
- 删除表的配置责任在一个单独的类中( 构造方法 )
- UITableViewDelegate和UITableViewDataSource上的自定义包装
- 将单元连接到自定义协议以进行重用
- 为每个表创建自己的数据模型
首先,我想展示一下如何在实践中使用它-然后,我将展示它是如何在后台实现的。
实作
任务是创建一个表,其中包含两个文本单元格,并且它们之间为空。
首先,我使用
UILabel创建了一个常规
TextTableViewCell 。
接下来,每个带有静态表的UIViewController需要其自己的构造函数,让我们创建它:
class ViewControllerConstructor: StaticConstructorContainer { typealias ModelType = <#type#> }
当我们从
StaticConstructorContainer继承它时,首先,通用协议要求我们键入模型(
ModelType )-这也是我们还需要创建的单元模型的类型,让我们开始吧。
我为此使用enum,因为它更适合我们的任务,而这里的乐趣就开始了。 我们将使用诸如
标题,字幕,彩色,字体等协议来将内容填充到表中。 您可以猜测,这些协议负责显示文本。 说标题协议需要
标题:字符串? ,并且如果我们的单元格支持
标题显示,则将其填充。 让我们看看它是什么样的:
protocol Fonted { var font: UIFont? { get } } protocol FontedConfigurable { func configure(by model: Fonted) } protocol Titled { var title: String? { get } } protocol TitledConfigurable { func configure(by model: Titled) } protocol Subtitled { var subtitle: String? { get } } protocol SubtitledConfigurable { func configure(by model: Subtitled) } protocol Imaged { var image: UIImage? { get } } protocol ImagedConfigurable { func configure(by model: Imaged) }
因此,这里仅展示此类协议的一小部分,您可以自己创建它,如您所见-非常简单。 我提醒您,我们一次创建它们是为了1个目的,然后忘记它们并平静地使用它们。
我们的单元格(
带有文本 )基本上支持以下内容:文本的字体,文本本身,文本的颜色,单元格的背景颜色,以及通常想到的任何东西。
到目前为止,我们只需要
标题 。 因此,我们从Titleed继承我们的模型。 在模型内部,以防万一,我们将指示我们将拥有哪些类型的单元格。
enum CellModel: Titled { case firstText case emptyMiddle case secondText var title: String? { switch self { case .firstText: return " - " case .secondText: return " - " default: return nil } } }
由于中间没有标签(空单元格),因此可以返回nil。
我们完成了C单元,您可以将其插入到我们的构造函数中。
class ViewControllerConstructor: StaticConstructorContainer { typealias ModelType = CellModel var models: [CellModel]
实际上,这就是我们的全部代码。 可以说我们的桌子已经准备好了。 让我们填写数据,看看会发生什么。
哦,是的,我差点忘了。 我们需要从TitledConfigurable协议继承单元格,以便它可以在其自身中插入标题。 单元格也支持动态高度。
extension TextTableViewCell: TitledConfigurable { func configure(by model: Titled) { label.text = model.title } }
填充的构造函数如下所示:
class ViewControllerConstructor: StaticConstructorContainer { typealias ModelType = CellModel var models: [CellModel] = [.firstText, .emptyMiddle, .secondText] func cellType(for model: CellModel) -> StaticTableViewCellClass.Type { switch model { case .emptyMiddle: return EmptyTableViewCell.self case .firstText, .secondText: return TextTableViewCell.self } } func configure(cell: UITableViewCell, by model: CellModel) { cell.selectionStyle = .none } func itemSelected(item: CellModel) { switch item { case .emptyMiddle: print(" ") default: print(" ...") } } }
看起来很紧凑,对不对?
实际上,我们要做的最后一件事就是将它们全部连接到ViewController'e:
class ViewController: UIViewController { private let tableView: UITableView = { let tableView = UITableView() return tableView }() private let constructor = ViewControllerConstructor() private lazy var delegateDataSource = constructor.delegateDataSource() override func viewDidLoad() { super.viewDidLoad() constructor.setup(at: tableView, dataSource: delegateDataSource) } }
一切都准备就绪,我们必须将
委托数据源作为类中的单独属性,以使弱链接不会在任何函数内中断。
我们可以运行并测试:

如您所见,一切正常。
现在,让我们总结并了解我们所取得的成就:
- 如果我们创建一个新的单元格并想用它替换当前单元格,则可以通过更改一个变量来实现。 我们有一个非常灵活的桌子系统
- 我们重用所有单元格。 链接到该表的单元格越多,使用它就越容易。 非常适合大型项目。
- 我们减少了创建表的代码量。 当项目中有许多协议和静态单元时,我们将不得不编写更少的代码。
- 我们将静态表的构造从UIViewController引入了Constructor
- 我们不再依赖索引,我们可以安全地交换数组中的单元,并且逻辑不会中断。
本文结尾处的测试项目代码。
从内到外如何工作?
我们已经讨论了协议如何工作。 现在,我们需要了解整个构造函数及其相关类的工作方式。
让我们从构造函数本身开始:
protocol StaticConstructorContainer { associatedtype ModelType var models: [ModelType] { get } func cellType(for model: ModelType) -> StaticTableViewCellClass.Type func configure(cell: UITableViewCell, by model: ModelType) func itemSelected(item: ModelType) }
这是一个通用协议,需要我们已经熟悉的功能。
更有趣的是它的
扩展名 :
extension StaticConstructorContainer { typealias StaticTableViewCellClass = StaticCell & NibLoadable func delegateDataSource() -> StaticDataSourceDelegate<Self> { return StaticDataSourceDelegate<Self>.init(container: self) } func setup<T: StaticConstructorContainer>(at tableView: UITableView, dataSource: StaticDataSourceDelegate<T>) { models.forEach { (model) in let type = cellType(for: model) tableView.register(type.nib, forCellReuseIdentifier: type.name) } tableView.delegate = dataSource tableView.dataSource = dataSource dataSource.tableView = tableView } }
我们在ViewController中调用的
setup函数为我们注册了所有单元并委托
dataSource和
委托 。
然后
委托数据源()为我们创建了包装器
UITableViewDataSource和
UITableViewDelegate 。 让我们看一下:
class StaticDataSourceDelegate<Container: StaticConstructorContainer>: NSObject, UITableViewDelegate, UITableViewDataSource { private let container: Container weak var tableView: UITableView? init(container: Container) { self.container = container } func reload() { tableView?.reloadData() } func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { let type = container.cellType(for: container.models[indexPath.row]) return type.estimatedHeight ?? type.height } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let type = container.cellType(for: container.models[indexPath.row]) return type.height } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return container.models.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let model = container.models[indexPath.row] let type = container.cellType(for: model) let cell = tableView.dequeueReusableCell(withIdentifier: type.name, for: indexPath) if let typedCell = cell as? TitledConfigurable, let titled = model as? Titled { typedCell.configure(by: titled) } if let typedCell = cell as? SubtitledConfigurable, let subtitle = model as? Subtitled { typedCell.configure(by: subtitle) } if let typedCell = cell as? ImagedConfigurable, let imaged = model as? Imaged { typedCell.configure(by: imaged) } container.configure(cell: cell, by: model) return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let model = container.models[indexPath.row] container.itemSelected(item: model) } }
我认为对于函数
heightForRowAt ,
numberOfRowsInSection ,
didSelectRowAt毫无疑问,它们只是实现了明确的功能。 这里最有趣的方法是
cellForRowAt 。
在其中,我们没有实现最漂亮的逻辑。 我们被迫将每个新协议写入此处的单元格,但是我们只做一次-这样就不会那么恐怖了。 如果模型像我们的单元格一样符合协议,那么我们将对其进行配置。 如果有人对如何实现此自动化有任何想法,我将很乐意听取评论。
这结束了逻辑。 在该系统中,我没有涉及第三方功利主义类,
您可以在此处阅读完整的代码 。
感谢您的关注!