본문 바로가기
IOS - Swift

[iOS/Swift] 앱스토어 게임 탭 따라하기 (Composition Layout)

by 게게겍 2023. 10. 1.

구현하려는 view

 

 

Compositional layout

 

 

각 필요한 Cell들을 Nib 파일로 묶어서 작성했습니다.

import UIKit

final class ExtraLargeAppCell: UICollectionViewCell {
    @IBOutlet private var captionLabel: UILabel!
    @IBOutlet private var titleLabel: UILabel!
    @IBOutlet private var subtitleLabel: UILabel!
    @IBOutlet private var thumbnailView: UIView!

    override func awakeFromNib() {
        super.awakeFromNib()
        thumbnailView.layer.cornerRadius = 4
    }
}

다음으로 Compositional Layout에 필요한 기본 값들을 Protocol로 선언하여 기본 Section값을 할당합니다.

import UIKit

protocol Sections {
    var numberOfItem: Int { get }
    func layoutSection() -> NSCollectionLayoutSection
    func configureCell(collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionViewCell

}

다양한 section들의 기본 값입니다. 고정값이 아닌 상대값으로 layout값을 맞춥니다.

import UIKit

struct FeaturedAppSection: Sections {
    
    let numberOfItem = 5

    func layoutSection() -> NSCollectionLayoutSection {
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)

        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.92), heightDimension: .fractionalHeight(0.4))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])

        let section = NSCollectionLayoutSection(group: group)
        section.orthogonalScrollingBehavior = .groupPagingCentered

        return section
    }
    
    func configureCell(collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: SectionTitleCell.self), for: indexPath)
        return cell
    }
}

호출할 viewController입니다.

import UIKit

final class GamesViewController: UIViewController {
    
    lazy var sections: [Sections] = [ // 불러오기
        FeaturedAppSection(),
        SectionTitleSection(),
        SmallAppSection(),
        SectionTitleSection(),
        MediumAppSection(),
        SectionTitleSection(),
        TodayGameSection(),
        CategorySection(),
        SectionTitleSection(),
        SectionTitleSection(showsNavigation: false),

    ]

    lazy var collectionView: UICollectionView = {
        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
        collectionView.backgroundColor = .systemBackground
        collectionView.dataSource = self
        collectionView.delegate = self
        
        
        collectionView.register(UINib(nibName: String(describing: SmallAppCell.self), bundle: nil),forCellWithReuseIdentifier: String(describing: SmallAppCell.self))
        collectionView.register(UINib(nibName: String(describing: MediumAppCell.self), bundle: nil), forCellWithReuseIdentifier: String(describing: MediumAppCell.self))
        collectionView.register(UINib(nibName: String(describing: ExtraLargeAppCell.self), bundle: nil), forCellWithReuseIdentifier: String(describing: ExtraLargeAppCell.self))

        collectionView.register(UINib(nibName: String(describing: CategoryCell.self), bundle: nil), forCellWithReuseIdentifier: String(describing: CategoryCell.self))
        collectionView.register(UINib(nibName: String(describing: SectionTitleCell.self), bundle: nil), forCellWithReuseIdentifier: String(describing: SectionTitleCell.self))

        
        return collectionView
    }()
    lazy var collectionViewLayout: UICollectionViewLayout = {
        var sections = self.sections
        let layout = UICollectionViewCompositionalLayout { (sectionIndex, environment) -> NSCollectionLayoutSection? in
            return sections[sectionIndex].layoutSection()
        } 
        return layout
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        collectionView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(collectionView)

        NSLayoutConstraint.activate([
            collectionView.topAnchor.constraint(equalTo: view.topAnchor),
            collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor)])
    }

}

extension GamesViewController: UICollectionViewDataSource {
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        sections.count
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return sections[section].numberOfItem
        
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        return sections[indexPath.section].configureCell(collectionView: collectionView, indexPath: indexPath)
    }
}

extension GamesViewController: UICollectionViewDelegate {}

각 필요한 Cell들을 Nib 파일로 묶어서 작성했습니다.

import UIKit

final class ExtraLargeAppCell: UICollectionViewCell {
    @IBOutlet private var captionLabel: UILabel!
    @IBOutlet private var titleLabel: UILabel!
    @IBOutlet private var subtitleLabel: UILabel!
    @IBOutlet private var thumbnailView: UIView!

    override func awakeFromNib() {
        super.awakeFromNib()
        thumbnailView.layer.cornerRadius = 4
    }
}

다음으로 Compositional Layout에 필요한 기본 값들을 Protocol로 선언하여 기본 Section값을 할당합니다.

import UIKit

protocol Sections {
    var numberOfItem: Int { get }
    func layoutSection() -> NSCollectionLayoutSection
    func configureCell(collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionViewCell

}

다양한 section들의 기본 값입니다. 고정값이 아닌 상대값으로 layout값을 맞춥니다.

import UIKit

struct FeaturedAppSection: Sections {
    
    let numberOfItem = 5

    func layoutSection() -> NSCollectionLayoutSection {
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)

        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.92), heightDimension: .fractionalHeight(0.4))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])

        let section = NSCollectionLayoutSection(group: group)
        section.orthogonalScrollingBehavior = .groupPagingCentered

        return section
    }
    
    func configureCell(collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: SectionTitleCell.self), for: indexPath)
        return cell
    }
}

호출할 viewController입니다.

import UIKit

final class GamesViewController: UIViewController {
    
    lazy var sections: [Sections] = [ // 불러오기
        FeaturedAppSection(),
        SectionTitleSection(),
        SmallAppSection(),
        SectionTitleSection(),
        MediumAppSection(),
        SectionTitleSection(),
        TodayGameSection(),
        CategorySection(),
        SectionTitleSection(),
        SectionTitleSection(showsNavigation: false),

    ]

    lazy var collectionView: UICollectionView = {
        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
        collectionView.backgroundColor = .systemBackground
        collectionView.dataSource = self
        collectionView.delegate = self
        
        
        collectionView.register(UINib(nibName: String(describing: SmallAppCell.self), bundle: nil),forCellWithReuseIdentifier: String(describing: SmallAppCell.self))
        collectionView.register(UINib(nibName: String(describing: MediumAppCell.self), bundle: nil), forCellWithReuseIdentifier: String(describing: MediumAppCell.self))
        collectionView.register(UINib(nibName: String(describing: ExtraLargeAppCell.self), bundle: nil), forCellWithReuseIdentifier: String(describing: ExtraLargeAppCell.self))

        collectionView.register(UINib(nibName: String(describing: CategoryCell.self), bundle: nil), forCellWithReuseIdentifier: String(describing: CategoryCell.self))
        collectionView.register(UINib(nibName: String(describing: SectionTitleCell.self), bundle: nil), forCellWithReuseIdentifier: String(describing: SectionTitleCell.self))

        
        return collectionView
    }()
    lazy var collectionViewLayout: UICollectionViewLayout = {
        var sections = self.sections
        let layout = UICollectionViewCompositionalLayout { (sectionIndex, environment) -> NSCollectionLayoutSection? in
            return sections[sectionIndex].layoutSection()
        } 
        return layout
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        collectionView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(collectionView)

        NSLayoutConstraint.activate([
            collectionView.topAnchor.constraint(equalTo: view.topAnchor),
            collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor)])
    }

}

extension GamesViewController: UICollectionViewDataSource {
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        sections.count
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return sections[section].numberOfItem
        
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        return sections[indexPath.section].configureCell(collectionView: collectionView, indexPath: indexPath)
    }
}

extension GamesViewController: UICollectionViewDelegate {}

 

참고

 

GitHub - kishikawakatsumi/AppStore-Clone-CollectionViewCompositionalLayouts: Sample project for implementing App Store.app UI wi

Sample project for implementing App Store.app UI with Collection View Compositional Layouts - GitHub - kishikawakatsumi/AppStore-Clone-CollectionViewCompositionalLayouts: Sample project for impleme...

github.com