TableViewConfigurator 2.0.0

TableViewConfigurator 2.0.0

测试已测试
Lang语言 SwiftSwift
许可证 MIT
发布最近发布2017年7月
SwiftSwift 版本3.0
SPM支持 SPM

John Volk 维护。



  • 作者:
  • John Volk

TableViewConfigurator

在实现 UITableView UI 时,往往会出现包含许多易出错实现的 UITableViewDataSourceUITableViewDelegate 的 controller 对象。

例如

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    switch section {
            
    case selectedItemSection:
        return 1
            
    case fooSection:
        return self.showFoo ? 4 : 1
            
    default:
        let thingCount = self.thingCollections[section - 1].things.count
            
        return thingCount == 0 ? 1 : thingCount
            
    }
}
    
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    switch indexPath.section {
            
    case self.selectedItemSection:
            
        ...
            
    case fooSection:
        switch indexPath.row {
                
        case 0:
              
            ...
                
        case 1:
                
            ...
                
        case 2:
              
            ...
                
        case 3:
                
            ...
                
        default:
                
            break
                
        }
            
    default:
        let things = self.thingCollections[indexPath.section - 1].things
            
        if things.count > 0 {
            ...
        } else {
            ...
        }
    }
}
    
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    let section = indexPath.section
        
    if section != selectedItemSection || (section == selectedItemSection && self.selectedItem == nil) {
        ...
    } else {
        ...
    }
}
    
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    switch indexPath.section {
            
    case selectedItemSection:
        break
            
    case fooSection:
        break
            
    default:
        let things = self.thingCollections[indexPath.section - 1].things
            
        if things.count > 0 {
            self.performSegueWithIdentifier("showThings", sender: self)
        }
    }
        
    tableView.deselectRowAtIndexPath(indexPath, animated: true)
}

创建 TableViewConfigurator 是为了消除这种类型的代码,用更声明性的方法来替换。

安装

TableViewConfigurator 通过 CocoaPods 提供。要安装它,只需将以下行添加到您的 Podfile 中

pod "TableViewConfigurator"

用法

TableViewConfigurator 基于 RowConfigurationSectionConfiguration 的概念。在概念层次的底层是 RowConfiguration。一个 RowConfiguration 允许您指定在您的 UITableView 中出现的单个行或行组。它目前有两种类型:ConstantRowConfigurationModelRowConfiguration

ConstantRowConfiguration

ConstantRowConfiguration 表示您的 UITableView 中的一个单一行。创建它的唯一方法是实现 ConfigurableTableViewCell 协议,该协议由 ConstantRowConfiguration 的构造函数通过泛型类型参数指定。

import UIKit
import TableViewConfigurator

class BasicCell: UITableViewCell, ConfigurableTableViewCell {

    func configure() {
        self.textLabel?.text = "Basic Cell"
    }
}

let rowConfiguration = ConstantRowConfiguration()

此时 rowConfiguration 已准备好使用,将在适当的时刻调用其 configure() 方法。但是,在使用前有几种不同的配置可以应用。

.cellResuseId()

默认情况下,TableViewConfigurator 会为您的 cell 类生成一个与类名相同的重用标识符。如果这不是您想要的行为,您可以选择在您的 cell 类中覆盖 buildReuseIdentifier(),或者在您的控制器中指定重用标识符。

let rowConfiguration = ConstantRowConfiguration().cellReuseId("someReuseId")

.height() / .estimatedHeight()

您可以根据您使用的尺寸方法指定单元格的身高或估计身高。

let rowConfiguration = ConstantRowConfiguration<BasicCell>().height(44.0)
let anotherConfiguration = ConstantRowConfiguration<BasicCell>().estimatedHeight(44.0)
.additionalConfig()

您可以在控制器上下文中,在调用 cell 的 configure() 方法后,指定应在单元格上发生额外配置。

let rowConfiguration = ConstantRowConfiguration<BasicCell>()
    .additionalConfig({ (cell) -> Void in
        cell.accessoryType = self.someControllerFlag ? .DisclosureIndicator : .None
    })
.selectionHandler()

您可以在当 ConstantRowConfiguration 中的行被选中时,指定应在控制器上下文中调用的代码。

let rowConfiguration = ConstantRowConfiguration<BasicCell>()
    .selectionHandler({ self.performSegueWithIdentifier("someSegue", sender: self) })
.canEditHandler()

您可以指定一个闭包,用于确定单元格是否可以参与 UITableView 编辑工作流程。

.editHandler()

您可以使用闭包处理从底层 UITableView 接收到的编辑事件。

.hideWhen()

最后,您还可以指定一个闭包,以指示何时应隐藏 ConstantRowConfiguration 中的行。

let rowConfiguration = ConstantRowConfiguration<BasicCell>()
    .hideWhen({ () -> Bool in
        return self.shouldHideRow
    })

ModelRowConfiguration

ModelRowConfiguration 表示由模型类型数组定义的一组行。它具有与 ConstantRowConfiguration 相同的所有配置选项,但是您定义的闭包回调将接受一个代表相关行模型的 model 参数和一个代表模型在当前模型序列中的位置的 index 参数。此外,它的构造函数需要两个泛型类型参数。第一个是一个 ModelConfigurableTableViewCell 的实现,第二个是一个符合 RowModel 协议的类。

它的构造函数还可以传入一个返回更新模型的函数。这对于动态 UI 非常有用。当使用 modelGenerator 时,您还可以选择性地指定模型生成是否应该优化。当您传递 true 时,生成器将仅在调用 TableViewConfigurator 本身的 changeSetAfterPerformingOperation()reloadData() 时调用。在底层 UITableView 上调用 reloadData() 不会产生任何可见的变化。

class PersonCell: UITableViewCell, ModelConfigurableTableViewCell {
    
    @IBOutlet var nameLabel: UILabel!
    @IBOutlet var ageLabel: UILabel!

    func configure(model: Person) {
        self.nameLabel.text = "\(model.firstName) \(model.lastName)"
        self.ageLabel.text = "Age \(model.age)"
    }
}

let rowConfiguration = ModelRowConfiguration <PersonCell, Person>(models: self.people)

RowModel

RowModel 协议允许 ModelRowConfiguration 准确比较配置的连续状态下,用于在表中动画变化。该协议要求一个名为 identityTag 的属性,以唯一标识任何给定的 RowModel。默认情况下,每个模型都会根据其在当前模型序列中的索引获得相应的 identityTag。对于仅改变模型在当前序列中可见性的情况,这通常是足够的,但如果本身序列发生修改,则将提供错误的结果。

ModelRowConfiguration 还添加了几个额外的“生成器”属性。

.heightGenerator()

您可以将一个函数指定为返回模型行最新的高度。

.estimatedHeightGenerator()

您可以将一个函数指定为返回模型行最新的估计高度。

SectionConfiguration

RowConfiguration 实例组合到 SectionConfiguration 中时,才能真正体现 TableViewConfigurator 的强大。您可以按任意顺序对 RowConfiguration 实例进行分组,TableViewConfigurator 将生成对于其支持的 UITableViewDataSourceUITableViewDelegate 部分的正确结果。

例如,假设你想创建一个由N个元素组成的UITableView部分,并且这些元素被两个常数行所包围。通常情况下,这样做既烦琐又容易出错。使用TableViewConfigurator则变得简单。

let people = [Person(firstName: "John", lastName: "Doe", age: 50),
    Person(firstName: "Alex", lastName: "Great", age: 32),
    Person(firstName: "Napoléon", lastName: "Bonaparte", age: 18)]
    
let section = SectionConfiguration(rowConfigurations:
    [ConstantRowConfiguration<BasicCell>(),
        ModelRowConfiguration<PersonCell, Person>(models: people),
        ConstantRowConfiguration<BasicCell>()])

对于SectionConfiguration,还有两个额外的配置选项可用。

headerTitle()

你可以指定用作 sections 标题的字符串。

footerTitle()

你可以指定用作 sections 页脚的字符串。

TableViewConfigurator

一旦你创建了你的RowConfigurationSectionConfiguration实例,下一步是将它们组合到你的TableViewConfigurator中,并在适当的地方从你的控制器中委托给它。TableViewConfigurator实现了UITableViewDataSourceUITableViewDelegate,但它不太可能实现你可能需要的全部部分,因此最好只从你的控制器中在适当的地方进行委托。

override func viewDidLoad() {
    super.viewDidLoad()
        
    let basicSection = SectionConfiguration(rowConfiguration:
        ConstantRowConfiguration<BasicCell>()
            .height(44.0))
        
    let peopleRows = ModelRowConfiguration<PersonCell, Person>(models: self.people)
        .hideWhen({ (model) -> Bool in
            return self.hidePeople
        })
        .height(44.0)
        
    let peopleSection = SectionConfiguration(rowConfigurations:
        [ConstantRowConfiguration<SwitchCell>()
            .additionalConfig({ (cell) -> Void in
                cell.hideLabel.text = "Hide People"
                cell.hideSwitch.on = self.hidePeople
                cell.switchChangedHandler = { (on) -> Void in
                    self.configurator.animateChangeSet(self.configurator.changeSetAfterPerformingOperation({ self.hidePeople = on }))
                }
            })
            .height(44.0), peopleRows, ConstantRowConfiguration<BasicCell>().height(44.0)])
        
    let disclosureSection = SectionConfiguration(rowConfiguration:
        ConstantRowConfiguration<DisclosureCell>()
            .selectionHandler({ () -> Bool in
                self.performSegueWithIdentifier("showDetails", sender: self)
                return true
            })
            .height(44.0))
        
    self.configurator = TableViewConfigurator(tableView: tableView, sectionConfigurations:
        [basicSection, peopleSection, disclosureSection])
}

func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return self.configurator.numberOfSectionsInTableView(tableView)
}

func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    return self.configurator.tableView(tableView, titleForHeaderInSection: section)
}
    
func tableView(tableView: UITableView, titleForFooterInSection section: Int) -> String? {
    return self.configurator.tableView(tableView, titleForFooterInSection: section)
}
    
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return self.configurator.tableView(tableView, numberOfRowsInSection: section)
}
    
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    return self.configurator.tableView(tableView, cellForRowAtIndexPath: indexPath)
}
    
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return self.configurator.tableView(tableView, heightForRowAtIndexPath: indexPath)
}
    
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    self.configurator.tableView(tableView, didSelectRowAtIndexPath: indexPath)
}

正如上面的例子所示,TableViewConfigurator也支持UITableView的行插入和删除。

changeSetAfterPerformingOperation() / .animateChangeSet()

为了支持行的插入和删除,你需要适当地设置你的cells的.hideWhen()处理程序(或者从你的模型生成器中返回nil),然后调用.changeSetAfterPerformingOperation()。TableViewConfigurator会注意到在执行操作前后的可见性变化,并将这些变化以TableViewChangeSet元组的形式返回给你。你所要做的只是将这些更改传递给.animateChangeSet()或你的UITableView,然后你的行/部分就会相应地进行动画处理。

indexPathsForRowConfiguration()

TableViewConfigurator还提供了indexPathsFor(rowConfiguration:)方法,以便你可以访问RowConfiguration的实际NSIndexPath数组。这对于调用reloadRowsAtIndexPaths()UITableView上(以强制你的细胞从它们自己的模型或常量配置重新加载)来说非常有用。

refreshAllRowConfigurations()

有时你可能想要刷新当前可见的单元格(或所有可见的单元格)的内容,而不强制单元格完全重新加载。例如,如果您的单元格中包含一个UITextField,进行重新加载(这会销毁并替换现有的单元格)会导致文本框失去焦点。为了解决这个问题,TableViewConfigurator提供了refreshAllRowConfigurations()方法,可以从模型或常量配置非破坏性地刷新任何可见的单元格。当然,当屏幕外的单元格变为可见时,它们会由TableView查询其代理以进行更新。

作者

John Volk,[email protected]

许可

TableViewConfigurator在MIT许可下可用。有关更多信息,请参阅LICENSE文件。