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

//去除表格上放多余的空隙 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
}

航哥,我的应用场景是这样的。我只有一个分区,想个这个分区添加一个页眉,然后页眉固定位置,列表可以滚动,并且点击页眉可以到另一个页面,要怎么处理一下,我是小白,不太懂!
航哥,想请教一下,如像AppStore里的更新页哪样。我有CoreData数据,数据里存有Date(储存日期有同一天和不同一天的),想在TableViewHeader中显示日期,同一天储存的数据只在当天的分组中显示应怎做?
//获取是否有默认调整的内边距
let defaultEdgeTop:CGFloat = self.automaticallyAdjustsScrollViewInsets ? 64 : 0
航哥 还有一个问题请教 就是self.automaticallyAdjustsScrollViewInsets的默认值为true 你这样设置的话在你这个例子是可以的 但是如果在没有导航的情况下self.automaticallyAdjustsScrollViewInsets的值为默认值 这个判断也就会偏移了64
航哥但是有表头 怎么偏移20呢?
写的很好,航哥,请教一下:浏览一个数组的图片,一页一页的浏览,使用collection view,点击cell,图片就全屏显示,全屏显示的时候,图片可以放大缩小。我遇到的问题就是“点击cell,图片就全屏显示,全屏显示的时候,图片可以放大缩小。”这些我不知道如何实现