ZJTableViewManager 1.0.8

ZJTableViewManager 1.0.8

JayJavenZ 认为。



  • Javen

英文简介

关于ZJTableViewManager

强大的数据驱动TableView,构建复杂TableView从未如此轻松。

使用

直接拖入 ZJTableViewManager 文件夹中的文件,或者使用 cocoapods:pod 'ZJTableViewManager', '~> 1.0.7'

适配

版本 Swift Xcode
0.2.7 4.0 / 4.2 Xcode 10 或更高版本
1.0.3 或更高版本 4.0 ~ 5.2 Xcode 10.2 或更高版本

简介

ZJTableViewManager 基于“数据驱动页面”的理念,接管了 UITableViewdelegatedataSource 逻辑,开发者只需关注数据处理,避免冗长的判断,使代码更易于维护。

例如,一个页面中有一个 UITableView,包含5种不同的 Cell。按照传统写法,在 tableView(_:cellForRowAt:) 代理方法中,会是这样:

public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if condition1 {
            return SimpleStringCell
        }else if condition2 {
            return FullLengthTextFieldCell
        }else if condition3 {
            return TextCell
        }else if condition4 {
            return PasswordCell
        }else if condition5 {
            return SwitchCell
        }else {
            return DefaultCell
        }
}

在下面两个方法中也可能需要写上这一系列判断条件。明显的缺点是代码冗长。

tableView(_:, heightForRowAt:)
tableView(_:, didSelectRowAt:)

在实际情况中,很多人直接使用 IndexPath 作为判断条件,大量使用 if else。当需要对 Cell 显示顺序进行调整时,基于 IndexPath 的判断就会出现问题,修改起来特别容易出现错误。

当然,有经验的程序员会抽象出一个 type,通过 type 来判断 Cell 类型,以避免 IndexPath 的缺陷。这其实已经算是一种数据驱动思想的体现,相比于使用 IndexPath 判断,更不容易出错。但这也还不够,在这些方法中仍然会有很多 if else,这既影响观感,也影响逻辑理解。

因此,ZJTableViewManager 在此基础上做了进一步的封装,效果如下:

就像示例代码所示,不需要处理TableView的delegate和dataSource,不需要那些 if else,Item控制Cell处理Cell的事件,我们只需用代码描述TableView的外观,它就会按照我们的描述构建出来。

使用方式

1.系统默认Cell

创建系统默认的cell,使用ZJTableViewItem类,创建之后加入section即可

let item = ZJTableViewItem(title: "测试cell 1")
section.add(item: item)

根据需要可以修改样式为subtitle

item.style = .subtitle
item.detailLabelText = "detail label text"

运行结果:

总结一下系统默认Cell的使用步骤:

  1. 页面上创建一个TableView(StoryBoard拖或者纯代码创建都可以)
  2. 通过这个TableView初始化一个manager
  3. 创建一个Section,加入到manager里
  4. 创建Cell对应的Item,赋值之后加入到section里
  5. manager.reload()

具体不展开说了,系统cell就那几个样式,平时也很少用到,自己尝试吧。

2.自定义Cell

自定义Cell才是我们实际项目中用到最多的,所以这一块需要详细说一下。 我们来尝试自定义这样一个Cell

左边是一个UILabel,右边一个UISwitch,功能是在UISwitch开关时会发出回调,在VC中处理。

首先,新建一个ZJSwitchCell类,继承自UITableViewCell,勾选上Also create XIB file(当然不用XIB,纯代码布局也可以)

在xib文件里面拖上控件,并且把控件和UISwitch的value change事件拖线到Cell文件里面

下面是重点: 在ZJSwitchCell.swift文件里面写一个ZJSwitchCellItem类,继承自ZJTableViewItem,有三个属性,标题title,开关状态isOn,回调闭包didChanged。

让ZJSwitchCell遵循ZJCellProtocol协议,如图所示,Xcode会弹出提示,点击fix,会自动加上需要的方法和类型

ZJCelltemClass这里填写上前面写好的ZJSwitchCellItem类名

然后Xcode还会有个错误提示,继续点fix,就好了。

可能有时候Xcode自动fix补全的代码有问题,比如说typealias ZJCelltemClass = 出现两遍或者根本没有fix按钮,不要慌,cmd+b编译一下,再试试就好了

然后在cellWillAppear()方法里面写上赋值操作,它等价于tableView(_:, cellForRowAt:)方法。再到valueChanged(:)方法里面,记录UISwitch的状态,并把当前这个item通过回调传出去。Cell部分的自定义就完成了。

最后,在VC里面使用,使用之前需要manager.register(ZJSwitchCell.self, ZJSwitchItem.self)注册一下,这和之前使用系统默认的Cell有区别,自定义的Cell都需要注册一下才可以使用。

class FormViewController: UIViewController {
    var tableView: UITableView!
    var manager: ZJTableViewManager!

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView = UITableView(frame: view.bounds, style: .plain)
        view.addSubview(tableView)
        manager = ZJTableViewManager(tableView: tableView)
        manager.register(ZJSwitchCell.self, ZJSwitchItem.self)

        let section = ZJTableViewSection()
        manager.add(section: section)
        
        // Switch Item
        section.add(item: ZJSwitchItem(title: "Switch Item", isOn: false, didChanged: { item in
            zj_log(item.isOn)
        }))

        manager.reload()
    }
}

总结一下自定义Cell的步骤:

  1. 新建Cell(XIB或者纯代码都可以)
  2. 创建Cell对应的Item,通过Item给Cell传值(实际项目中一般是用Item持有Model,在cellWillAppear()中通过item.model取值并赋值到控件里面
  3. 在VC中将Cell向TableViewManager注册。

其他使用方式请参考上面系统默认Cell的使用。

3.Cell固定高度及动态计算高度处理

前面示例中的两种Cell高度都是系统默认的44,但在实际项目中我们可能需要不同高度的Cell,如何处理呢?

固定高度:聪明的同学可能已经发现了,Item控制了Cell的所有表现,所以肯定是通过Item来控制的。Item中有一个cellHeight属性,给它赋值就可以控制Cell的高度。我们可以在重写Item对象的init()方法时,给它一个固定的高度

override init() {
   super.init()
   cellHeight = 100
}

或者在VC中初始化Item之后,再给cellHeight赋值,都是可以的。

动态高度:动态高度的前提是使用AutoLayout布局,确保约束没有缺失,然后在Item赋值之后,调用一下autoHeight(:)方法,高度就算好了。

let item = AutomaticHeightCellItem()
item.feed = feed
//计算高度
item.autoHeight(manager)
//把cell加入进section
section.add(item: item)

具体可以参考下面的文章,里面阐述得更加详细,这里就不展开了。 Swift UITableViewCell高性能动态计算高度

4.TableView相关事件(如点击事件)

设置点击事件回调:

item.setSelectionHandler { (callBackItem: LevelCellItem) in
    //Do some thing
}

其他事件同理,包括section的一些事件(比如section即将出现之类的回调),具体请看Demo。

5.Scroll事件的代理

在使用TableView的同时,有时还需要处理Scroll事件,例如判断滚动是否停止,或监听滚动事件等,可以通过设置manager.scrollDelegate = self并遵循ZJTableViewScrollDelegate 来获取所有滚动事件的回调,使用方式和UIScrollViewDelegate相同。

Demo:

电商项目的评价、打星评价、添加评论图片

image image

这里主要包含3个cell,一个是打星cell,一个是评论cell,还有一个是添加图片cell。在viewController中只有20行代码,耦合度低。

override func viewDidLoad() {
        super.viewDidLoad()
        self.title = "Demo"
        self.manager = ZJTableViewManager(tableView: self.tableView)
        
        //register cell
        self.manager?.register(OrderEvaluateCell.self, OrderEvaluateItem.self)
        self.manager?.register(ZJPictureTableCell.self, ZJPictureTableItem.self)
        
        //add section
        let section = ZJTableViewSection(headerHeight: 10, color: UIColor.init(white: 0.9, alpha: 1))
        self.manager?.add(section: section)
        
        //add cells
        for i in 0...10 {
          i  //评价cell
            section.add(item: OrderEvaluateItem(title: "评价"))
            let textItem = ZJTextItem(text: nil, placeHolder: "请在此输入您的评价~", ddChanged: nil)
            textItem.isHideSeparator = true
            section.add(item: textItem)
            
            //图片cell
            if i%2 == 1 {
                //只展示图片
                let pictureItem = ZJPictureTableItem(maxNumber: 5, column: 4, space: 15, width: self.view.frame.size.width, superVC: self, pictures: [image])
                pictureItem.type = .read
                section.add(item: pictureItem)
            }else{
                //添加图片
                let pictureItem = ZJPictureTableItem(maxNumber: 5, column: 4, space: 15, width: self.view.frame.size.width, superVC: self)
                pictureItem.type = .edit
                section.add(item: pictureItem)
            }
        }
        
    }

注:

TableView可以依赖storyboard、xib、纯代码来完成初始化,cell可以依赖于xib或纯代码构建。