UICollectionView — bộ sưu tập và FlowLayout trong iOS được giải thích

Tác giả: IT Sectr Đã đăng: 2026-02-23 Thời gian đọc: 9 phút

UICollectionView là một thành phần UIKit mạnh mẽ cho iOS quản lý việc hiển thị các bộ sưu tập đối tượng dưới dạng lưới, danh sách và bố cục tùy chỉnh. Không giống như UITableView, UICollectionView không bị giới hạn ở một cột: nó hỗ trợ định vị phần tử tùy ý thông qua UICollectionViewLayout. Chúng tôi giải thích bản chất của UICollectionView, cách FlowLayout và CompositionalLayout hoạt động, và cách tạo ô tùy chỉnh trong Swift. Tại IT Sectr, chúng tôi sử dụng UICollectionView cho thư viện ảnh, danh mục và bảng tin. Để so sánh với bảng, hãy đọc bài viết về UITableView.

Những điểm chính

  • UICollectionView — một thành phần UIKit để hiển thị bộ sưu tập dưới dạng lưới, danh sách và bố cục tùy ý.
  • UICollectionViewFlowLayout — bố cục tiêu chuẩn hỗ trợ hướng, kích thước ô và khoảng cách.
  • UICollectionViewCompositionalLayout — bố cục hiện đại (iOS 13+) cho các phần phức tạp với khối, băng chuyền và nhóm.
  • UICollectionViewDiffableDataSource — DataSource an toàn kiểu với tính toán thay đổi tự động (iOS 13+).
  • UICollectionViewCell — ô cơ bản với backgroundView, selectedBackgroundView và contentView tích hợp.

UICollectionView trong iOS là gì?

UICollectionView là một lớp từ framework UIKit được giới thiệu trong iOS 6 (2012). Nó hiển thị một bộ sưu tập có thứ tự các ô (UICollectionViewCell) trong một bố cục linh hoạt được quản lý bởi đối tượng UICollectionViewLayout. UICollectionView giải quyết cùng vấn đề như UITableView (danh sách) nhưng với định vị phần tử tùy ý: lưới, băng chuyền, khảm, thác.

Kiến trúc UICollectionView được xây dựng trên mô hình MVC: dữ liệu trong DataSource, bố cục trong UICollectionViewLayout, hành vi trong UICollectionViewDelegate. Sự tách biệt này cho phép thay thế bố cục mà không thay đổi mã dữ liệu. Theo Apple (WWDC 2025), 72% ứng dụng iOS trong top 100 App Store sử dụng UICollectionView để hiển thị nội dung. CollectionView hỗ trợ bố cục nhiều cột từ iOS 6 và từ iOS 13 đã có CompositionalLayout cho các phần phức tạp.

Vòng đời của ô

UICollectionView sử dụng một nhóm các ô có thể tái sử dụng (reuse queue), tương tự như UITableView. Phương thức dequeueReusableCell(withReuseIdentifier:for:) trả về một ô từ nhóm hoặc tạo mới nếu nhóm trống. Không giống như UITableView, UICollectionViewCell có contentView tích hợp mà bạn nên thêm tất cả subview vào — không thêm trực tiếp vào ô, vì contentView chịu trách nhiệm cho hoạt ảnh chọn và chỉnh sửa chính xác.

FlowLayout vs CompositionalLayout: so sánh

UICollectionViewFlowLayout — bố cục tiêu chuẩn hoạt động từ iOS 6. Nó sắp xếp các phần tử theo hàng (flow) với khả năng cấu hình kích thước ô, hướng cuộn, khoảng cách tối thiểu và các phần. UICollectionViewCompositionalLayout — bố cục hiện đại (iOS 13+) mô tả bố cục thông qua sự kết hợp của các khối: nhóm → phần → bố cục, trong đó mỗi nhóm có thể ngang, dọc hoặc tùy chỉnh.

Tham sốUICollectionViewFlowLayoutUICollectionViewCompositionalLayout
Phiên bản tối thiểuiOS 6iOS 13
Bố cục phức tạpChỉ lưới hoặc danh sách đơn giảnPhần với bố cục khác nhau, nhóm, băng chuyền
Khả năng thích ứngQua kích thước ủy quyềnNSCollectionLayoutDimension.fractionalWidth/Height
Hiệu suấtKết xuất tất cả phần tử hiển thịTạo phần lười biếng
Hỗ trợ iPadYêu cầu thích ứng thủ côngNhóm thích ứng qua fractionalWidth
Độ phức tạp triển khaiThấp (5–10 dòng)Trung bình (15–30 dòng)
swift
// UICollectionView cơ bản với FlowLayout
class ViewController: UIViewController {

    private var collectionView: UICollectionView!

    override func viewDidLoad() {
        super.viewDidLoad()

        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .vertical
        layout.minimumInteritemSpacing = 8
        layout.minimumLineSpacing = 12
        layout.sectionInset = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
        layout.itemSize = CGSize(width: view.frame.width - 32, height: 100)

        collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
        collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "cell")
        collectionView.dataSource = self
        view.addSubview(collectionView)
    }
}

// DataSource
extension ViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView,
                         numberOfItemsInSection section: Int) -> Int { 20 }

    func collectionView(_ collectionView: UICollectionView,
                         cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
        as! CustomCell
        cell.configure(with: "Item \(indexPath.row)")
        return cell
    }
}

Khuyến nghị của Apple (WWDC 2025): sử dụng CompositionalLayout cho các dự án mới. Nó cung cấp nhiều khả năng hơn cho bố cục thích ứng, hoạt động tốt hơn với UIKit và SwiftUI qua UICollectionView.Representable, và hỗ trợ tiêu đề phần với kích thước tự động. Giữ FlowLayout cho các màn hình đơn giản với lưới ô giống hệt nhau và phiên bản tối thiểu iOS 12 trở xuống.

UICollectionViewCell và UICollectionViewDelegate

UICollectionViewCell — lớp cơ sở cho các ô UICollectionView. Mỗi ô chứa contentView (vùng chứa chính), backgroundView (nền mặc định), selectedBackgroundView (nền khi chọn). Cấu hình giao diện của ô bên trong contentView — không bao giờ thêm subview trực tiếp vào UICollectionViewCell.

swift
// Ô tùy chỉnh với cấu hình
class ProductCell: UICollectionViewCell {

    private let imageView = UIImageView()
    private let titleLabel = UILabel()
    private let priceLabel = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupViews()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func setupViews() {
        contentView.addSubview(imageView)
        contentView.addSubview(titleLabel)
        contentView.addSubview(priceLabel)

        // Ràng buộc Auto Layout
        imageView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            imageView.topAnchor.constraint(equalTo: contentView.topAnchor),
            imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor),
            titleLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 8),
            titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8),
            titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8),
            priceLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4),
            priceLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8)
        ])
    }

    func configure(with product: Product) {
        imageView.image = UIImage(named: product.imageName)
        titleLabel.text = product.title
        priceLabel.text = product.price
    }
}

UICollectionViewDelegate cung cấp các phương thức để xử lý lựa chọn, làm nổi bật, menu ngữ cảnh và sắp xếp lại. Không giống như UITableView, UICollectionViewDelegate cho phép kiểm soát không chỉ thao tác chạm vào phần tử mà còn cả nhấn giữ (menu ngữ cảnh qua UIContextMenuInteraction). Để xử lý thao tác chạm, hãy triển khai collectionView(_:didSelectItemAt:) — nó được gọi khi hoạt ảnh lựa chọn hoàn tất.

UICollectionViewDiffableDataSource: DataSource hiện đại

UICollectionViewDiffableDataSource — sự thay thế an toàn kiểu cho UICollectionViewDataSource được giới thiệu trong iOS 13. Thay vì quản lý ô thủ công qua cellForItemAt, DiffableDataSource sử dụng NSDiffableDataSourceSnapshot — ảnh chụp nhanh trạng thái dữ liệu tự động tính toán sự khác biệt giữa trạng thái cũ và mới và tạo hoạt ảnh cho các thay đổi.

swift
// DiffableDataSource với các phần
enum Section {
    case featured, recommended, categories
}

class ModernDataSource: UIViewController {

    private var dataSource: UICollectionViewDiffableDataSource<Section, Product>!

    override func viewDidLoad() {
        super.viewDidLoad()
        configureDataSource()
        applyInitialSnapshot()
    }

    private func configureDataSource() {
        let cellRegistration = UICollectionView.CellRegistration<ProductCell, Product> { cell, _, product in
            cell.configure(with: product)
        }

        dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) {
            collectionView, indexPath, product in
            collectionView.dequeueConfiguredReusableCell(
                using: cellRegistration, for: indexPath, item: product
            )
        }
    }

    private func applyInitialSnapshot() {
        var snapshot = NSDiffableDataSourceSnapshot<Section, Product>()
        snapshot.appendSections([.featured, .recommended, .categories])
        snapshot.appendItems(featuredProducts, toSection: .featured)
        snapshot.appendItems(recommendedProducts, toSection: .recommended)
        snapshot.appendItems(categories, toSection: .categories)
        dataSource.apply(snapshot, animatingDifferences: true)
    }

    // Cập nhật với hoạt ảnh tự động
    func updateProducts(_ products: [Product]) {
        var snapshot = dataSource.snapshot()
        snapshot.deleteItems(snapshot.itemIdentifiers)
        snapshot.appendItems(products)
        dataSource.apply(snapshot, animatingDifferences: true)
    }
}

Lợi ích của DiffableDataSource: (1) hoạt ảnh tự động — insert, delete, reload xảy ra với hoạt ảnh mặc định; (2) an toàn kiểu — các phần và mục được định kiểu, lỗi biên dịch thay vì sự cố runtime; (3) Snapshot — ảnh chụp nhanh trạng thái có thể được lưu, hoàn tác, so sánh; (4) hiệu suất — DiffableDataSource sử dụng thuật toán Hellier để tính toán tập hợp thay đổi tối thiểu. Apple (WWDC 2024) khuyến nghị DiffableDataSource cho tất cả UICollectionView mới và chỉ giữ DataSource cũ cho iOS 12 trở về trước.

Tạo bố cục tùy chỉnh qua UICollectionViewLayout

UICollectionViewLayout — một lớp trừu tượng kiểm soát hoàn toàn vị trí của các phần tử bộ sưu tập. Nếu FlowLayout và CompositionalLayout không phù hợp (ví dụ: bố cục hình tròn, xoắn ốc, biểu đồ), hãy tạo lớp con của UICollectionViewLayout và ghi đè các phương thức chuẩn bị bố cục.

swift
// Bố cục hình tròn tùy chỉnh
class CircularLayout: UICollectionViewLayout {

    private var attributesCache: [UICollectionViewLayoutAttributes] = []

    private let radius: CGFloat = 120

    override func prepare() {
        super.prepare()
        attributesCache.removeAll()

        guard let collectionView else { return }
        let count = collectionView.numberOfItems(inSection: 0)
        let center = CGPoint(x: collectionView.bounds.midX, y: collectionView.bounds.midY)

        for i in 0..<count {
            let indexPath = IndexPath(item: i, section: 0)
            let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
            let angle = (CGFloat(i) / CGFloat(count)) * 2 * .pi

            attributes.center = CGPoint(
                x: center.x + radius * cos(angle),
                y: center.y + radius * sin(angle)
            )
            attributes.size = CGSize(width: 60, height: 60)
            attributesCache.append(attributes)
        }
    }

    override var collectionViewContentSize: CGSize {
        collectionView?.bounds.size ?? .zero
    }

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        attributesCache.filter { $0.frame.intersects(rect) }
    }

    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        attributesCache[indexPath.item]
    }
}

Hiệu suất: UICollectionViewLayout tùy chỉnh nên lưu trữ thuộc tính trong prepare() và trả về chúng trong O(1) tại layoutAttributesForItem(at:). Đối với bộ sưu tập có 1000+ phần tử, hãy sử dụng cập nhật bộ nhớ đệm gia tăng qua shouldInvalidateLayout(forBoundsChange:) — điều này tránh tính toán lại toàn bộ bố cục khi cuộn. Apple (WWDC 2023) khuyến nghị CompositionalLayout thay vì bố cục tùy chỉnh: nó bao phủ 95% tình huống mà không cần viết bố cục từ đầu.

UICollectionView vs UITableView: khi nào chọn cái gì

UITableViewUICollectionView là hai thành phần UIKit chính để hiển thị danh sách. UITableView đơn giản hơn và được thiết kế cho danh sách một cột. UICollectionView linh hoạt hơn và hỗ trợ bất kỳ bố cục nào. Sự lựa chọn giữa chúng phụ thuộc vào cấu trúc dữ liệu trực quan và mức độ tùy chỉnh yêu cầu.

Tham sốUITableViewUICollectionView
Bố cụcChỉ danh sách dọcDanh sách, lưới, thác, băng chuyền, tùy chỉnh
Độ phức tạpThấp (DataSource đơn giản)Trung bình (Layout + DataSource + Delegate)
Ô mặc định4 kiểu (basic, subtitle, value1, value2)Chỉ trống (tùy chỉnh qua contentView)
Chế độ chỉnh sửaTích hợp (delete, move, insert)Yêu cầu triển khai thủ công
Header/FooterTích hợp (viewForHeaderInSection)Qua supplementary views
Hiệu suấtCao cho danh sáchCao cho bất kỳ bố cục nào
iOS 6+Có sẵn từ iOS 2Có sẵn từ iOS 6

Quy tắc thực tế: sử dụng UITableView nếu màn hình là danh sách hàng đơn giản (cài đặt, danh bạ, menu). Sử dụng UICollectionView nếu màn hình yêu cầu lưới, cuộn ngang, băng chuyền, kích thước ô khác nhau hoặc nhóm phức tạp. Từ iOS 14, UITableView cũng đã hỗ trợ CompositionalLayout qua UICollectionLayoutListConfiguration, cho phép kết hợp lợi ích của cả hai thành phần.

Câu hỏi thường gặp

Làm thế nào để tạo UICollectionView với cuộn ngang?

Đặt scrollDirection = .horizontal trong UICollectionViewFlowLayout. Đối với CompositionalLayout, sử dụng NSCollectionLayoutSize với fractionalWidth cho chiều rộng nhóm thích ứng. Cuộn ngang phù hợp cho băng chuyền, thư viện ảnh và danh mục. Để phân trang, đặt collectionView.isPagingEnabled = true hoặc sử dụng UICollectionViewFlowLayout với itemSize = bounds.width.

Tại sao UICollectionView không hiển thị ô?

Kiểm tra: (1) ô đã được đăng ký qua register(_:forCellWithReuseIdentifier:), (2) DataSource trả về số lượng mục > 0, (3) ô được dequeue với reuseIdentifier đúng, (4) itemSize không bằng 0 (cho FlowLayout). Một lỗi điển hình là itemSize = CGSize.zero, làm cho ô có chiều cao bằng không. Đặt itemSize rõ ràng hoặc triển khai ủy quyền collectionView(_:layout:sizeForItemAt:).

Làm thế nào để thêm khoảng cách giữa các ô UICollectionView?

Đối với FlowLayout: layout.minimumInteritemSpacing (giữa các mục trong một hàng) và layout.minimumLineSpacing (giữa các hàng). Đối với CompositionalLayout: NSCollectionLayoutGroup.interItemSpacingcontentInsets trên nhóm/phần. sectionInset đặt lề bên ngoài của phần. Sử dụng sectionInsetReference = .fromContentInset để xử lý Safe Area chính xác.

Làm thế nào để cập nhật UICollectionView mà không tải lại?

Sử dụng performBatchUpdates cho các hoạt ảnh nhóm: collectionView.performBatchUpdates { insertItems, deleteItems, reloadItems }. Để cập nhật an toàn và có hoạt ảnh, sử dụng UICollectionViewDiffableDataSource: gọi dataSource.apply(snapshot, animatingDifferences: true) — tất cả thay đổi sẽ được tính toán và áp dụng tự động mà không cần insert/delete thủ công.

UICollectionView vs CollectionView trong SwiftUI?

Trong SwiftUI, tương đương với UICollectionView là LazyVGridLazyHGrid (iOS 14+). SwiftUI Grid dễ viết hơn (cú pháp khai báo) nhưng kém hơn UICollectionView về hiệu suất cho 500+ phần tử và tùy chỉnh bố cục. Đối với bộ sưu tập phức tạp, hãy sử dụng UICollectionView qua UIViewRepresentable. Trong các dự án IT Sectr, chúng tôi chọn UICollectionView cho danh mục và thư viện ảnh, và SwiftUI Grid cho các màn hình đơn giản.

Tóm tắt

  • UICollectionView — một thành phần UIKit linh hoạt cho lưới, danh sách, băng chuyền và bố cục tùy chỉnh (iOS 6+).
  • FlowLayout phù hợp cho lưới đơn giản; CompositionalLayout (iOS 13+) cho các phần phức tạp với bố cục khác nhau.
  • UICollectionViewCell sử dụng contentView cho tất cả subview — không thêm trực tiếp vào ô.
  • UICollectionViewDiffableDataSource — DataSource hiện đại an toàn kiểu với hoạt ảnh thay đổi tự động.
  • UICollectionViewLayout tùy chỉnh cho phép tạo bố cục tùy ý (hình tròn, xoắn ốc, biểu đồ).
  • 72% ứng dụng iOS trong top 100 App Store sử dụng UICollectionView để hiển thị nội dung.
  • Đối với danh sách đơn giản, sử dụng UITableView; cho mọi thứ khác, sử dụng UICollectionView.

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm