如何实现可收起和展开的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.

如何实现可收起和展开的Table Section的更多相关文章

  1. ios – CRASH尝试删除并重新加载相同的索引路径

    非常感谢!

  2. Swift - 动态添加删除TableView的单元格以及内部元件-日期控件

    比如我们做一个消息提醒页面,默认页面只显示两个单元格。当点击第二个单元格时,下面会再添加一个单元格放置日期选择控件。而再次点击第二个单元格,日期选择控件又会隐藏。//日期选择器显示状态datePickerVisible:Bool=falseoverridefuncviewDidLoad(){super.viewDidLoad()self.title="添加任务"//去除尾部多余的空行.tableView.tableFooterView=UIView}didReceiveMemoryWarning(){.d

  3. 第七章:table单元格的选择和UIAlertController

    运行app,自己试试选择cell更多关于UIAlertController再继续研究之前,我们需要更多的了解一下UIAlertController。UIAlertController是在iOS8引入用来替代UIAlertView和UIActionSheet的。参照上面的代码片段,我们可以指定UIAlertController的preferredStyle。创建好动作后可以使用addAction将动作和UIAlertController连接起来。这就是使用UIAlertController的方法。

  4. tableview使用自定义类,页面跳转,本地存储

    如图,添加下面的三行代码2下面创建自己的cell,新建一个swift文件,命名为TableViewCell3因为还要考虑到界面的跳转,需要新建swift文件PushTest

  5. Swift UITableView相关功能八

    但是,我们发现当我们点击右侧索引的时候好像和table的关系不明确。其实我们少了一个代理方法,他是专门用来关联索引和table分区的这里我们简单设置了一下,将字母顺序和table的分区对应上了。

  6. [IOSS]UITableView分组

    [IOSS]UITableView分组DEMO:http://download.csdn.net/detail/u012881779/9233421应用入口(AppDelegate.swift)分组控制器(DMViewController.swift)分组Cell(DMTableViewCell.swift)示意图:

  7. Swift:表格视图单元格单选二

    效果前言前段时间写了一篇博客:表格视图单元格单选(一),实现起来并不复杂,简单易懂。,由于每一组中,单元格内容不一致,因此建议用字典存储。我们可以为它创建一个indexPath属性,在配置表格视图协议方法cellForRowAtIndexPath:时,我们赋值单元格的indexPath属性就OK了。

  8. Swift设置Table View的Cell中Lable自适应内容高度的

    Swift设置TableView的Cell中Lable自适应内容高度的最后修改在TableCell中Label的lines属性,将其设置为0。

  9. Swift - 给表格添加编辑功能删除,插入

    overridefuncloadView(){super.loadView()}viewDidLoad(){.viewDidLoad()//初始化数据,这一次数据,我们放在属性列表文件里self.allnames=[0:[](["UILabel标签"ottom:auto!important;font-family:Consolas,"UITableView表格视图"])];print.adHeaders=["常见UIKit控件"ottom:auto!important;font-family:Consol

  10. 更加 Swift 化的 Collection View 和 Table View Cells

    本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

随机推荐

  1. Swift UITextField,UITextView,UISegmentedControl,UISwitch

    下面我们通过一个demo来简单的实现下这些控件的功能.首先,我们拖将这几个控件拖到storyboard,并关联上相应的属性和动作.如图:关联上属性和动作后,看看实现的代码:

  2. swift UISlider,UIStepper

    我们用两个label来显示slider和stepper的值.再用张图片来显示改变stepper值的效果.首先,这三个控件需要全局变量声明如下然后,我们对所有的控件做个简单的布局:最后,当slider的值改变时,我们用一个label来显示值的变化,同样,用另一个label来显示stepper值的变化,并改变图片的大小:实现效果如下:

  3. preferredFontForTextStyle字体设置之更改

    即:

  4. Swift没有异常处理,遇到功能性错误怎么办?

    本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

  5. 字典实战和UIKit初探

    ios中数组和字典的应用Applicationschedule类别子项类别名称优先级数据包contactsentertainment接触UIKit学习用Swift调用CocoaTouchimportUIKitletcolors=[]varbackView=UIView(frame:CGRectMake(0.0,0.0,320.0,CGFloat(colors.count*50)))backView

  6. swift语言IOS8开发战记21 Core Data2

    上一话中我们简单地介绍了一些coredata的基本知识,这一话我们通过编程来实现coredata的使用。还记得我们在coredata中定义的那个Model么,上面这段代码会加载这个Model。定义完方法之后,我们对coredata的准备都已经完成了。最后强调一点,coredata并不是数据库,它只是一个框架,协助我们进行数据库操作,它并不关心我们把数据存到哪里。

  7. swift语言IOS8开发战记22 Core Data3

    上一话我们定义了与coredata有关的变量和方法,做足了准备工作,这一话我们来试试能不能成功。首先打开上一话中生成的Info类,在其中引用头文件的地方添加一个@objc,不然后面会报错,我也不知道为什么。

  8. swift实战小程序1天气预报

    在有一定swift基础的情况下,让我们来做一些小程序练练手,今天来试试做一个简单地天气预报。然后在btnpressed方法中依旧增加loadWeather方法.在loadWeather方法中加上信息的显示语句:运行一下看看效果,如图:虽然显示出来了,但是我们的text是可编辑状态的,在storyboard中勾选Editable,再次运行:大功告成,而且现在每次单击按钮,就会重新请求天气情况,大家也来试试吧。

  9. 【iOS学习01】swift ? and !  的学习

    如果不初始化就会报错。

  10. swift语言IOS8开发战记23 Core Data4

    接着我们需要把我们的Rest类变成一个被coredata管理的类,点开Rest类,作如下修改:关键字@NSManaged的作用是与实体中对应的属性通信,BinaryData对应的类型是NSData,CoreData没有布尔属性,只能用0和1来区分。进行如下操作,输入类名:建立好之后因为我们之前写的代码有些地方并不适用于coredata,所以编译器会报错,现在来一一解决。

返回
顶部