UICollectionView — کامپوننت قدرتمند UIKit برای iOS که نمایش مجموعهای از اشیاء را به صورت شبکهها، لیستها و طرحهای سفارشی مدیریت میکند. برخلاف UITableView، UICollectionView به یک ستون محدود نمیشود: از چیدمان دلخواه عناصر از طریق UICollectionViewLayout پشتیبانی میکند. ماهیت UICollectionView، نحوه کار FlowLayout و CompositionalLayout و نحوه ایجاد سلولهای سفارشی در Swift را توضیح میدهیم. در IT Sectr از UICollectionView برای گالریها، کاتالوگها و تابلوهای اعلانات استفاده میکنیم. برای مقایسه با جداول، مقاله UITableView را بخوانید.
نکات اصلی
UICollectionView — کلاسی از فریمورک UIKit است که در iOS 6 (2012) معرفی شد. این کلاس مجموعه مرتبی از سلولها (UICollectionViewCell) را در یک طرح منعطف که توسط شی UICollectionViewLayout مدیریت میشود نمایش میدهد. UICollectionView همان وظیفه UITableView (لیستها) را حل میکند، اما با چیدمان دلخواه عناصر: شبکهها، چرخفلکها، موزاییکها، آبشاریها.
معماری UICollectionView بر اساس الگوی MVC ساخته شده است: دادهها — در DataSource، طرح — در UICollectionViewLayout، رفتار — در UICollectionViewDelegate. این تقسیمبندی امکان تعویض طرحها را بدون تغییر کد دادهها فراهم میکند. به گفته اپل (WWDC 2025)، 72% از برنامههای iOS در ۱۰۰ اپ برتر App Store از UICollectionView برای نمایش محتوا استفاده میکنند. CollectionView از iOS 6 از طرحهای چندستونی پشتیبانی میکند و از iOS 13 CompositionalLayout را برای بخشهای پیچیده دریافت کرد.
UICollectionView مانند UITableView از یک مخزن سلولهای قابل استفاده مجدد (reuse queue) استفاده میکند. متد dequeueReusableCell(withReuseIdentifier:for:) یک سلول از مخزن برمیگرداند یا اگر مخزن خالی باشد سلول جدیدی ایجاد میکند. برخلاف UITableView، سلول UICollectionViewCell دارای contentView داخلی است که همه زیرنمایها باید به آن اضافه شوند — آنها را مستقیماً به سلول اضافه نکنید، زیرا contentView مسئول انیمیشن صحیح انتخاب و ویرایش است.
UICollectionViewFlowLayout — طرح استانداردی که از iOS 6 کار میکند. عناصر را در ردیفها (flow) با امکان تنظیم اندازه سلول، جهت اسکرول، حداقل فاصلهها و بخشها قرار میدهد. UICollectionViewCompositionalLayout — طرح مدرن (iOS 13+) که طرح را از طریق ترکیب بلوکها توصیف میکند: گروه ← بخش ← طرح، که در آن هر گروه میتواند افقی، عمودی یا سفارشی باشد.
| پارامتر | UICollectionViewFlowLayout | UICollectionViewCompositionalLayout |
|---|---|---|
| حداقل نسخه | iOS 6 | iOS 13 |
| طرحهای پیچیده | فقط شبکه یا لیست ساده | بخشهایی با طرحهای مختلف، گروهها، چرخفلکها |
| سازگاری | از طریق اندازه دلیگیت | NSCollectionLayoutDimension.fractionalWidth/Height |
| عملکرد | تمام عناصر قابل مشاهده را رندر میکند | ایجاد تنبل بخشها |
| پشتیبانی iPad | نیاز به تطبیق دستی دارد | گروههای تطبیقی از طریق fractionalWidth |
| سختی نوشتن | کم (۵–۱۰ خط) | متوسط (۱۵–۳۰ خط) |
// 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
}
}
توصیه اپل (WWDC 2025): برای پروژههای جدید از CompositionalLayout استفاده کنید. امکانات بیشتری برای طرحهای تطبیقی فراهم میکند، با UIKit و SwiftUI از طریق UICollectionView.Representable بهتر کار میکند و از هدرهای بخش با اندازه خودکار پشتیبانی میکند. FlowLayout را برای صفحههای ساده با شبکه سلولهای یکسان و حداقل نسخه iOS 12 و پایینتر保留 کنید.
UICollectionViewCell — کلاس پایه برای سلولهای UICollectionView. هر سلول شامل contentView (کانتینر اصلی)، backgroundView (پسزمینه پیشفرض)، selectedBackgroundView (پسزمینه هنگام انتخاب) است. ظاهر سلول را در داخل contentView تنظیم کنید — هرگز زیرنماها را مستقیماً به UICollectionViewCell اضافه نکنید.
// سلول سفارشی با پیکربندی
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 constraints
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 — جایگزین امن از نظر نوع برای UICollectionViewDataSource که در iOS 13 معرفی شد. به جای مدیریت دستی سلولها از طریق cellForItemAt، DiffableDataSource از NSDiffableDataSourceSnapshot استفاده میکند — تصویری از وضعیت دادهها که به طور خودکار تفاوت بین وضعیت قدیم و جدید را محاسبه کرده و تغییرات را انیمیشن میکند.
// 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: (۱) انیمیشن خودکار — insert، delete، reload با انیمیشن پیشفرض انجام میشوند؛ (۲) امنیت نوع — بخشها و عناصر تایپشده هستند، خطاهای کامپایل به جای خطاهای runtime؛ (۳) Snapshot — تصویر وضعیت قابل ذخیره، بازگردانی و مقایسه است؛ (۴) عملکرد — DiffableDataSource از الگوریتم هلییر برای محاسبه حداقل مجموعه تغییرات استفاده میکند. اپل (WWDC 2024) DiffableDataSource را برای همه UICollectionViewهای جدید توصیه میکند و DataSource قدیمی را فقط برای iOS 12 و قدیمیتر保留 کنید.
UICollectionViewLayout — کلاس انتزاعی که به طور کامل چیدمان عناصر مجموعه را کنترل میکند. اگر FlowLayout و CompositionalLayout مناسب نیستند (مثلاً طرح دایرهای، مارپیچ، نمودار)، یک زیرکلاس از UICollectionViewLayout ایجاد کنید و متدهای آمادهسازی طرح را override کنید.
// طرح دایرهای سفارشی
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() کش کرده و در layoutAttributesForItem(at:) با O(1) بازگرداند. برای مجموعههای با ۱۰۰۰+ عنصر از بهروزرسانی افزایشی کش از طریق shouldInvalidateLayout(forBoundsChange:) استفاده کنید — این کار باعث میشود در هنگام اسکرول کل طرح دوباره محاسبه نشود. اپل (WWDC 2023) CompositionalLayout را به جای طرحهای سفارشی توصیه میکند: ۹۵% سناریوها را بدون نیاز به نوشتن طرح از صفر پوشش میدهد.
UITableView و UICollectionView — دو کامپوننت اصلی UIKit برای نمایش لیستها. UITableView سادهتر است و برای لیستهای تکستونی طراحی شده. UICollectionView انعطافپذیرتر است و از هر نوع طرحی پشتیبانی میکند. انتخاب بین آنها به ساختار بصری دادهها و سفارشیسازی مورد نیاز بستگی دارد.
| پارامتر | UITableView | UICollectionView |
|---|---|---|
| طرح | فقط لیست عمودی | لیست، شبکه، آبشاری، چرخفلک، سفارشی |
| پیچیدگی | کم (DataSource ساده) | متوسط (Layout + DataSource + Delegate) |
| سلول پیشفرض | ۴ سبک (basic, subtitle, value1, value2) | فقط خالی (سفارشی از طریق contentView) |
| حالت ویرایش | داخلی (delete, move, insert) | نیاز به پیادهسازی دستی دارد |
| Header/Footer | داخلی (viewForHeaderInSection) | از طریق نماهای کمکی |
| عملکرد | بالا برای لیستها | بالا برای هر نوع طرح |
| iOS 6+ | از iOS 2 در دسترس | از iOS 6 در دسترس |
قاعده عملی: اگر صفحه یک لیست ساده از ردیفها است (تنظیمات، مخاطبین، منو) از UITableView استفاده کنید. اگر صفحه نیاز به شبکه، اسکرول افقی، چرخفلک، اندازههای مختلف سلول یا گروهبندی پیچیده دارد از UICollectionView استفاده کنید. از iOS 14، UITableView نیز از CompositionalLayout از طریق UICollectionLayoutListConfiguration پشتیبانی دریافت کرد که امکان ترکیب مزایای هر دو کامپوننت را فراهم میکند.
سوالات متداول
scrollDirection = .horizontal را در UICollectionViewFlowLayout تنظیم کنید. برای CompositionalLayout از NSCollectionLayoutSize با fractionalWidth برای عرض تطبیقی گروه استفاده کنید. اسکرول افقی برای چرخفلکها، گالریها و دستهبندیها مناسب است. برای اسکرول صفحهای، collectionView.isPagingEnabled = true را تنظیم کنید یا از UICollectionViewFlowLayout با itemSize = bounds.width استفاده کنید.
بررسی کنید: (۱) سلول از طریق register(_:forCellWithReuseIdentifier:) ثبت شده است، (۲) DataSource تعداد عناصر > ۰ را برمیگرداند، (۳) سلول با reuseIdentifier صحیح dequeued شده است، (۴) itemSize صفر نیست (برای FlowLayout). خطای معمول — itemSize = CGSize.zero که باعث میشود سلولها ارتفاع صفر داشته باشند. itemSize را به صراحت تنظیم کنید یا دلیگیت collectionView(_:layout:sizeForItemAt:) را پیادهسازی کنید.
برای FlowLayout: layout.minimumInteritemSpacing (بین عناصر در یک ردیف) و layout.minimumLineSpacing (بین ردیفها). برای CompositionalLayout: NSCollectionLayoutGroup.interItemSpacing و contentInsets روی گروه/بخش. sectionInset فاصلههای خارجی بخش را مشخص میکند. برای در نظر گرفتن صحیح Safe Area از sectionInsetReference = .fromContentInset استفاده کنید.
از performBatchUpdates برای انیمیشنهای گروهی استفاده کنید: collectionView.performBatchUpdates { insertItems, deleteItems, reloadItems }. برای بهروزرسانی امن و انیمیشندار از UICollectionViewDiffableDataSource استفاده کنید: dataSource.apply(snapshot, animatingDifferences: true) را فراخوانی کنید — همه تغییرات به طور خودکار محاسبه و بدون insert/delete دستی اعمال میشوند.
در SwiftUI معادل UICollectionView LazyVGrid و LazyHGrid هستند (iOS 14+). SwiftUI Grid از نظر نوشتن سادهتر است (سینتکس اعلانی)، اما در عملکرد برای ۵۰۰+ عنصر و سفارشیسازی طرحها از UICollectionView پایینتر است. برای مجموعههای پیچیده از UICollectionView از طریق UIViewRepresentable استفاده کنید. در پروژههای IT Sectr برای کاتالوگها و گالریها UICollectionView را انتخاب میکنیم و برای صفحههای ساده SwiftUI Grid را.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.