UICollectionView — คอลเลกชันและ FlowLayout ใน iOS อธิบาย

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-02-23 เวลาอ่าน: 9 นาที

UICollectionView เป็นคอมโพเนนต์ UIKit ที่ทรงพลังสำหรับ iOS ที่จัดการการแสดงคอลเลกชันของวัตถุในรูปแบบกริด รายการ และเลย์เอาต์ที่กำหนดเอง ซึ่งแตกต่างจาก UITableView ตรงที่ UICollectionView ไม่จำกัดอยู่เพียงคอลัมน์เดียว: รองรับการวางตำแหน่งองค์ประกอบตามต้องการผ่าน UICollectionViewLayout เราอธิบายแก่นแท้ของ UICollectionView วิธีที่ FlowLayout และ CompositionalLayout ทำงาน และวิธีสร้างเซลล์ที่กำหนดเองใน Swift ที่ IT Sectr เราใช้ UICollectionView สำหรับแกลเลอรี แคตตาล็อก และกระดานประกาศ สำหรับการเปรียบเทียบกับตาราง อ่าน บทความเกี่ยวกับ UITableView

ประเด็นสำคัญ

  • UICollectionView — คอมโพเนนต์ UIKit สำหรับแสดงคอลเลกชันในรูปแบบกริด รายการ และเลย์เอาต์ตามต้องการ
  • UICollectionViewFlowLayout — เลย์เอาต์มาตรฐานที่รองรับทิศทาง ขนาดเซลล์ และระยะห่าง
  • UICollectionViewCompositionalLayout — เลย์เอาต์ที่ทันสมัย (iOS 13+) สำหรับส่วนที่ซับซ้อนพร้อมบล็อก แครอเซล และกลุ่ม
  • UICollectionViewDiffableDataSource — DataSource ที่ปลอดภัยชนิดพร้อมการคำนวณการเปลี่ยนแปลงอัตโนมัติ (iOS 13+)
  • UICollectionViewCell — เซลล์พื้นฐานที่มี backgroundView, selectedBackgroundView และ contentView ในตัว

UICollectionView ใน iOS คืออะไร?

UICollectionView เป็นคลาสจากเฟรมเวิร์ก UIKit ที่เปิดตัวใน iOS 6 (2012) มันแสดงคอลเลกชันที่เรียงลำดับของเซลล์ (UICollectionViewCell) ในเลย์เอาต์ที่ยืดหยุ่นซึ่งจัดการโดยวัตถุ UICollectionViewLayout UICollectionView แก้ปัญหาเดียวกันกับ UITableView (รายการ) แต่มีการวางตำแหน่งองค์ประกอบตามต้องการ: กริด แครอเซล โมเสก น้ำตก

สถาปัตยกรรม UICollectionView สร้างขึ้นบนรูปแบบ MVC: ข้อมูลใน DataSource เลย์เอาต์ใน UICollectionViewLayout พฤติกรรมใน UICollectionViewDelegate การแยกนี้ช่วยให้สามารถเปลี่ยนเลย์เอาต์ได้โดยไม่ต้องเปลี่ยนโค้ดข้อมูล ตามข้อมูลของ Apple (WWDC 2025) 72% ของแอป iOS ใน 100 อันดับแรกของ App Store ใช้ UICollectionView สำหรับการแสดงเนื้อหา CollectionView รองรับเลย์เอาต์หลายคอลัมน์ตั้งแต่ iOS 6 และตั้งแต่ iOS 13 ก็ได้รับ CompositionalLayout สำหรับส่วนที่ซับซ้อน

วงจรชีวิตของเซลล์

UICollectionView ใช้พูลของเซลล์ที่ใช้ซ้ำได้ (reuse queue) คล้ายกับ UITableView เมธอด dequeueReusableCell(withReuseIdentifier:for:) จะคืนค่าเซลล์จากพูลหรือสร้างใหม่หากพูลว่างเปล่า ซึ่งแตกต่างจาก UITableView ตรงที่ UICollectionViewCell มี contentView ในตัวซึ่งคุณควรเพิ่มซับวิวทั้งหมด — อย่าเพิ่มลงในเซลล์โดยตรง เนื่องจาก contentView รับผิดชอบอนิเมชันการเลือกและการแก้ไขที่ถูกต้อง

FlowLayout vs CompositionalLayout: การเปรียบเทียบ

UICollectionViewFlowLayout — เลย์เอาต์มาตรฐานที่ทำงานตั้งแต่ iOS 6 มันจัดเรียงองค์ประกอบเป็นแถว (flow) โดยมีความสามารถในการกำหนดค่าขนาดเซลล์ ทิศทางการเลื่อน ระยะห่างขั้นต่ำ และส่วนต่างๆ UICollectionViewCompositionalLayout — เลย์เอาต์ที่ทันสมัย (iOS 13+) ที่อธิบายเลย์เอาต์ผ่านการรวมกันของบล็อก: กลุ่ม → ส่วน → เลย์เอาต์ โดยแต่ละกลุ่มสามารถเป็นแนวนอน แนวตั้ง หรือกำหนดเอง

พารามิเตอร์UICollectionViewFlowLayoutUICollectionViewCompositionalLayout
เวอร์ชันขั้นต่ำiOS 6iOS 13
เลย์เอาต์ที่ซับซ้อนเฉพาะกริดหรือรายการธรรมดาส่วนที่มีเลย์เอาต์ต่างกัน กลุ่ม แครอเซล
ความสามารถในการปรับเปลี่ยนผ่านขนาดดีลิเกตNSCollectionLayoutDimension.fractionalWidth/Height
ประสิทธิภาพเรนเดอร์องค์ประกอบที่มองเห็นทั้งหมดการสร้างส่วนแบบขี้เกียจ
รองรับ iPadต้องการการปรับเปลี่ยนด้วยตนเองกลุ่มแบบปรับได้ผ่าน fractionalWidth
ความซับซ้อนในการนำไปใช้ต่ำ (5–10 บรรทัด)ปานกลาง (15–30 บรรทัด)
swift
// UICollectionView พื้นฐานกับ 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
    }
}

คำแนะนำของ Apple (WWDC 2025): ใช้ CompositionalLayout สำหรับโปรเจกต์ใหม่ มันให้ความสามารถมากขึ้นสำหรับเลย์เอาต์แบบปรับได้ ทำงานได้ดีกว่ากับ UIKit และ SwiftUI ผ่าน UICollectionView.Representable และรองรับส่วนหัวของส่วนที่มีขนาดอัตโนมัติ เก็บ FlowLayout ไว้สำหรับหน้าจอธรรมดาที่มีกริดของเซลล์ที่เหมือนกันและเวอร์ชันขั้นต่ำ iOS 12 และต่ำกว่า

UICollectionViewCell และ UICollectionViewDelegate

UICollectionViewCell — คลาสพื้นฐานสำหรับเซลล์ UICollectionView แต่ละเซลล์ประกอบด้วย contentView (คอนเทนเนอร์หลัก), backgroundView (พื้นหลังเริ่มต้น), selectedBackgroundView (พื้นหลังเมื่อเลือก) กำหนดลักษณะของเซลล์ภายใน contentView — อย่าเพิ่มซับวิวลงใน UICollectionViewCell โดยตรง

swift
// เซลล์ที่กำหนดเองพร้อมการกำหนดค่า
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)

        // ข้อจำกัดของ 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 จัดเตรียมเมธอดสำหรับจัดการการเลือก การเน้น เมนูบริบท และการจัดลำดับใหม่ ซึ่งแตกต่างจาก UITableView ตรงที่ UICollectionViewDelegate อนุญาตให้ควบคุมไม่เพียงแต่การแตะองค์ประกอบเท่านั้น แต่ยังรวมถึงการกดค้าง (เมนูบริบทผ่าน UIContextMenuInteraction) ในการจัดการการแตะ ให้ใช้ collectionView(_:didSelectItemAt:) — มันจะถูกเรียกเมื่ออนิเมชันการเลือกเสร็จสมบูรณ์

UICollectionViewDiffableDataSource: DataSource ที่ทันสมัย

UICollectionViewDiffableDataSource — ตัวแทนที่ปลอดภัยชนิดสำหรับ UICollectionViewDataSource ที่เปิดตัวใน iOS 13 แทนที่จะจัดการเซลล์ด้วยตนเองผ่าน cellForItemAt DiffableDataSource ใช้ NSDiffableDataSourceSnapshot — ภาพรวมสถานะข้อมูลที่คำนวณความแตกต่างระหว่างสถานะเก่าและใหม่โดยอัตโนมัติและทำให้การเปลี่ยนแปลงเป็นอนิเมชัน

swift
// DiffableDataSource พร้อมส่วน
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)
    }

    // อัปเดตด้วยอนิเมชันอัตโนมัติ
    func updateProducts(_ products: [Product]) {
        var snapshot = dataSource.snapshot()
        snapshot.deleteItems(snapshot.itemIdentifiers)
        snapshot.appendItems(products)
        dataSource.apply(snapshot, animatingDifferences: true)
    }
}

ข้อดีของ DiffableDataSource: (1) อนิเมชันอัตโนมัติ — insert, delete, reload เกิดขึ้นพร้อมอนิเมชันเริ่มต้น; (2) ความปลอดภัยของชนิด — ส่วนและรายการถูกกำหนดชนิด ข้อผิดพลาดตอนคอมไพล์แทนที่จะเกิดครา�

การสร้างเลย์เอาต์ที่กำหนดเองผ่าน UICollectionViewLayout

UICollectionViewLayout — คลาสนามธรรมที่ควบคุมตำแหน่งขององค์ประกอบคอลเลกชันอย่างสมบูรณ์ หาก FlowLayout และ CompositionalLayout ไม่เหมาะสม (เช่น เลย์เอาต์วงกลม เกลียว แผนภาพ) ให้สร้างคลาสย่อยของ UICollectionViewLayout และแทนที่เมธอดการเตรียมเลย์เอาต์

swift
// เลย์เอาต์วงกลมที่กำหนดเอง
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]
    }
}

ประสิทธิภาพ: UICollectionViewLayout ที่กำหนดเองควรแคชแอตทริบิวต์ใน prepare() และส่งคืนใน O(1) ใน layoutAttributesForItem(at:) สำหรับคอลเลกชันที่มีมากกว่า 1000 องค์ประกอบ ให้ใช้การอัปเดตแคชแบบเพิ่มหน่วยผ่าน shouldInvalidateLayout(forBoundsChange:) — ซึ่งจะหลีกเลี่ยงการคำนวณเลย์เอาต์ทั้งหมดใหม่เมื่อเลื่อน Apple (WWDC 2023) แนะนำ CompositionalLayout แทนเลย์เอาต์ที่กำหนดเอง: ครอบคลุม 95% ของสถานการณ์โดยไม่ต้องเขียนเลย์เอาต์จากศูนย์

UICollectionView vs UITableView: เมื่อใดควรเลือกอะไร

UITableView และ UICollectionView เป็นคอมโพเนนต์ UIKit หลักสองตัวสำหรับแสดงรายการ UITableView ง่ายกว่าและออกแบบมาสำหรับรายการคอลัมน์เดียว UICollectionView ยืดหยุ่นกว่าและรองรับเลย์เอาต์ใด ๆ การเลือกระหว่างพวกเขาขึ้นอยู่กับโครงสร้างข้อมูลที่มองเห็นและการปรับแต่งที่ต้องการ

พารามิเตอร์UITableViewUICollectionView
เลย์เอาต์เฉพาะรายการแนวตั้งรายการ กริด น้ำตก แครอเซล กำหนดเอง
ความซับซ้อนต่ำ (DataSource ธรรมดา)ปานกลาง (Layout + DataSource + Delegate)
เซลล์เริ่มต้น4 สไตล์ (basic, subtitle, value1, value2)ว่างเท่านั้น (กำหนดเองผ่าน contentView)
โหมดแก้ไขในตัว (delete, move, insert)ต้องการการนำไปใช้ด้วยตนเอง
Header/Footerในตัว (viewForHeaderInSection)ผ่าน supplementary views
ประสิทธิภาพสูงสำหรับรายการสูงสำหรับเลย์เอาต์ใด ๆ
iOS 6+พร้อมใช้งานตั้งแต่ iOS 2พร้อมใช้งานตั้งแต่ iOS 6

กฎปฏิบัติ: ใช้ UITableView หากหน้าจอเป็นรายการแถวธรรมดา (การตั้งค่า รายชื่อ เมนู) ใช้ UICollectionView หากหน้าจอต้องการกริด การเลื่อนแนวนอน แครอเซล ขนาดเซลล์ต่างกัน หรือการจัดกลุ่มที่ซับซ้อน ตั้งแต่ iOS 14 เป็นต้นมา UITableView ก็รองรับ CompositionalLayout ผ่าน UICollectionLayoutListConfiguration ซึ่งช่วยให้รวมข้อดีของทั้งสองคอมโพเนนต์

คำถามที่พบบ่อย

วิธีสร้าง UICollectionView ด้วยการเลื่อนแนวนอน?

ตั้งค่า scrollDirection = .horizontal ใน UICollectionViewFlowLayout สำหรับ CompositionalLayout ให้ใช้ NSCollectionLayoutSize ด้วย fractionalWidth สำหรับความกว้างกลุ่มที่ปรับได้ การเลื่อนแนวนอนเหมาะสำหรับแครอเซล แกลเลอรี และหมวดหมู่ สำหรับการเลื่อนแบบหน้า ให้ตั้งค่า collectionView.isPagingEnabled = true หรือใช้ UICollectionViewFlowLayout ด้วย itemSize = bounds.width

ทำไม UICollectionView ไม่แสดงเซลล์?

ตรวจสอบ: (1) เซลล์ถูกลงทะเบียนผ่าน register(_:forCellWithReuseIdentifier:), (2) DataSource ส่งคืนจำนวนรายการ > 0, (3) เซลล์ถูก dequeue ด้วย reuseIdentifier ที่ถูกต้อง, (4) itemSize ไม่เป็นศูนย์ (สำหรับ FlowLayout) ข้อผิดพลาดทั่วไปคือ itemSize = CGSize.zero ซึ่งทำให้เซลล์มีความสูงเป็นศูนย์ ตั้งค่า itemSize อย่างชัดเจนหรือใช้ดีลิเกต collectionView(_:layout:sizeForItemAt:)

วิธีเพิ่มระยะห่างระหว่างเซลล์ UICollectionView?

สำหรับ FlowLayout: layout.minimumInteritemSpacing (ระหว่างรายการในแถว) และ layout.minimumLineSpacing (ระหว่างแถว) สำหรับ CompositionalLayout: NSCollectionLayoutGroup.interItemSpacing และ contentInsets บนกลุ่ม/ส่วน sectionInset กำหนดขอบเขตภายนอกของส่วน ใช้ sectionInsetReference = .fromContentInset สำหรับการจัดการ Safe Area ที่ถูกต้อง

วิธีอัปเดต UICollectionView โดยไม่โหลดซ้ำ?

ใช้ performBatchUpdates สำหรับอนิเมชันกลุ่ม: collectionView.performBatchUpdates { insertItems, deleteItems, reloadItems } สำหรับการอัปเดตที่ปลอดภัยและมีอนิเมชัน ให้ใช้ UICollectionViewDiffableDataSource: เรียก dataSource.apply(snapshot, animatingDifferences: true) — การเปลี่ยนแปลงทั้งหมดจะถูกคำนวณและนำไปใช้โดยอัตโนมัติโดยไม่ต้อง insert/delete ด้วยตนเอง

UICollectionView vs CollectionView ใน SwiftUI?

ใน SwiftUI สิ่งที่เทียบเท่ากับ UICollectionView คือ LazyVGrid และ LazyHGrid (iOS 14+) SwiftUI Grid เขียนง่ายกว่า (ไวยากรณ์แบบประกาศ) แต่ด้อยกว่า UICollectionView ในประสิทธิภาพสำหรับ 500+ องค์ประกอบและการปรับแต่งเลย์เอาต์ สำหรับคอลเลกชันที่ซับซ้อน ให้ใช้ UICollectionView ผ่าน UIViewRepresentable ในโปรเจกต์ IT Sectr เราเลือก UICollectionView สำหรับแคตตาล็อกและแกลเลอรี และ SwiftUI Grid สำหรับหน้าจอธรรมดา

สรุป

  • UICollectionView — คอมโพเนนต์ UIKit ที่ยืดหยุ่นสำหรับกริด รายการ แครอเซล และเลย์เอาต์ที่กำหนดเอง (iOS 6+)
  • FlowLayout เหมาะสำหรับกริดธรรมดา; CompositionalLayout (iOS 13+) สำหรับส่วนที่ซับซ้อนด้วยเลย์เอาต์ต่างกัน
  • UICollectionViewCell ใช้ contentView สำหรับซับวิวทั้งหมด — อย่าเพิ่มลงในเซลล์โดยตรง
  • UICollectionViewDiffableDataSource — DataSource ที่ปลอดภัยชนิดที่ทันสมัยพร้อมอนิเมชันการเปลี่ยนแปลงอัตโนมัติ
  • UICollectionViewLayout ที่กำหนดเอง ช่วยให้สร้างเลย์เอาต์ตามต้องการ (วงกลม เกลียว แผนภาพ)
  • 72% ของแอป iOS ใน 100 อันดับแรกของ App Store ใช้ UICollectionView สำหรับการแสดงเนื้อหา
  • สำหรับรายการธรรมดา ใช้ UITableView; สำหรับทุกสิ่งอื่น ใช้ UICollectionView

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม