当前位置: > > > Swift - 表格UITableView的plain、grouped两种样式详解(附分组头悬停)

Swift - 表格UITableView的plain、grouped两种样式详解(附分组头悬停)

在表格 tableview 初始化的时候我们可以指定需要使用的 UITableViewStyle 样式,可用的样式一共有两种:.plain .grouped。下面分别对它们做介绍。

一、plain模式

1,默认样式

  • plain 模式下,如果 tableview 有多个 section(分区、分组),组与组之间默认是没有间距的。
  • 同时组头或组尾会有 sticky 效果(粘性效果、悬停效果),即表格滚动时组头与组尾会自动停留,而不是跟随单元格一同移动。
         
import UIKit

class ViewController: UIViewController , UITableViewDelegate, UITableViewDataSource{
    
    var tableView:UITableView?
    
    //分组头标题
    var articleHeaders:[String]!
    //所有文章标题
    var articleNames:Dictionary<Int, [String]>!
    
    override func loadView() {
        super.loadView()
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //初始化数据
        self.articleNames =  [
            0:[String]([
                "1、文本标签(UILabel)的用法",
                "2、按钮(UIButton)的用法",
                "3、文本输入框(UITextField)的用法",
                "4、多行文本输入框(UITextView)的用法",
                "5、开关按钮(UISwitch)的用法",
                "6、分段选择控件(UISegmentedControl)的用法",
                "7、图像控件(UIImageView)的用法",
                ]),
            1:[String]([
                "1、使用占位符文本placeholder添加文本框提示",
                "2、使用autofocus让控件自动获取焦点",
                "3、表单客户端验证",
                "4、日期和时间选择输入",
                "5、颜色选择器",])
        ]
        
        self.articleHeaders = [
            "Swift文章",
            "HTML5文章"
        ]
        
        //创建表视图
        self.tableView = UITableView(frame:self.view.frame, style:.plain)
        self.tableView!.delegate = self
        self.tableView!.dataSource = self
        //创建一个重用的单元格
        self.tableView!.register(UITableViewCell.self,
                                 forCellReuseIdentifier: "SwiftCell")
        self.view.addSubview(self.tableView!)
    }
    
    //在本例中,有2个分区
    func numberOfSections(in tableView: UITableView) -> Int {
        return self.articleHeaders.count
    }
    
    //返回表格行数(也就是返回控件数)
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let data = self.articleNames[section]
        return data!.count
    }
    
    // UITableViewDataSource协议中的方法,该方法的返回值决定指定分区的头部
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int)
        -> String? {
            return self.articleHeaders[section]
    }
    
    // UITableViewDataSource协议中的方法,该方法的返回值决定指定分区的尾部
    func tableView(_ tableView:UITableView, titleForFooterInSection section:Int)->String? {
        let data = self.articleNames[section]
        return "有\(data!.count)篇文章"
    }
    
    //创建各单元显示内容(创建参数indexPath指定的单元)
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
        -> UITableViewCell {
            //为了提供表格显示性能,已创建完成的单元需重复使用
            let identify:String = "SwiftCell"
            //同一形式的单元格重复使用,在声明时已注册
            let cell = tableView.dequeueReusableCell(withIdentifier: identify,
                                                     for: indexPath)
            cell.accessoryType = .disclosureIndicator
            var data = self.articleNames[indexPath.section]
            cell.textLabel?.text = data![indexPath.row]
            return cell
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

2,调整分组间的间距

如果需要设置组与组之间的间距,可以通过 viewForHeaderInSection viewForFooterInSection heightForHeaderInSection heightForFooterInSection 这几个方法配合实现。

3,去除分组头、分组尾的停留效果

这个通过重写 tableView 的 scrollViewDidScroll 方法可以实现。要注意的是页面是否有导航控制器,有的话要把自动内边距调整给考虑进去。
(1)分组头部不悬停
//header不悬停
func scrollViewDidScroll(_ scrollView: UIScrollView) {
    //组头高度
    let sectionHeaderHeight:CGFloat = 30
    //获取是否有默认调整的内边距
    let defaultEdgeTop:CGFloat = navigationController?.navigationBar != nil
        && self.automaticallyAdjustsScrollViewInsets ? 64 : 0
    
    if scrollView.contentOffset.y >= -defaultEdgeTop &&
        scrollView.contentOffset.y <= sectionHeaderHeight - defaultEdgeTop  {
        scrollView.contentInset = UIEdgeInsetsMake(-scrollView.contentOffset.y, 0, 0, 0)
    }
    else if (scrollView.contentOffset.y>=sectionHeaderHeight - defaultEdgeTop) {
        scrollView.contentInset = UIEdgeInsetsMake(-sectionHeaderHeight + defaultEdgeTop,
                                                   0, 0, 0)
    }
}

(2)分组尾部不悬停
//footer不悬停
func scrollViewDidScroll(_ scrollView: UIScrollView) {
    //组尾高度
    let sectionFooterHeight:CGFloat = 30
    
    //获取是否有默认调整的内边距
    let defaultEdgeTop:CGFloat = navigationController?.navigationBar != nil
        && self.automaticallyAdjustsScrollViewInsets ? 64 : 0
    
    let b = scrollView.contentOffset.y + scrollView.frame.height
    let h = scrollView.contentSize.height - sectionFooterHeight
    
    if b <= h {
        scrollView.contentInset = UIEdgeInsetsMake(defaultEdgeTop, 0, -30, 0)
    }else if b > h && b < scrollView.contentSize.height {
         scrollView.contentInset = UIEdgeInsetsMake(defaultEdgeTop, 0, b - h - 30, 0)
    }
}

(3)分组头部、尾部均不悬停
//header、footer均不悬停
func scrollViewDidScroll(_ scrollView: UIScrollView) {
    //组头高度
    let sectionHeaderHeight:CGFloat = 30
    //组尾高度
    let sectionFooterHeight:CGFloat = 30
    
    //获取是否有默认调整的内边距
    let defaultEdgeTop:CGFloat = navigationController?.navigationBar != nil
        && self.automaticallyAdjustsScrollViewInsets ? 64 : 0
    
    //上边距相关
    var edgeTop = defaultEdgeTop
    if scrollView.contentOffset.y >= -defaultEdgeTop &&
        scrollView.contentOffset.y <= sectionHeaderHeight - defaultEdgeTop  {
        edgeTop = -scrollView.contentOffset.y
    }
    else if (scrollView.contentOffset.y>=sectionHeaderHeight - defaultEdgeTop) {
        edgeTop = -sectionHeaderHeight + defaultEdgeTop
    }
    
    //下边距相关
    var edgeBottom:CGFloat = 0
    let b = scrollView.contentOffset.y + scrollView.frame.height
    let h = scrollView.contentSize.height - sectionFooterHeight
    
    if b <= h {
        edgeBottom = -30
    }else if b > h && b < scrollView.contentSize.height {
        edgeBottom = b - h - 30
    }
    
    //设置内边距
    scrollView.contentInset = UIEdgeInsetsMake(edgeTop, 0, edgeBottom, 0)
}

二、grouped模式

1,默认样式

  • grouped 模式下,如果 tableview 有多个 section(分区、分组),组与组之间默认是有间距的。
  • 而且在表格滚动的同时组头与组尾会随之滚动、不停留,不会有 sticky 效果(粘性效果、悬停效果)。
下面分别是:分区头尾均有、只有分区头、分区头尾都没有。这三种情况:
               

2,去掉多余的间距

(1)在分组头、分组尾都存在时,可以将 tableview 最上方的间距给去除。
 
//去除表格上放多余的空隙
self.tableView?.contentInset = UIEdgeInsetsMake(-20, 0, 0, 0)

(2)如果只有分组头,没有分组尾,除了将 tableview 最上方的间距给去除,还可以将分组尾的高度设置为 0.01(不能设为 0,否则无效)。同时还要将分组尾设置成一个空的 UIView(否则在 iOS11 下分组尾高度不会起作用)。

//去除表格上放多余的空隙
self.tableView?.contentInset = UIEdgeInsetsMake(-20, 0, 0, 0)

//设置分组尾的高度
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
    return 0.01
}

//将分组尾设置为一个空的View
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
    return UIView()
}

(3)如果分组头、分组尾均没有,还可以将分组头的高度设置为 0.01

func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return 0.01
}
评论5
  • 5楼
    2017-11-14 11:08
    根根

    航哥,我的应用场景是这样的。我只有一个分区,想个这个分区添加一个页眉,然后页眉固定位置,列表可以滚动,并且点击页眉可以到另一个页面,要怎么处理一下,我是小白,不太懂!

    站长回复

    你可以继承UIView来自定义一个分区头(SectionHeader)作为页眉,然后在上面加个点击手势(UITapGestureRecognizer),点击后实现相关的跳转逻辑。

  • 4楼
    2017-08-22 12:05
    小白菜鸟

    航哥,想请教一下,如像AppStore里的更新页哪样。我有CoreData数据,数据里存有Date(储存日期有同一天和不同一天的),想在TableViewHeader中显示日期,同一天储存的数据只在当天的分组中显示应怎做?

    站长回复

    那就先把数据读取出来,然后把数据按日期进行分组,剩下的操作就和文章里的一样了。

  • 3楼
    2017-05-03 11:50
    小曹

    //获取是否有默认调整的内边距
    let defaultEdgeTop:CGFloat = self.automaticallyAdjustsScrollViewInsets ? 64 : 0
    航哥 还有一个问题请教 就是self.automaticallyAdjustsScrollViewInsets的默认值为true 你这样设置的话在你这个例子是可以的 但是如果在没有导航的情况下self.automaticallyAdjustsScrollViewInsets的值为默认值 这个判断也就会偏移了64

    站长回复

    谢谢提醒,这里判断确实写的有问题,现已修改。

  • 2楼
    2017-05-03 11:46
    小曹

    航哥但是有表头 怎么偏移20呢?

    站长回复

    有表头代码是一样的,不影响。

  • 1楼
    2017-04-21 11:07
    火麒麟

    写的很好,航哥,请教一下:浏览一个数组的图片,一页一页的浏览,使用collection view,点击cell,图片就全屏显示,全屏显示的时候,图片可以放大缩小。我遇到的问题就是“点击cell,图片就全屏显示,全屏显示的时候,图片可以放大缩小。”这些我不知道如何实现

    站长回复

    可参考我之前写的这篇文章:Swift - 实现图片全屏展示功能(可左右滑动切换图片)