0

Im updating an app which had many collectionViews embedded inside tableview so I decided to use UICollectionViewCompositionalLayout instead. I find them very flexible and I was able to design Home page for my app using this layout.

I followed some tutorials which just showed same data types, like Integers in multiple sections or photos app showing photos in different sections. In those tutorials they will use dataSource like this:

var dataSource: UICollectionViewDiffableDataSource<Section, PhotosItem>! = nil

or

var dataSource: UICollectionViewDiffableDataSource<Section, MoviesEntity>!

As my homepage consisted of many types of data, I simply used:

var dataSource: UICollectionViewDiffableDataSource<Section, Int>! = nil

And simply used numberOfSections, numberOfItemsInSection and cellForItemAt methods. I used this approach as my homepage will get data from multiple api's and some sections are static.

In UICollectionView, I would simply hit an api and update my data model and reload collectionView and do same for different collectionViews handling different data models in same page but now there is only one CollectionView so how do I handle this?

Do I just hit 7-8 API's and reload collectionView everytime? What I would like to do is use snapshot feature of feeding CollectionView with data coming from multiple API's.

This is what I have done to create compositional layout:

func generateLayout() -> UICollectionViewLayout {
        let layout = UICollectionViewCompositionalLayout { (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
            let isWideView = layoutEnvironment.traitCollection.horizontalSizeClass == .regular
            
            let sectionLayoutKind = Section.allCases[sectionIndex]
            switch (sectionLayoutKind) {
            case .firstSection:
                return self.generateFirstLayout(isWide: isWideView)
            case .secondSection:
                return self.generateCategoriesLayout(isWide: isWideView)
            case .services:
                return self.generateServicesLayout(isWide: isWideView)
            case .spotlight:
                return self.generateSpotlightLayout(isWide: isWideView)
            case .offers:
                return self.generateOffersLayout(isWide: isWideView)
            case .reviews:
                return self.generateReviewLayout(isWide: isWideView)
            case .logo:
                return self.generateLogoLayout(isWide: isWideView)
            }
        }
    }

And im simply using these functions to generate sections and adding Dummy items in them:

    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return Section.allCases.count
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        switch section {
        case 0 :
            return 1
        case 1 :
            return categories.count
        case 2 :
            return services.count
        case 3:
            return 2
        case 4:
            return 5
        case 5:
            return offers.count
        case 6:
            return 4
        default:
            return 10
        }
    }

So far this is all dummy data and now I want to feed live data and this is not same as what those tutorials did but my sections are different and I could not create something like "PhotosItem" to feed into dataSource method like in those tutorials.

Those tutorials used something like this:

 func configureDataSource() {
    dataSource = UICollectionViewDiffableDataSource
      <Section, AlbumItem>(collectionView: albumsCollectionView) {
        (collectionView: UICollectionView, indexPath: IndexPath, albumItem: AlbumItem) -> UICollectionViewCell? in

        let sectionType = Section.allCases[indexPath.section]
        switch sectionType {
        case .featuredAlbums:
          guard let cell = collectionView.dequeueReusableCell(
            withReuseIdentifier: FeaturedAlbumItemCell.reuseIdentifer,
            for: indexPath) as? FeaturedAlbumItemCell else { fatalError("Could not create new cell") }
          cell.featuredPhotoURL = albumItem.imageItems[0].thumbnailURL
          cell.title = albumItem.albumTitle
          cell.totalNumberOfImages = albumItem.imageItems.count
          return cell

        case .sharedAlbums:
          guard let cell = collectionView.dequeueReusableCell(
            withReuseIdentifier: SharedAlbumItemCell.reuseIdentifer,
            for: indexPath) as? SharedAlbumItemCell else { fatalError("Could not create new cell") }
          cell.featuredPhotoURL = albumItem.imageItems[0].thumbnailURL
          cell.title = albumItem.albumTitle
          return cell

        case .myAlbums:
          guard let cell = collectionView.dequeueReusableCell(
            withReuseIdentifier: AlbumItemCell.reuseIdentifer,
            for: indexPath) as? AlbumItemCell else { fatalError("Could not create new cell") }
          cell.featuredPhotoURL = albumItem.imageItems[0].thumbnailURL
          cell.title = albumItem.albumTitle
          return cell

        }
    }
    
    dataSource.supplementaryViewProvider = { (
      collectionView: UICollectionView,
      kind: String,
      indexPath: IndexPath) -> UICollectionReusableView? in

      guard let supplementaryView = collectionView.dequeueReusableSupplementaryView(
        ofKind: kind,
        withReuseIdentifier: HeaderView.reuseIdentifier,
        for: indexPath) as? HeaderView else { fatalError("Cannot create header view") }

      supplementaryView.label.text = Section.allCases[indexPath.section].rawValue
      return supplementaryView
    }

    let snapshot = snapshotForCurrentState()
    dataSource.apply(snapshot, animatingDifferences: false)
  }

But i don't have one AlbumItem but like ReviewItem, OfferItem and many more. So I was wondering do I stick to my way and just call the api and reload my collectionView? I have seen many apps that probably have same situation as me. So can anyone help me how to deal with this? Im using SwiftyJSON to feed my data into my data models. Just telling this as I have seen in most examples they use Hashable.

Mel
  • 429
  • 1
  • 4
  • 12

1 Answers1

0

I found answer by making my Model Hashable and had to switch from SwiftyJSON to Codable completely which I was avoiding for months. When your model is Hashable, your can pass it as item for your section. You can create different data models and add them to any section as required.

So for above, if I want to add PhotoItems or MovieItems or anything else, first I need to create Sections which can be done by creating an Enum like this:

enum Section {
case Section1
case SEction2
}

And then create another enum for items for each section like this:

enum Items {
case Section1Item(PhotoItem)
case Section2Item(MovieItem)
}

then define your dataSource like this:

var dataSource: UICollectionViewDiffableDataSource<Section, Items>! 

As you can see now we can add items of any type as we please instead of defining Item type like in question.

Finally just create a function to define snapshot:

var snapshot = Snapshot()
        
snapshot.appendItems(ArrayOfItemType1.map(PhotosItem.photos), toSection: Section.first)

snapshot.appendItems(ArrayOfItemType2.map(MovieItem.movie), toSection: Section.first)

        
datasource.apply(snapshot, animatingDifferences: true)
Mel
  • 429
  • 1
  • 4
  • 12