官方文档:Table View Programming Guide for iOS
UITableView 适用于单表格展示,支持滑动。
UITableViewDelegate协议
官网文档:UITableViewDelegate
UITableviewDelegate作用:对UITableview的整个生命周期的各过程或者各种事件状态设置接口,方便你在某种时刻或者事件状态下做自定义操作。具体分为:
1.某个视图模块(cell、header、footer)的将要显示和结束显示.
2.某个视图在某个位置index/section的高度/预留高度。
3.传图UIView作为header、footer的视图。
4.附属按钮点击时候
5.高亮状态的三个状态。
6.选择或取消选择的两个状态(will和didEnd)
7.编辑状态、编辑类型设置(没有、删除、插入)、自定义删除按钮的字【titleForDelecteXXXX】、自定义增加多个按钮【editActionForRow】,注意自带的删除将会消失,所以可以自己加上,最先放入的在右边、是否首行缩进(indent)??
8.移动时的操作接口
9.复制与粘贴菜单操作(如果需要的实现相关三个代理【performAction】)
10.与焦点(focus)有关的几个接口【不知道干嘛用的】
Tips: 如果TableViewCell满足约束,设置tableView.rowHeight = UITableView.automaticDimension 即可实现自动撑开 Cell。
[官网文档:Self-Sizing Table View Cells]
UITableViewDataSource协议
1.确定表格模块是,sectionNumber。
2.确定每个块中有多少行。
3.每行的数据填充。
4.header与footer的text填充,【疑问:字体颜色样式怎么控制?自定义header覆盖吗?】。
5.编辑(插入、移动、删除的数据源处理)
6.添加右侧栏的侧栏字母序列【注意:tableView的style必须为plain】
7.右侧索引字母对应的某个模块,相当于将右侧索引字母与模块关联起来。
扩展:右侧索引字母的颜色与tableView的tintColor一致。
UITableViewDataSourcePrefetch协议
主要就两个方法,一个预加载数据(比如在其中进行数据源的预加载处理),另一个取消预加载,可以提高很大性能,大量数据建议使用。
对表格进行增删
1、确定删除位置
2、先更新数据源【特重要,不然会发生错误终止,因为行数不对应/不够,】
3、删除或插入行
4、更新表格
示例:评分cell的显示与隐藏
/**
* 选择评分
*/
func tableViewSelectScore(){
self.tableview?.beginUpdates() //开始更新表格(一般是用于插入或删除行)
let tempIndexPath = [NSIndexPath(forRow: 2, inSection: 0)] //首先确定哪一个路径改变(某一段的某行(基数是从0开始的)))
if showScore{//如果已经点开拉评论
//说明评论已经打开,需要取消掉评论行
//1.取消前需要处理数据源,行数减少一个
self.titleArray.removeAtIndex(2)
//2移除那行
self.tableview?.deleteRowsAtIndexPaths(tempIndexPath, withRowAnimation: UITableViewRowAnimation.Right)//从右边划出
self.showScore = false //如果是有一个可选显示的,可以设一个标记变量
}else{
self.titleArray.insert("", atIndex: 2) //很重要,在表格对应的数据源添加数据,不然就会发生表格行数不够的错误,引起程序终止
self.tableview?.insertRowsAtIndexPaths(tempIndexPath, withRowAnimation: UITableViewRowAnimation.Left) //插入位置是【参数是数组】,及动画效果(从左到右)
self.showScore = true //显示星星
}
self.tableview?.endUpdates() //结束更新
}
多个增删,建议使用performBatchUpdates(_:completion:) | Apple Developer Documentation
补充提示:
1、表格视图的Footer 与header,注意与sectione的footer和header不是同一个
UITableViewCell
通过子类化 UITableViewCell来自定义 Cell, 添加需要的控件。
通常建议将需要展示的内容先添加在自定义 View上,再将 UITableViewCell 作为容器显示(理由参考:Why I never subclass UITableViewCell or UICollectionViewCell)。 优点:可以复用 View(用在其他 View、UITableViewCell、UICollectionCell 等各种组合)
定义显示 View
// 定义显示 View
class ProductView: UIView {
// all the same properties
// ...
override init(frame: CGRect) {
super.init(frame: frame)
setupLayout()
}
private func setupLayout() {
// all the same layout
// ...
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(with product: ProductViewModel) {
// same configuration logic
// ...
}
}
更好的兼容 SwiftUI
import SwiftUI
struct ProductViewRepresentable: UIViewRepresentable {
let product: ProductViewModel
func makeUIView(context: Context) -> ProductView {
return ProductView()
}
func updateUIView(_ uiView: ProductView, context: Context) {
uiView.configure(with: product)
}
}
定义Swift 泛型。
public class ViewEmbeddingTableViewCell<EmbeddedView: UIView>: UITableViewCell {
public let embeddedView: UIView = {
let view = EmbeddedView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(embeddedView)
// 这里还可以定义统一边距,以便统一控制 Cell
NSLayoutConstraint.activate([
embeddedView.topAnchor.constraint(equalTo: contentView.topAnchor),
embeddedView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
embeddedView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
embeddedView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
完美使用
tableView.register(
ViewEmbeddingTableViewCell<ProductView>.self,
forCellWithReuseIdentifier: "ProductCell"
)
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: "ProductCell",
for: indexPath
) as? ViewEmbeddingCollectionViewCell<ProductView>
cell?.embeddedView.configure(with: productViewModel)
自定义header/Footer
1、 还需要添加一些UI控件或定制样式,这就需要通过tableview的代理方法viewForHeaderInSection(返回值为UIView)内进行定制
2、 定制思想:创建需要的控件对象,配置好控件对象,创建一个视图对象,将控件对象添加到视图对象并布局好即可,做好相应的响应处理(必要时)
refreshControl
如果直接使用UITableViewController,是可以自带刷新控件的,默认是不使用,需要则设置true, 但是只可以下拉刷新,
使用的几个注意点,
1.下拉后自动执行
// self.refreshControl?.beginRefreshing() //!!!:
会立即自动调用tableView.ReloadDatatableView cellForRow xxx的代理方法,给cell填充的方法,但是不会执行
func numberOfSections(in tableView:UITableView) ->Int,
所以需要注意:此时数据源最好不变,否则可能会导致越界异常
2.需要手动在一定的逻辑处结束刷新。
self.refreshControl?.endRefreshing() //之后显示重新加载table
默认的footer(如果不设置为0.001f的话)【此图片用于评论区】
UITableView(Plain)取消footer和header的粘黏
示例代码
func scrollViewDidScroll(_ scrollView: UIScrollView) {
/// 取消粘连
let sectionHeaderHeight:CGFloat = 30;
let sectionFooterHeight: CGFloat = 55
let offsetY = scrollView.contentOffset.y
if (offsetY <= sectionHeaderHeight && offsetY >= 0) {
scrollView.contentInset = UIEdgeInsets.init(top: -offsetY, left: 0, bottom: -sectionFooterHeight, right: 0)
} else if (offsetY >= sectionHeaderHeight) {
scrollView.contentInset = UIEdgeInsets.init(top: -offsetY, left: 0, bottom: -sectionFooterHeight, right: 0)
}else if (offsetY >= scrollView.contentSize.height - scrollView.frame.size.height - sectionFooterHeight && offsetY <= scrollView.contentSize.height - scrollView.frame.size.height){
scrollView.contentInset = UIEdgeInsets(top: -offsetY, left: 0, bottom: (scrollView.contentSize.height-scrollView.frame.height-sectionFooterHeight), right: 0)
}
}
使用 Group 注意事项
如果我们不想保留 tabelView 的 header和 footer沾粘效果。可以利用 group 样式来取消,但是同时需要注意设置 headerView 和 footerView 的高度。
设置 tableViewFooterHeader
let header = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: CGFloat.leastNormalMagnitude))
let footer = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: CGFloat.leastNormalMagnitude))
tableView.tableViewHeaderView = header
tableView.tableViewFooterView = footer
设置 HeaderView 高度
以下方式视情况二选一,优先级: 代理方法>直接设置
直接设置
tableView.sectionHeaderHeight = CGFloat.leastNormalMagnitude
代理方式设置
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return CGFloat.leastNonzeroMagnitude // 或者0.01
}
设置 FooterView 高度
以下方式视情况二选一,优先级: 代理方法>直接设置
直接设置方式
tableView.sectionFooterHeight = CGFloat.leastNormalMagnitude
代理方法设置
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return CGFloat.leastNonzeroMagnitude //或者0.001, 注意0.01(或者CGFloat.leastNormalMagnitude)在 iOS14中还是有35空白
}
以上高度大部分可以直接设置0.01(footerView代理中需要使用0.001/CGFloat.leastNonzeroMagnitude),但是建议设置 `CGFloat.leastNormalMagnitude`,原因在于Cell滑动工程中可能会有白影闪过
最小 Float 正数值
Objective-C: CGFLOAT_MIN
Swift:
CGFloat.leastNormalMagnitude :最小的正规则数
CGFloat.leastNonzeroMagnitude:最小正数(如果支持 subnormal,会比 CGFloat.leastNormalMagnitude 小,否则就是相等的)
关于 subnormal value 也叫 denormal number,统一称作非规格化浮点数
修改 Header Title 样式
在多组标题时,代理方法只能传入 title, 那要如何修改样式了(在不重写 headerView前提下)?
只需要实现 代理方法的 willDisplayHeaderView 方法, 然后获取相应对象,修改即可。
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
guard let header = view as? UITableViewHeaderFooterView else {
return
}
header.textLabel?.font = UIFont.systemFont(ofSize: 12, weight: .medium)
header.textLabel?.textColor = UIColor(hexString: "#787878")
header.contentView.backgroundColor = UIColor(hexString: "#F8F8F8") // 注意直接修改 backgroundColor 没有效果。一般为nil
}
section Header 取消背景默认灰色(不重写 headerView方式)
guard let header = view as? UITableViewHeaderFooterView else {
return
}
//header.tintColor = .clear // 可以实现,但是不推荐,可能影响其他控件
if #available(iOS 14.0, *) {
header.backgroundConfiguration = UIBackgroundConfiguration.clear()
} else {
header.backgroundColor = .clear
}
兼容原因:在 iOS14后,增加了现代化配置 Cell 功能,会在 background配置某些view.可参考 iOS14的 Modern cell configration给我们带来什么