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 — مصدر بيانات آمن الأنواع مع حساب تلقائي للتغييرات (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 ضد 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: مصدر بيانات حديث

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) أمان الأنواع — الأقسام والعناصر مقيدة بأنواع، أخطاء في الترجمة بدلاً من أعطال وقت التشغيل؛ (3) Snapshot — يمكن حفظ لقطة الحالة وتراجعها ومقارنتها؛ (4) الأداء — يستخدم DiffableDataSource خوارزمية Hellier لحساب الحد الأدنى من التغييرات. توصي Apple (WWDC 2024) باستخدام DiffableDataSource لجميع UICollectionView الجديدة، والاحتفاظ بـ DataSource القديم فقط لإصدار iOS 12 والأقدم.

إنشاء تخطيط مخصص عبر 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 ضد UITableView: متى تختار ماذا

UITableView و UICollectionView هما المكونان الرئيسيان في UIKit لعرض القوائم. UITableView أبسط ومصمم للقوائم ذات العمود الواحد. UICollectionView أكثر مرونة ويدعم أي تخطيط. يعتمد الاختيار بينهما على الهيكل المرئي للبيانات والتخصيص المطلوب.

المعاملUITableViewUICollectionView
التخطيطقائمة عمودية فقطقائمة، شبكة، شلال، عربة دوارة، مخصص
التعقيدمنخفض (DataSource بسيط)متوسط (Layout + DataSource + Delegate)
الخلية الافتراضية4 أنماط (basic, subtitle, value1, value2)فارغة فقط (مخصصة عبر contentView)
وضع التحريرمدمج (delete, move, insert)يتطلب تنفيذًا يدويًا
Header/Footerمدمجة (viewForHeaderInSection)عرض 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) تم سحب الخلية باستخدام 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 ضد 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 — مصدر بيانات حديث وآمن الأنواع مع رسوم متحركة تلقائية للتغييرات.
  • UICollectionViewLayout المخصص يسمح بإنشاء تخطيطات عشوائية (دائرة، حلزون، رسم بياني).
  • 72% من تطبيقات iOS في أفضل 100 في App Store تستخدم UICollectionView لعرض المحتوى.
  • للقوائم البسيطة، استخدم UITableView؛ لكل شيء آخر، استخدم UICollectionView.

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا