一、UITableView是什么?
UITableView是一个强大的视图组件,它可以显示一个滚动的列表数据。每个列表项被称为一个单元格(UITableViewCell)。UITableView的数据源(通常是一个数组)和委托(通常是视图控制器)提供了列表的内容和行为。
二、UITableView什么工作原理?
理解UITableView的工作原理对于优化其性能和用户体验至关重要。当你滚动表格时,UITableView并不会为所有单元格创建视图。相反,它使用了一种叫做"单元格重用"的技术,只创建屏幕上可见的单元格,并且当这些单元格滚动出屏幕时,会将其放入一个重用队列中。当新的单元格滚动到屏幕上时,UITableView会从重用队列中取出一个单元格,并对其进行配置。这就是为什么在cellForRowAt方法中,你总是需要配置单元格的所有属性------因为你无法确定这个单元格是新的,还是从重用队列中取出的。
三、 UITableView如何优化?
了解完上面的工作原理,我们就可以从以下几个方面进行优化
● 重用单元格
● 异步加载图片
● 预估单元格高度
● 避免离屏渲染
● 避免图片尺寸不一致
● 避免动态添加内容
● 滑动按需加载
1. 重用单元格
在UITableView中,单元格的重用是提高性能的关键。通过重用单元格,我们可以避免频繁地创建和销毁单元格对象,从而减少内存分配和释放的开销。
要实现单元格的重用,我们需要注册单元格的重用标识符,并在cellForRowAtIndexPath方法中使用dequeueReusableCellWithIdentifier方法获取可重用的单元格。下面是一个简单的示例:
swift
// 注册单元格的重用标识符
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
// 在cellForRowAtIndexPath方法中获取可重用的单元格
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
// 配置单元格的数据和界面
return cell
}
2. 异步加载图片
在UITableView中加载图片时,如果图片较大或者网络加载较慢,可能会导致滚动卡顿。为了解决这个问题,我们可以使用异步加载图片的方式。
异步加载图片的核心思想是在后台线程中加载图片,然后在主线程中更新UI。这样可以避免阻塞主线程,提高滚动的流畅性。下面是一个使用SDWebImage库进行异步加载图片的示例:
swift
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let imageUrl = "https://xxxxx/xxAA.jpg"
cell.imageView?.sd_setImage(with: URL(string: imageUrl), placeholderImage: UIImage(named: "placeholder"))
return cell
}
3. 预估单元格高度
当UITableView包含可变高度的行时,如果逐行计算高度,会导致性能开销巨大,因为这种方法需要对每一行进行高度计算,从而增加了CPU和GPU的负担。因此为了优化性能,可以使用estimatedRowHeight属性来为单元格设置预估高度。
estimatedRowHeight属性允许您为UITableView设置一个估计的行高,以便在加载表格时预先分配足够的空间,而不需要逐行计算高度。通过使用estimatedRowHeight属性,您可以避免因逐行计算高度而产生的性能瓶颈,从而提高表格的滚动性能和响应速度。
需要注意的是,estimatedRowHeight只是一种估计值,实际行高可能会根据内容的不同而有所差异。因此,在使用estimatedRowHeight时,您需要根据表格中内容的特性和规律来选择一个合适的估计值,以确保表格的显示效果和性能达到一个良好的状态。
ini
tableView.estimatedRowHeight = 10.0f;//预估行高度
4. 避免离屏渲染
使用圆角、阴影等视图功能的时候,会进行离屏渲染,而离屏渲染跟当前屏幕渲染又不同。它会单独开辟一块缓冲区进行渲染操作,这个过程相对较耗资源。
(1)对于圆角较好的做法是让美术提供圆角背景图,并给Cell设成背景图片。
(2)对于阴影效果可以通过以下代码设置
ini
import UIKit
extension UIImageView {
func addShadow() {
layer.masksToBounds = false
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0, height: 0)
layer.shadowOpacity = 0.5
layer.shadowRadius = 2
layer.shadowPath = UIBezierPath(rect: bounds).cgPath
}
}
5.避免图片尺寸不一致
为了提高UITableView的性能,图片的尺寸应该与UIImageView的尺寸保持一致。通过将图片的尺寸调整为与UIImageView的尺寸相同,可以避免不必要的缩放和拉伸,从而减少计算资源和内存的占用。
此外,图片的contentMode也会对UITableView的滚动速度造成影响。如果图片的contentMode不正确,可能会导致图片加载速度变慢,从而影响应用程序的性能。因此,需要根据需要显示的图片大小来处理contentMode,以优化图片的显示效果。
5. 避免动态添加内容
在初始化UITableViewCell时,将所有需要展示的子视图添加到cell中,避免动态添加。这些子视图可以是自定义的UI元素,例如文本标签、图像视图、按钮等。
优化方法:添加子视图后,可以根据需要设置它们的隐藏属性,以控制它们的显示和隐藏。
通过在初始化时将所有子视图添加到cell中,可以确保它们在显示时已经存在,并且可以在需要时轻松地显示或隐藏它们。这种方法适用于在cell中展示动态内容的情况,例如根据数据源动态设置不同的视图内容。
需要注意的是,如果在初始化时添加了过多的子视图,可能会占用额外的内存和计算资源。因此,在添加子视图时应该考虑到这一点,并根据需要合理地管理内存和计算资源的使用。
6. 滑动按需加载
滑动按需载可分为两种:
● 分页加载
分页加载是指当用户滚动到UITableView底部时,自动加载下一页数据。这可以通过监听UITableView的滚动事件来实现。以下是一个示例代码:
swift
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offsetY = scrollView.contentOffset.y
let contentHeight = scrollView.contentSize.height
let scrollViewHeight = scrollView.frame.height
if offsetY > contentHeight - scrollViewHeight {
// 加载下一页数据
loadNextPageData()
}
}
● 滑动范围加载
滑动范围前后加载是指只在目标滚动范围的前后指定 3 行加载。这么处理是因为用户在快速滑动的过程中,大量快速滑动的内容是无用的。所以我们只需要加载最后滑动范围的内容即可。
less
// 按需加载 - 如果目标行与当前行相差超过指定行数,只在目标滚动范围的前后指定3行加载。
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
// 获取目标偏移量对应的索引路径
let ip = indexPathForRow(at: CGPoint(x: 0, y: targetContentOffset.pointee.y))
// 获取当前可见的第一个索引路径
if let cip = indexPathsForVisibleRows?.first {
let skipCount = 10 // 指定行数
// 如果目标行与当前行相差超过指定行数
if abs(cip.row - ip.row) > skipCount {
// 获取目标滚动范围内的所有索引路径
let temp = indexPathsForRows(in: CGRect(x: 0, y: targetContentOffset.pointee.y, width: self.width, height: self.height))
var arr = temp as NSArray as? [IndexPath] ?? []
// 如果滚动方向是向上
if velocity.y < 0 {
if let indexPath = temp.last, indexPath.row > 3 {
// 在目标滚动范围前添加3个索引路径
arr.append(IndexPath(row: indexPath.row-3, section: 0))
arr.append(IndexPath(row: indexPath.row-2, section: 0))
arr.append(IndexPath(row: indexPath.row-1, section: 0))
}
}
// 将需要加载的数据添加到数组中
self.needLoadDatas.append(contentsOf: arr)
}
}
}
四、 总结
本文介绍 UITableView 性能优化的思路,在具体的业务应用中,可能会面临不同的场景和不同的需求,需要具体问题具体分析,但万变不离其中,如果大家有其它优化思路、疑问或者复杂场景,欢迎评论区一起交流~