CollectionView İle Compositional Layout Kullanımı

Gorkem Gur
Appcent
Published in
5 min readFeb 20, 2024

Compositional Layout Nedir ?

Apple tarafından WWDC19 ile kullanımımıza sunulmuştur, karmaşık UI öğelerini bir arada kullanmak için oldukça güçlü bir API’dir.

Compositional Layout’u Neden Kullanmalıyız ?

Yukarıda da bahsettiğimiz gibi eğer karmaşık bir UI yapısı kurmamız gerekiyorsa ya da dynamic olarak değişkenlik gösteren bir sayfa yapımız varsa örneğin backend servisi tarafından bize dönen response içerisinde AppStore gibi en üstte slider hemen altında kategoriler ve bir altında da keşfet alanı olacağını düşünelim. Bu yapıları oluşturmak için response geldiği anda tıpki bir section yapısı gibi liste oluşturarak içerisinde itemleri ekleyebilir ve bu şekilde dynamic bir collectionView yapısı oluşturabiliriz, eğer backend servisinden slider gelmezse kategori yapımız en üste çıkacaktır bunu yapmanın farklı yolları da var tabii ki fakat kodlama performansımızı ve hızımızı arttıracak bir yapı kullanmak her zaman bizim faydamıza olacaktır.

Compositional Layout Nasıl Bir Yapıdır ?

Compositional Layout item öğelerini bir araya getirerek group’ları group’lar ise bir araya gelerek section yapısını oluşturur, daha detaylı anlayabilmemiz için AppStore uygulamasındaki Uygulamalar sayfasına göz atabiliriz.

App Store Uygulamalar Sayfası
  • Uygulamalar Başlığının hemen altında filtreleme yapabilmemiz için butonlar bulunuyor, aslında bu butonların her birisini “UICollectionViewCell” olarak düşünmeliyiz. Adım adım incelememiz gerekirse Burada Müzik yazan buton’u item olarak ele alabiliriz yanındaki AR Uygulamarı ve diğer butonlarla bir araya gelerek group’ları oluşturuyor ve bu groupların ekranda kapladıkları bütün alanı da section olarak adlandırabiliriz.
  • Hemen altında yere alan kısımda ise bir slider yapısı görüyoruz, burada da yukarıdaki durum geçerli imageview ve üstündeki yazılar birer item, hepsi bir araya gelerek group oluşturuyorlar ve ekranda kapladıkları alanı ise section olarak adlandırabiliriz.

Bu yazımızda, yukarıda örneğini gördüğümüz uygulamalar sayfasının tasarımını yapacağız, bunun için ilk önce ihtiyacımız olan değişkenleri ve modelleri belirlememiz gerekiyor.

Ilk olarak ana sayfamız için hücre tiplerini belirleyeceğimiz bir enum oluşturalım,

enum MainPageCellType {
case filterCell
case sliderCell
case mostRecommendedCell
}

Şimdi buradaki yapıda filtreleme ve slider hemen hemen aynı yapıda olduğu için bir de LayoutType adında bir enum tanımlıyoruz,

enum LayoutType {
case horizontal(isSlider: Bool)
case normal(headerIdentifier: String?)
}

Tiplerimizi belirtttikten sonra bu tipte oluşturacağımız hücreler için modellerini belirlememiz gerekiyor.

FilterCellModel

struct FilterCellModel {
let title: String
let imageName: String
}

SliderCellModel

struct SliderCellModel {
let title: String
let subTitle: String
let description: String
let imageName: String
}

MostRecommendedCellModel

struct MostRecommendedCellModel {
let imageName: String
let title: String
let description: String
}

Tüm hücre tiplerimizin modellerini oluşturduk, şimdi ise anasayfada bu hücre tiplerini nasıl oluşturacağımızı ve kullanacağımızı belirlemek için SectionModel Adında bir protokol oluşturacağız ve bu protokolü kullanarak sectionlar için modellerimizi oluşturacağız

SectionModel

protocol SectionModel {
var sectionTitle: String? { get }
var itemCount: Int { get }
var cellType: MainPageCellType { get }
func getItem(at index: Int) -> Any
}

FilterSection

struct FilterSection: SectionModel {
var sectionTitle: String?

var filterSectionList: [FilterCellModel]

var itemCount: Int {
return filterSectionList.count
}

var cellType: MainPageCellType {
return .filterCell
}

func getItem(at index: Int) -> Any {
filterSectionList[index]
}
}

SliderSection

struct SliderSection: SectionModel {
var sectionTitle: String?

var sliderList: [SliderCellModel]

var itemCount: Int {
return sliderList.count
}

var cellType: MainPageCellType {
return .sliderCell
}

func getItem(at index: Int) -> Any {
sliderList[index]
}
}

MostRecommendedSection

struct MostRecommendedSection: SectionModel {
var sectionTitle: String?

var mostRecommendedList: [MostRecommendedCellModel]

var itemCount: Int {
return mostRecommendedList.count
}

var cellType: MainPageCellType {
return .mostRecommendedCell
}

func getItem(at index: Int) -> Any {
mostRecommendedList[index]
}
}

Bu oluşturduğumuz modelleri MainPageViewModel üzerinde kullanalım ve artık yapımızı oluşturalım şuan burada örnek olması açısından statik şekildde ekleyeceğiz verileri fakat buradaki durum şuanlık sadece örnek olduğu için kendi istek ve ihtiyaçlarınıza göre bu listeleri güncelleyebilir veya genişletebilirsiniz.

MainPageViewModel Class

Şimdi bize gerekli modelleri de oluşturduk bundan sonra yapmamız gereken şey Compositional Layout’u oluşturmak bunun için de bir Manager class’ı yazmalıyız, ilerleyen zamanlarda ekleme çıkarma yapabiliriz ve tek bir yerden kontrol edilebilmesi okunabilirlik ve geliştirme açısından da oldukça önemli.

CompositionalLayoutManager Class

Bu manager class’ını viewcontroller içerisinde collectionView’ımızın layout’unu oluşturmak için kullanacağız, createLayoutSection içerisinde layout tipimizi göndererek ihtiyacımız olan NSLayoutSection öğesini alacağız.

Yukarıdaki createNormalSection içerisindeki kod bloğu içerisindeki groupSize kısmında height değerini 270, item içerisindeki height değerini de 90 verdik çünkü biz en çok önerilenler kısmında alt alta 3 adet item olsun istiyoruz. Buradaki değerleri tamamen kendi ihtiyaçlarınıza göre düzenleyebilirsiniz.

ViewController içerisindeki kodumuz da bu şekilde olacak;

func setCompositionalLayout() {
let layout = UICollectionViewCompositionalLayout { [weak self] sectionIndex, env -> NSCollectionLayoutSection? in
guard let self = self else { return nil }
let sectionType = self.viewModel.getSection(at: sectionIndex).cellType

return self.layoutSection(for: sectionType)
}

mainCollectionView.setCollectionViewLayout(layout, animated: true)
}

private func layoutSection(for cellType: MainPageCellType) -> NSCollectionLayoutSection {
switch cellType {
case .filterCell:
return CompositionalLayoutManager.sharedInstance.createLayoutSection(layoutType: .horizontal(isSlider: false))
case .mostRecommendedCell:
return CompositionalLayoutManager.sharedInstance.createLayoutSection(layoutType: .normal(headerIdentifier: "RecommendedHeaderView"))
case .sliderCell:
return CompositionalLayoutManager.sharedInstance.createLayoutSection(layoutType: .horizontal(isSlider: true))
}
}

Ve daha önce viewModel içerisinde oluşturduğumuz verileri collectionView’ın UICollectionViewDelegate ve UICollectionViewDataSource protokollerini kullanarak ekranda göstereceğiz.

extension MainPageViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
viewModel.getSectionsCount()
}

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
viewModel.getSection(at: section).itemCount
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let section = viewModel.getSection(at: indexPath.section)
let item = section.getItem(at: indexPath.row)
switch section.cellType {
case .filterCell:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: FilterCollectionViewCell.reuseIdentifier, for: indexPath) as! FilterCollectionViewCell
if let filterModel = item as? FilterCellModel {
cell.setup(model: filterModel)
}
return cell
case .mostRecommendedCell:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MostRecommendedCollectionViewCell.reuseIdentifier, for: indexPath) as! MostRecommendedCollectionViewCell
if let mostRecommendedModel = item as? MostRecommendedCellModel {
cell.setup(model: mostRecommendedModel)
}
return cell
case .sliderCell:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: SliderCollectionViewCell.reuseIdentifier, for: indexPath) as! SliderCollectionViewCell
if let sliderCellModel = item as? SliderCellModel {
cell.setup(model: sliderCellModel)
}
return cell
}
}


func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let section = viewModel.getSection(at: indexPath.section)
let item = section.getItem(at: indexPath.row)
switch section.cellType {
case .filterCell:
print("item : \(item as? FilterCellModel)")
case .sliderCell:
print("item : \(item as? SliderCellModel)")
case .mostRecommendedCell:
print("item : \(item as? MostRecommendedCellModel)")
}
}
}

Ve uygulamamızı çalıştırdığımız zaman aşağıdaki gibi bir çıktı elde ediyoruz.

Projenin kaynak kodlarına aşağıdaki linkten ulaşabilirsiniz. Bir sonraki yazıda görüşmek üzere, herkese kolay gelsin :)

Örnek Proje Linki

--

--