静态通用表

图片

我们每个人通常都必须处理静态表,它们可以是我们的应用程序,授权屏幕,“关于我们”屏幕等的设置。 但是,新手开发人员通常不对此类表应用任何开发模式,而是将所有类全部写成一个不可伸缩的,不灵活的系统。

关于我如何解决这个问题-在削减。

你在说什么


在解决静态表问题之前,您应该了解它是什么。 静态表是您已经知道行数及其中内容的表。 以下类似表格的示例。

图片

问题


首先,应该找出问题所在:为什么我们不能仅创建一个将作为UITableViewDelegate和UITableViewDatasource的ViewController并仅描述您需要的所有单元格? 至少-我们的表格存在5个问题:

  1. 难以扩展
  2. 取决于索引
  3. 不灵活
  4. 缺乏重用
  5. 需要大量代码才能初始化

解决方案


解决问题的方法基于以下基础:

  1. 删除表的配置责任在一个单独的类中( 构造方法
  2. UITableViewDelegateUITableViewDataSource上的自定义包装
  3. 将单元连接到自定义协议以进行重用
  4. 为每个表创建自己的数据模型

首先,我想展示一下如何在实践中使用它-然后,我将展示它是如何在后台实现的。

实作


任务是创建一个表,其中包含两个文本单元格,并且它们之间为空。

首先,我使用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] //        ,    func cellType(for model: CellModel) -> Self.StaticTableViewCellClass.Type { //      ,    } func configure(cell: UITableViewCell, by model: CellModel) { //      ,   ,      } func itemSelected(item: CellModel) { //  didSelect,     } } 

实际上,这就是我们的全部代码。 可以说我们的桌子已经准备好了。 让我们填写数据,看看会发生什么。

哦,是的,我差点忘了。 我们需要从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) } } 

一切都准备就绪,我们必须将委托数据源作为类中的单独属性,以使弱链接不会在任何函数内中断。

我们可以运行并测试:

图片

如您所见,一切正常。

现在,让我们总结并了解我们所取得的成就:

  1. 如果我们创建一个新的单元格并想用它替换当前单元格,则可以通过更改一个变量来实现。 我们有一个非常灵活的桌子系统
  2. 我们重用所有单元格。 链接到该表的单元格越多,使用它就越容易。 非常适合大型项目。
  3. 我们减少了创建表的代码量。 当项目中有许多协议和静态单元时,我们将不得不编写更少的代码。
  4. 我们将静态表的构造从UIViewController引入了Constructor
  5. 我们不再依赖索引,我们可以安全地交换数组中的单元,并且逻辑不会中断。

本文结尾处的测试项目代码。

从内到外如何工作?


我们已经讨论了协议如何工作。 现在,我们需要了解整个构造函数及其相关类的工作方式。

让我们从构造函数本身开始:
 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委托

然后委托数据源()为我们创建了包装器UITableViewDataSourceUITableViewDelegate 。 让我们看一下:

 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) } } 

我认为对于函数heightForRowAtnumberOfRowsInSectiondidSelectRowAt毫无疑问,它们只是实现了明确的功能。 这里最有趣的方法是cellForRowAt

在其中,我们没有实现最漂亮的逻辑。 我们被迫将每个新协议写入此处的单元格,但是我们只做一次-这样就不会那么恐怖了。 如果模型像我们的单元格一样符合协议,那么我们将对其进行配置。 如果有人对如何实现此自动化有任何想法,我将很乐意听取评论。

这结束了逻辑。 在该系统中,我没有涉及第三方功利主义类, 您可以在此处阅读完整的代码

感谢您的关注!

Source: https://habr.com/ru/post/zh-CN439016/


All Articles