1
\$\begingroup\$

This is a datasource for Achievements that can be toggled to be viewed within a grid or list layout. Thoughts/notes

  • This code was previously duplicated in two different ViewControllers.
  • Should the CollectionLayout code go in here?
  • Should the SectionHeader and layout toggle button delegate go in here?
  • Have I implemented the DiffableDataSource correctly?

Any comments or advise welcome.

Screenshot of Datasource in use

class AchievementDataSource: UICollectionViewDiffableDataSource<AchievementSectionID, Achievement> {
    var sections: [AchievementListSection] = [AchievementListSection(id: 0, name: "")]
    lazy var currentLayout = CollectionLayoutToggleHeader.State(rawValue: userLayoutPreferenceProvider()) ?? .list {
        didSet {
            userLayoutPreferenceSetter(currentLayout.rawValue)
        }
    }
    var userLayoutPreferenceProvider: () -> String
    var userLayoutPreferenceSetter: (String) -> Void

    private let achievementPlaceholderImage = UIImage(named: "placeholder_home_achievements_home_badge")

    init(collectionView: UICollectionView,
         userLayoutPreferenceProvider: @escaping () -> String = { LocalUser.PreferenceSettings.groupAchievementLayout() },
         userLayoutPreferenceSetter: @escaping (String) -> Void = { layoutString in
            LocalUser.PreferenceSettings.setGroupAchievementLayout(layoutString)
         } ) {

        self.userLayoutPreferenceProvider = userLayoutPreferenceProvider
        self.userLayoutPreferenceSetter = userLayoutPreferenceSetter
        weak var weakSelf: AchievementDataSource?
        super.init(collectionView: collectionView) { collectionView, indexPath, achievement in
            return weakSelf?.configureCell(collectionView: collectionView, indexPath: indexPath, achievement: achievement)
        }
        weakSelf = self
    }

    func updateCollectionView() {
        var snapshot = NSDiffableDataSourceSnapshot<AchievementSectionID, Achievement>()
        snapshot.appendSections(sections.map { $0.id })
        sections.forEach { section in
            guard !section.isCollapsed else { return }
            snapshot.appendItems(section.achievements, toSection: section.id)
        }
        apply(snapshot)
    }

    private func configureCell(collectionView: UICollectionView, indexPath: IndexPath, achievement: Achievement) -> UICollectionViewCell? {
        switch currentLayout {
        case .list:
            return makeListCell(collectionView: collectionView, indexPath: indexPath, achievement: achievement)
        case .grid:
            return makeGridCell(collectionView: collectionView, indexPath: indexPath, achievement: achievement)
        }
    }

    private func makeListCell(collectionView: UICollectionView, indexPath: IndexPath, achievement: Achievement) -> UICollectionViewCell? {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: AchievementCell.reuseIdentifier, for: indexPath) as! AchievementCell

        cell.titleLabel.text = achievement.name
        cell.descriptionLabel.text = achievement.description
        cell.rarityIcon.image = achievement.level.icon
        cell.rarityIcon.tintColor = achievement.level.color
        cell.rarityLabel.text = achievement.level.name
        let imageFormatting: ImageFormatType  = achievement.isLocked ? .achievementLocked : .none
        cell.imageView.pal_setProcessedImage(from: achievement.imageURL,
                                             placeholder: achievementPlaceholderImage,
                                             imageType: imageFormatting)
        cell.isLocked = achievement.isLocked
        cell.dateLabel.text = achievement.dateUnlocked
        cell.isProgressBarHidden = (achievement.type != .multi)
        cell.stepCompletionLabel.text = achievement.stepCompletionMessage ?? ""
        cell.progressBar.progress = achievement.completionDecimal ?? 0
        cell.progressLabel.text = "\(achievement.completionPercentage ?? 0)%"

        cell.separatorView.isHidden = isLastCellInSection(cell, indexPath: indexPath)
        return cell
    }

    private func makeGridCell(collectionView: UICollectionView, indexPath: IndexPath, achievement: Achievement) -> UICollectionViewCell? {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ImageSubtitleCell.reuseID, for: indexPath) as! ImageSubtitleCell
        cell.titleLabel.text = achievement.name
        cell.isLocked = achievement.isLocked
        let imageFormatting: ImageFormatType  = achievement.isLocked ? .achievementLocked : .none
        cell.imageView.pal_setProcessedImage(from: achievement.imageURL,
                                             placeholder: achievementPlaceholderImage,
                                             imageType: imageFormatting)
        return cell
    }

    private func isLastCellInSection(_ cell: UICollectionViewCell, indexPath: IndexPath) -> Bool {
        let currentSnapshot = snapshot()
        let sectionIdentifier = currentSnapshot.sectionIdentifiers[indexPath.section]
        let numberOfItemsInSection = currentSnapshot.numberOfItems(inSection: sectionIdentifier)
        return indexPath.item + 1 == numberOfItemsInSection
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.