본문 바로가기

iOS 개발/Apple App Store 클론 코딩

AppleAppStore - 11. DetailViewController 화면 구현

반응형

안녕하세요. 제가 가장 최근까지 개발했던 DetailViewController에 대한 설명할게요.

 

구현하는데에 있어서, 다양한 cell을 구현하다 보니, 시간적으로 오래 걸렸고, 고려할 사항도 많았어요. 코드도 많았습니다.

 

그래서 설명이 엄청 길고 장황해질것 같아서 무서워여 ㄷㄷㄷㄷ

 

------------------------------------------------------------------------------------------------------------------

 

기본적으로 UICollectionViewCompositionalLayout을 사용하여 collectionView를 구성하고, section을 구현합니다.

그래서 기본적인 레이아웃을 먼저 설명하고, section별 구현을 설명할게요.

 

먼저 navigationBar에 들어가는 앱 아이콘과 버튼입니다.

스크롤 시 네비게이션바에 보여집니다. appStore 앱에는 애니메이션이 추가됐는데, 저는 애니메이션 추가 없이 구현했어요.

나~중에 심심할때 애니메이션을 추가할지도..?

 

12라인 : 네비게이션바의 앱 아이콘 입니다. 앱 아이콘은 추가하였으나, 센터가 안맞는 이슈가 있어서 좀 걸렸던 기억이 있어요. 이는 snapKit으로 width를 설정하니, 해결되더라구여.

UIView에 imageView를 추가하여 센터 세팅하였습니다. 

 

36라인 : barButtonItem으로 구현했습니다.

 

49라인 : navigationBar에 추가한 뷰들에 대한 isHidden 여부입니다. collectionView의 scroll에 따라, isHidden을 설정합니다.

 

122라인 : 섹션 array입니다. 섹션 목록을 받아와, 섹션 안의 아이템 array로 레이아웃을 구성할겁니다.

 

127라인 : collectionView의 레이아웃은 "collectionViewLayout" 인데, 아래 150라인을 참고하세요. "UICollectionViewCompositionalLayout"을 리턴합니다.

 

134라인 : cell을 등록합니다.

 

142라인 : header를 등록합니다.

 

각 세션을 리턴하는데~ 이는 추후 세션별로 설명할테니, "세션을 리턴하는구나~"라고 생각해주세요.

 

184라인 : 레이아웃을 설정합니다.

 

187라인 : collectionView를 추가하고, 제약사항을 구성합니다.

 

198, 199라인 : 네비게이션바를 설정합니다. barButtonItem과 titleView를 설정합니다.

 

207라인 : viewWillAppear에서 네비게이션 설정을 합니다.

 

385라인 : 섹션의 개수를 리턴합니다.

 

389라인 : 섹션의 아이템 개수를 리턴합니다.

 

393라인 : 섹션별 cell을 설정하고, cell을 리턴합니다. "section에 맞는 cell을 리턴하는구나~"라고 생각해주세요.

 

473라인 : section별 header를 리턴합니다. "section에 맞는 header를 리턴하는구나~"라고 생각해주세요.

 

524라인 : cell 터치에 따른 동작을 구현합니다. 저는 screenshot을 선택했을때의 동작만 구현했어요!!

 

544라인 : 스크롤시, 스크롤의 offset을 확인하여, 네비게이션바의 아이콘을 보여줄지 말지 결정합니다.

특정 y보다 크다면 앱 아이콘을 보여주고, y보다 작다면 앱 아이콘을 숨겨줍니다.

간단하쥬????

 

550라인 : layout update delegate를 구현했어요. cell중에는 expand 가능한 cell이 있는데, expand후 레이아웃을 업데이트 하여, 레이아웃을 제대로 보여줄수 있도록 합니다.

 

555라인 : cell이 expand 상태인지 상태를 변경합니다. expand한 cell이 있다면 계속 expand 상태로 유지해야하기 때문에, expand 상태를 저장합니다.

 

어때요?????

section과 cell 구현만 빼면, 아주아주아주우아아주앚아주아주앚우자웅아주 평범한 collectionView 구현이죠??

그럼 다음부턴 section과 cell을 구현해보죠!!

 

아자!아자!

 

------------------------------------------------------------------------------------------------------------------

 

잘못되거나 부족한 내용 등, 피드백 감사합니다!

 

Skillist의 AppleAppStore 프로젝트

https://github.com/DeveloperSkillist/AppleAppStoreCloneCode

 

GitHub - DeveloperSkillist/AppleAppStoreCloneCode: AppleAppStoreCloneCode

AppleAppStoreCloneCode. Contribute to DeveloperSkillist/AppleAppStoreCloneCode development by creating an account on GitHub.

github.com

 

 

↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓  전체 코드  ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

import UIKit

class DetailViewController: UIViewController {
    
    private lazy var navigationAppView: UIView = {
        let uiView = UIView()
        uiView.snp.makeConstraints {
            $0.width.equalTo(64)
            $0.height.equalTo(64)
        }
        
        var iconView = DownloadableImageView()
        if let item = item {
            iconView.downloadImage(url: item.artworkUrl512)
        }
        iconView.layer.cornerRadius = 10
        iconView.layer.masksToBounds = true
        iconView.contentMode = .scaleAspectFit

        uiView.addSubview(iconView)
        
        iconView.snp.makeConstraints {
            $0.width.height.equalTo(40)
            $0.center.equalTo(uiView)
        }
        return uiView
    }()
    
    private lazy var navigationButton: UIBarButtonItem = {
        let button = UIButton()
        button.backgroundColor = .link
        button.setTitle("app_download_title".localized, for: .normal)
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 15
        button.clipsToBounds = true
        button.snp.makeConstraints {
            $0.width.equalTo(70)
        }
        return UIBarButtonItem(customView: button)
    }()
    
    lazy var isHiddenNavigationBarAppInfo: Bool = false {
        didSet {
            if oldValue == isHiddenNavigationBarAppInfo {
                return
            }
            
            navigationAppView.isHidden = isHiddenNavigationBarAppInfo
            navigationButton.customView?.isHidden = isHiddenNavigationBarAppInfo
        }
    }
    
    var item: SearchItemResult? {
        willSet {
            sections.removeAll()
            
            guard let item = newValue else {
                return
            }
            
            //TODO: section 설정
            sections.append(
                DetailItem(itemType: .main, headerType: .none)
            )
            
            sections.append(
                DetailItem(itemType: .infoShortItem, items: item.languageCodesISO2A, headerType: .line)
            )
            
            sections.append(
                DetailItem(itemType: .screenShots, items: item.screenshotUrls, headerType: .none)
            )
            
            sections.append(
                DetailItem(itemType: .textDescription, headerType: .line)
            )
            
            sections.append(
                DetailItem(itemType: .review, items: item.supportedDevices, headerType: .review)
            )
            
            sections.append(
                DetailItem(itemType: .textInfo, headerType: .largeTitleWithButton)
            )
            
            sections.append(
                DetailItem(
                    itemType: .infoListItem,
                    items: [
                        item.artistName,
                        item.fileSizeBytes,
                        item.languageCodesISO2A.description,
                        item.minimumOSVersion,
                        item.releaseNotes
                    ],
                    itemNames: [
                        "제공자",
                        "크기",
                        "언어",
                        "최소 버전",
                        "배포 노트"
                    ],
                    isExpanded: [
                        false,
                        false,
                        false,
                        false,
                        false
                    ],
                    headerType: .largeTitleWithButton
                )
            )
        }
    }
    private var sections: [DetailItem] = []
    
    private lazy var collectionView: UICollectionView = {
        var collectionView = UICollectionView(
            frame: .zero,
            collectionViewLayout: collectionViewLayout
        )
        collectionView.backgroundColor = .systemBackground
        
        collectionView.dataSource = self
        collectionView.delegate = self
        
        //cell
        collectionView.register(DetailMainCollectionViewCell.self, forCellWithReuseIdentifier: "DetailMainCollectionViewCell")
        collectionView.register(DetailInfoShortItemCollectionViewCell.self, forCellWithReuseIdentifier: "DetailInfoShortItemCollectionViewCell")
        collectionView.register(DetailScreenShotCollectionViewCell.self, forCellWithReuseIdentifier: "DetailScreenShotCollectionViewCell")
        collectionView.register(DetailTextInfoCollectionViewCell.self, forCellWithReuseIdentifier: "DetailTextInfoCollectionViewCell")
        collectionView.register(DetailReviewCollectionViewCell.self, forCellWithReuseIdentifier: "DetailReviewCollectionViewCell")
        collectionView.register(DetailInfoInfoCollectionViewCell.self, forCellWithReuseIdentifier: "DetailInfoInfoCollectionViewCell")
        
        //header
        collectionView.register(DetailLineHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "DetailLineHeaderView")
        collectionView.register(DetailLargeTitleWithButtonHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "DetailLargeTitleWithButtonHeaderView")
        collectionView.register(DetailReviewHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "DetailReviewHeaderView")
        
        return collectionView
    }()
    
    private lazy var collectionViewLayout: UICollectionViewLayout = {
        return UICollectionViewCompositionalLayout { [weak self] section, _ -> NSCollectionLayoutSection? in
            let itemType = self?.sections[section].itemType
            switch itemType {
            case .main:
                return self?.createMainSection()
                
            case .infoShortItem:
                return self?.createInfoShortItemSection()
                
            case .screenShots:
                return self?.createScreenShotItemSection()
                
            case .textDescription:
                return self?.createTextInfoWithHeaderSection()
                
            case .textInfo:
                return self?.createTextInfoWithHeaderSection()
                
            case .review:
                return self?.createReviewSection()
                
            case .infoListItem:
                return self?.createInfoListItemSection()
                
            default:
                return nil
            }
        }
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        setupLayout()
    }
    
    private func setupLayout() {
        [
            collectionView
        ].forEach {
            view.addSubview($0)
        }
        
        collectionView.snp.makeConstraints {
            $0.edges.equalToSuperview()
        }
        
        navigationItem.rightBarButtonItems = [navigationButton]
        navigationItem.titleView = navigationAppView
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        setupNavigationBar()
    }
    
    private func setupNavigationBar() {
        navigationController?.navigationBar.prefersLargeTitles = false
        navigationItem.titleView?.isHidden = isHiddenNavigationBarAppInfo
    }
}

extension DetailViewController {
    private func createMainSection() -> NSCollectionLayoutSection {
        let itemMargin: CGFloat = 5
        let sectionMargin: CGFloat = 15
        
        //item
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        item.contentInsets = .init(top: itemMargin, leading: itemMargin, bottom: itemMargin, trailing: itemMargin)
        
        //group
        let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(self.view.frame.width - (sectionMargin * 2)), heightDimension: .absolute(100))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 1)
//        group.contentInsets = .init(top: 0, leading: 0, bottom: 0, trailing: 0)
        
        //section
        let section = NSCollectionLayoutSection(group: group)
        section.orthogonalScrollingBehavior = .none
        section.contentInsets = .init(top: sectionMargin, leading: sectionMargin, bottom: sectionMargin, trailing: sectionMargin)
        
        return section
    }
    
    private func createInfoShortItemSection() -> NSCollectionLayoutSection {
        let itemMargin: CGFloat = 5
        let sectionMargin: CGFloat = 15
        
        //item
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        item.contentInsets = .init(top: itemMargin, leading: itemMargin, bottom: itemMargin, trailing: itemMargin)
        
        //group
        let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(100), heightDimension: .absolute(100))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 1)
//        group.contentInsets = .init(top: 0, leading: 0, bottom: 0, trailing: 0)
        
        //section
        let section = NSCollectionLayoutSection(group: group)
        section.orthogonalScrollingBehavior = .continuous
        section.contentInsets = .init(top: sectionMargin, leading: sectionMargin, bottom: sectionMargin, trailing: sectionMargin)
        
        //header
        let sectionHeader = createHeader()
        section.boundarySupplementaryItems = [sectionHeader]
        
        return section
    }
    
    private func createScreenShotItemSection() -> NSCollectionLayoutSection {
        let itemMargin: CGFloat = 5
        let sectionMargin: CGFloat = 15
        
        //item
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        item.contentInsets = .init(top: itemMargin, leading: itemMargin, bottom: itemMargin, trailing: itemMargin)
        
        //group
        let groupSize = NSCollectionLayoutSize(widthDimension: .absolute((collectionView.frame.width - sectionMargin) / 1.5), heightDimension: .absolute(500))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 1)
//        group.contentInsets = .init(top: 0, leading: 0, bottom: 0, trailing: 0)
        
        //section
        let section = NSCollectionLayoutSection(group: group)
        section.orthogonalScrollingBehavior = .groupPaging
        section.contentInsets = .init(top: sectionMargin, leading: sectionMargin, bottom: sectionMargin, trailing: sectionMargin)
        
        return section
    }
    
    private func createTextInfoWithHeaderSection() -> NSCollectionLayoutSection {
        let itemMargin: CGFloat = 5
        let sectionMargin: CGFloat = 15
        
        //item
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(1))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        item.contentInsets = .init(top: itemMargin, leading: itemMargin, bottom: itemMargin, trailing: itemMargin)
        
        //group
        let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(collectionView.frame.width - sectionMargin), heightDimension: .estimated(1))
//        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 1)
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
//        group.contentInsets = .init(top: 0, leading: 0, bottom: 0, trailing: 0)
        
        //section
        let section = NSCollectionLayoutSection(group: group)
        section.orthogonalScrollingBehavior = .none
        section.contentInsets = .init(top: sectionMargin, leading: sectionMargin, bottom: sectionMargin, trailing: sectionMargin)
        
        //header
        let sectionHeader = createHeader()
        section.boundarySupplementaryItems = [sectionHeader]
        
        return section
    }
    
    private func createReviewSection() -> NSCollectionLayoutSection {
        let itemMargin: CGFloat = 5
        let sectionMargin: CGFloat = 15
        
        //item
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        item.contentInsets = .init(top: itemMargin, leading: itemMargin, bottom: itemMargin, trailing: itemMargin)
        
        //group
        let width = (self.view.frame.width - (sectionMargin * 2))
        let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(width), heightDimension: .absolute(width * 0.7))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 1)
//        group.contentInsets = .init(top: 0, leading: 0, bottom: 0, trailing: 0)
        
        //section
        let section = NSCollectionLayoutSection(group: group)
        section.orthogonalScrollingBehavior = .groupPaging
        section.contentInsets = .init(top: sectionMargin, leading: sectionMargin, bottom: sectionMargin, trailing: sectionMargin)
        
        //header
        let sectionHeader = createHeader()
        section.boundarySupplementaryItems = [sectionHeader]
        
        return section
    }
    
    private func createInfoListItemSection() -> NSCollectionLayoutSection {
        let itemMargin: CGFloat = 5
        let sectionMargin: CGFloat = 15
        
        //item
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(1))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        item.contentInsets = .init(top: itemMargin, leading: itemMargin, bottom: itemMargin, trailing: itemMargin)
        
        //group
        let width = self.view.frame.width - sectionMargin
        let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(width), heightDimension: .estimated(1))
        let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [item])
//        group.contentInsets = .init(top: 0, leading: 0, bottom: 0, trailing: 0)
        
        //section
        let section = NSCollectionLayoutSection(group: group)
        section.orthogonalScrollingBehavior = .none
        section.contentInsets = .init(top: sectionMargin, leading: sectionMargin, bottom: sectionMargin, trailing: sectionMargin)
        
        //header
        let sectionHeader = createHeader()
        section.boundarySupplementaryItems = [sectionHeader]
        
        return section
    }
}

extension DetailViewController {
    private func createHeader() -> NSCollectionLayoutBoundarySupplementaryItem {
        //section에 보여줄 헤더를 구현
        let headerSize = NSCollectionLayoutSize(
            widthDimension: .fractionalWidth(1),
            heightDimension: .estimated(1)
        )
        
        let header = NSCollectionLayoutBoundarySupplementaryItem(
            layoutSize: headerSize,
            elementKind: UICollectionView.elementKindSectionHeader,
            alignment: .top
        )
        
        return header
    }
}

extension DetailViewController: UICollectionViewDataSource {
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return sections.count
    }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return sections[section].items?.count ?? 0
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let item = item else {
            return UICollectionViewCell()
        }
        
        let section = sections[indexPath.section]
        
        switch section.itemType {
        case .main:
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DetailMainCollectionViewCell", for: indexPath) as? DetailMainCollectionViewCell else {
                return UICollectionViewCell()
            }
            cell.setupItem(item: item)
            return cell
            
        case .infoShortItem:
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DetailInfoShortItemCollectionViewCell", for: indexPath) as? DetailInfoShortItemCollectionViewCell else {
                return UICollectionViewCell()
            }
            if let sectionItem = section.items?[indexPath.row] as? String {
                cell.setupItem(item: sectionItem)
            }
            return cell
            
        case .textDescription:
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DetailTextInfoCollectionViewCell", for: indexPath) as? DetailTextInfoCollectionViewCell else {
                return UICollectionViewCell()
            }
            cell.setupItem(text: item.resultDescription)
            cell.collectionViewLayoutUpdateDelegate = self
            return cell
            
        case .screenShots:
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DetailScreenShotCollectionViewCell", for: indexPath) as? DetailScreenShotCollectionViewCell else {
                return UICollectionViewCell()
            }
            if let screenUrl = section.items?[indexPath.row] as? String {
                cell.setupItem(screenUrl: screenUrl)
            }
            return cell
            
        case .textInfo:
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DetailTextInfoCollectionViewCell", for: indexPath) as? DetailTextInfoCollectionViewCell else {
                return UICollectionViewCell()
            }
            cell.setupItem(text: item.releaseNotes ?? "-")
            cell.collectionViewLayoutUpdateDelegate = self
            return cell
            
        case .review:
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DetailReviewCollectionViewCell", for: indexPath) as? DetailReviewCollectionViewCell else {
                return UICollectionViewCell()
            }
            if let supportedDevice = section.items?[indexPath.row] as? String {
                cell.setupItem(text: supportedDevice)
            }
            return cell
            
        case .newFeatureInfo:
            return UICollectionViewCell()
            
        case .infoListItem:
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DetailInfoInfoCollectionViewCell", for: indexPath) as? DetailInfoInfoCollectionViewCell else {
                return UICollectionViewCell()
            }
            
            cell.indexPath = indexPath
            
            let section = sections[indexPath.section]
            if let itemName = section.itemNames?[indexPath.row],
               let item = section.items?[indexPath.row] as? String,
               let isExpanded = section.isExpanded?[indexPath.row] {
                cell.setupItem(infoName: itemName, shortInfo: item, detailInfo: item, isExpanded: isExpanded)
            }
            
            cell.collectionViewLayoutUpdateDelegate = self
            return cell
        }
    }

    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
        switch kind {
        case UICollectionView.elementKindSectionHeader:
            let section = sections[indexPath.section]
            switch section.headerType {
                
            case .line:
                guard let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "DetailLineHeaderView", for: indexPath) as? DetailLineHeaderView else {
                    return UICollectionReusableView()
                }
                return headerView
                
            case .largeTitleWithButton:
                guard let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "DetailLargeTitleWithButtonHeaderView", for: indexPath) as? DetailLargeTitleWithButtonHeaderView else {
                    return UICollectionReusableView()
                }
                
                if section.itemType == .textInfo {
                    headerView.setupLargeTitleData(largeTitleText: "app_new_feature".localized, largeButtonText: "app_version_history".localized)
                } else if section.itemType == .infoListItem {
                    headerView.setupLargeTitleData(largeTitleText: "정보", largeButtonText: nil)
                }
                return headerView
                
            case .review:
                guard let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "DetailReviewHeaderView", for: indexPath) as? DetailReviewHeaderView else {
                    return UICollectionReusableView()
                }
                headerView.setupLargeTitleData(
                    largeTitleText: "app_review_title".localized,
                    largeButtonText: "show_all_title".localized
                )
                if let item = item {
                    headerView.setupItem(
                        rate: String(round(item.averageUserRating * 10)/10),
                        ratingCount: item.userRatingCount
                    )
                }
                return headerView
                
            case .none:
                return UICollectionReusableView()
            }
            
        default:
            return UICollectionReusableView()
        }
    }
}

extension DetailViewController: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        let section = sections[indexPath.section]
        
        //TODO: didSelect
        switch section.itemType {
        case .screenShots:
            guard let screenShots = section.items as? [String] else {
                return
            }
            let detailScreenshotsVC = DetailScreenShotsViewController()
            detailScreenshotsVC.screenShots = screenShots
            detailScreenshotsVC.startRow = indexPath.row
            present(detailScreenshotsVC, animated: true, completion: nil)
            
        default:
            return
        }
        
    }
    
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let yOffset = scrollView.contentOffset.y
        isHiddenNavigationBarAppInfo = yOffset < 0
    }
}

extension DetailViewController: CollectionViewLayoutUpdateDelegate {
    func collectionViewLayoutUpdate() {
        self.collectionView.collectionViewLayout.invalidateLayout()
    }
    
    func expandCell(indexPath: IndexPath) {
        sections[indexPath.section].isExpanded?[indexPath.row] = true
    }
}

 

반응형