如何实现可收起和展开的Table Section

如何实现可收起和展开的Table Section

这是一个简单的iOS swift项目,旨在介绍如何实现可收起和展开的table section,并且,项目不需要main storyboard,XIB,注册nib等,只需要纯的Swfit代码!

项目源代码:https://github.com/jeantimex/ios-swift-collapsible-table-section

如果你希望获得Swift 3.0的代码,可以在migrate-to-swift-3.0分支里找到,最终将会汇入master分支。

效果


如何实现可收起和展开的Table Section?

第一步. 准备数据

假设我们有如下的数据,它已经按照不同的section进行组织和整理,每个section都是一个Section结构(或对象):

struct Section {
  var name: String!
  var items: [String]!
  var collapsed: Bool!

  init(name: String,items: [String],collapsed: Bool = false) {
    self.name = name
    self.items = items
    self.collapsed = collapsed
  }
}

var sections = [Section]()

sections = [
  Section(name: "Mac",items: ["MacBook","MacBook Air","MacBook Pro","iMac","Mac Pro","Mac mini","Accessories","OS X El Capitan"]),Section(name: "iPad",items: ["iPad Pro","iPad Air 2","iPad mini 4","Accessories"]),Section(name: "iPhone",items: ["iPhone 6s","iPhone 6","iPhone SE","Accessories"])
]

collapsed表示当前的section是否被收起或展开,默认下是false,即展开。

第二步. Section Header

根据苹果 API reference,我们应该使用UITableViewHeaderFooterView. 让我们创建一个section header的类来继承它,我们把这个section header类起名为CollapsibleTableViewHeader:

class CollapsibleTableViewHeader: UITableViewHeaderFooterView {
    let titleLabel = UILabel()
    let arrowLabel = UILabel()

    override init(reuseIdentifier: String?) {
        super.init(reuseIdentifier: reuseIdentifier)

        contentView.addSubview(titleLabel)
        contentView.addSubview(arrowLabel)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

当用户点击section header的时候我们需要收起或者展开这个section,为了实现这样的效果,让我们借用一下UITapGestureRecognizer. 同时我们需要将这个tap事件通知给table view并让它来更新section的collapsed值。

protocol CollapsibleTableViewHeaderDelegate {
    func toggleSection(header: CollapsibleTableViewHeader,section: Int)
}

class CollapsibleTableViewHeader: UITableViewHeaderFooterView {
    var delegate: CollapsibleTableViewHeaderDelegate?
    var section: Int = 0
    ...
    override init(reuseIdentifier: String?) {
        super.init(reuseIdentifier: reuseIdentifier)
        ...
        addGestureRecognizer(UITapGestureRecognizer(target: self,action: #selector(CollapsibleTableViewHeader.tapHeader(_:))))
    }
    ...
    func tapHeader(gestureRecognizer: UITapGestureRecognizer) {
        guard let cell = gestureRecognizer.view as? CollapsibleTableViewHeader else {
            return
        }
        delegate?.toggleSection(self,section: cell.section)
    }

    func setCollapsed(collapsed: Bool) {
        // Animate the arrow rotation (see Extensions.swf)
        arrowLabel.rotate(collapsed ? 0.0 : CGFloat(M_PI_2))
    }
}

既然我们不用任何storyboard或者XIB,如何实现自动布局呢?答案是运用NSLayoutConstraintconstraintsWithVisualFormat函数。

override init(reuseIdentifier: String?) {
    ...
    // arrowLabel must have fixed width and height
    arrowLabel.widthAnchor.constraintEqualToConstant(12).active = true
    arrowLabel.heightAnchor.constraintEqualToConstant(12).active = true

    titleLabel.translatesAutoresizingMaskIntoConstraints = false
    arrowLabel.translatesAutoresizingMaskIntoConstraints = false
}

override func layoutSubviews() {
    super.layoutSubviews()
    ...
    let views = [
        "titleLabel" : titleLabel,"arrowLabel" : arrowLabel,]

    contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(
        "H:|-20-[titleLabel]-[arrowLabel]-20-|",options: [],metrics: nil,views: views
    ))

    contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(
        "V:|-[titleLabel]-|",views: views
    ))

    contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(
        "V:|-[arrowLabel]-|",views: views
    ))
}

第三步. UITableView DataSource 以及 Delegate

首先,sections的数量为sections.count:

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
  return sections.count
}

每个section里面cell的数量为:

override func tableView(tableView: UITableView,numberOfRowsInSection section: Int) -> Int {
    return sections[section].items.count
}

接下来使用tableView的viewForHeaderInSection函数来渲染我们的section header:

override func tableView(tableView: UITableView,viewForHeaderInSection section: Int) -> UIView? {
    let header = tableView.dequeueReusableHeaderFooterViewWithIdentifier("header") as? CollapsibleTableViewHeader ?? CollapsibleTableViewHeader(reuseIdentifier: "header")

    header.titleLabel.text = sections[section].name
    header.arrowLabel.text = ">"
    header.setCollapsed(sections[section].collapsed)

    header.section = section
    header.delegate = self

    return header
}

普通的cell就很简单了,没什么好说的:

override func tableView(tableView: UITableView,cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell? ?? UITableViewCell(style: .Default,reuseIdentifier: "cell")

    cell.textLabel?.text = sections[indexPath.section].items[indexPath.row]

    return cell
}

最后一步. 如何收起和展开?

思路超级简单!如果该section的collapsed值为true,我们就将这个section里所有cell的高度都设为0,否则为 44.0!

override func tableView(tableView: UITableView,heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return sections[indexPath.section].collapsed! ? 0 : 44.0
}

切换收起和展开的函数如下:

extension CollapsibleTableViewController: CollapsibleTableViewHeaderDelegate {
    func toggleSection(header: CollapsibleTableViewHeader,section: Int) {
        let collapsed = !sections[section].collapsed

        // Toggle collapse
        sections[section].collapsed = collapsed
        header.setCollapsed(collapsed)

        // Adjust the height of the rows inside the section
        tableView.beginUpdates()
        for i in 0 ..< sections[section].items.count {
            tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: i,inSection: section)],withRowAnimation: .Automatic)
        }
        tableView.endUpdates()
    }
}

注意到我们不是简单的重绘整个section,实际上我们只需要重绘section里的所有cell就好,这样做的好处是避免了section header因重绘时闪烁的效果,最重要是的可以让我们更平滑地处理我们想要的动画效果,例如旋转那个箭头,改变背景颜色等等。

好了就这么多吧,如果你很感兴趣,请参考源码。

更多的关于table section收起和展开的项目

有时候你可能想要在grouped-style的table里实现section的收起和展开,我写了另外一个demo,https://github.com/jeantimex/ios-swift-collapsible-table-section-in-grouped-section. 实现的方法其实很类似。


作者: Yong Su @ Box Inc.

相关文章

软件简介:蓝湖辅助工具,减少移动端开发中控件属性的复制和粘...
现实生活中,我们听到的声音都是时间连续的,我们称为这种信...
前言最近在B站上看到一个漂亮的仙女姐姐跳舞视频,循环看了亿...
【Android App】实战项目之仿抖音的短视频分享App(附源码和...
前言这一篇博客应该是我花时间最多的一次了,从2022年1月底至...
因为我既对接过session、cookie,也对接过JWT,今年因为工作...