精选文章

12. Kingfisher + SnapKit 列表模板

2018-10-01 · Kingfisher

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()
    }
}

这套模板解决了列表中最常见的图片错位与卡顿问题,直接复用即可。

JJ

作者简介

专注于内容创作、产品策略与设计实践。欢迎交流合作。

上一篇 下一篇