12. Kingfisher + SnapKit 列表模板
模板目标:列表滚动稳定、图片加载不抖动、复用不串图。
一、安装(SPM)
https://github.com/onevcat/Kingfisher
https://github.com/SnapKit/SnapKit
二、数据模型
struct FeedItem {
let id: Int
let title: String
let subtitle: String
let coverURL: URL?
}
三、Cell(SnapKit 布局 + Kingfisher 加载)
import UIKit
import SnapKit
import Kingfisher
final class FeedCell: UITableViewCell {
private let cover = UIImageView()
private let titleLabel = UILabel()
private let subtitleLabel = UILabel()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
cover.contentMode = .scaleAspectFill
cover.clipsToBounds = true
cover.layer.cornerRadius = 8
titleLabel.font = .systemFont(ofSize: 16, weight: .bold)
subtitleLabel.font = .systemFont(ofSize: 13)
subtitleLabel.textColor = .darkGray
subtitleLabel.numberOfLines = 2
contentView.addSubview(cover)
contentView.addSubview(titleLabel)
contentView.addSubview(subtitleLabel)
cover.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(12)
make.top.equalToSuperview().offset(12)
make.width.height.equalTo(72)
make.bottom.lessThanOrEqualToSuperview().inset(12)
}
titleLabel.snp.makeConstraints { make in
make.leading.equalTo(cover.snp.trailing).offset(12)
make.trailing.equalToSuperview().inset(12)
make.top.equalTo(cover.snp.top)
}
subtitleLabel.snp.makeConstraints { make in
make.leading.trailing.equalTo(titleLabel)
make.top.equalTo(titleLabel.snp.bottom).offset(6)
make.bottom.lessThanOrEqualToSuperview().inset(12)
}
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
override func prepareForReuse() {
super.prepareForReuse()
cover.kf.cancelDownloadTask()
cover.image = nil
titleLabel.text = nil
subtitleLabel.text = nil
}
func configure(_ item: FeedItem) {
titleLabel.text = item.title
subtitleLabel.text = item.subtitle
let processor = DownsamplingImageProcessor(size: CGSize(width: 72, height: 72))
cover.kf.setImage(
with: item.coverURL,
placeholder: UIImage(named: "placeholder"),
options: [
.processor(processor),
.cacheOriginalImage,
.transition(.fade(0.2))
]
)
}
}
四、列表控制器(预加载)
final class FeedVC: UIViewController, UITableViewDataSource, UITableViewDelegate, UITableViewDataSourcePrefetching {
private let tableView = UITableView()
private var items: [FeedItem] = []
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
tableView.register(FeedCell.self, forCellReuseIdentifier: "FeedCell")
tableView.dataSource = self
tableView.delegate = self
tableView.prefetchDataSource = self
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 96
view.addSubview(tableView)
tableView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
loadData()
}
private func loadData() {
items = [
FeedItem(id: 1, title: "图文列表", subtitle: "列表加载模板", coverURL: URL(string: "https://example.com/a.png")),
FeedItem(id: 2, title: "图片优化", subtitle: "缩略图与缓存", coverURL: URL(string: "https://example.com/b.png"))
]
tableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FeedCell", for: indexPath) as! FeedCell
cell.configure(items[indexPath.row])
return cell
}
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
let urls = indexPaths.compactMap { items[$0.row].coverURL }
ImagePrefetcher(urls: urls).start()
}
}
这套模板解决了列表中最常见的图片错位与卡顿问题,直接复用即可。