iOS/Toy project

[iOS : Toy Project] Spotify Paywall : CollectionView, Paging Control

yevdev 2022. 6. 28. 12:26

📌 열번째 프로젝트 

스포티파이 구매뷰 앱을 만들어보자

 

 

요번 포스팅부터는 ViewController 등 기본 세팅과 AutoLayout의 내용들은 다루지 않겠다! (기억해둘건 당연히 메모해둘것)

 

1️⃣ AutoLayout

 

2️⃣ CollectionViewCell 만들기

- BannerCell 이란 이름의 CollectionView cell

//
//  BannerCell.swift
//  SpotifyPaywall
//
//  Created by 오예진 on 2022/06/28.
//

import UIKit

class BannerCell: UICollectionViewCell {
    
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var descriptionLabel: UILabel!
    @IBOutlet weak var thumbnailImageView: UIImageView!
    
    // Data 받아서 구성
    func configure(_ info: BannerInfo) {
        titleLabel.text = info.title
        descriptionLabel.text = info.description
        thumbnailImageView.image = UIImage(named: info.imageName)
    }
    
}

 

 

3️⃣ ViewController 작성

- diffable datasource, snapshot, compositional layout

//
//  PaywallViewController.swift
//  SpotifyPaywall
//
//  Created by 오예진 on 2022/06/28.
//

import UIKit

class PaywallViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!
    @IBOutlet weak var pageControl: UIPageControl!
    
    let bannerInfos: [BannerInfo] = BannerInfo.list
    let colors: [UIColor] = [.systemPurple, .systemOrange, .systemPink, .systemRed]
    
    // Section과 Item 꼭 잊지 말기
    enum Section {
        case main
    }
    typealias Item = BannerInfo
    var datasource: UICollectionViewDiffableDataSource<Section, Item>!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // presentation:  diffable datasource
        datasource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView, cellProvider: { collectionView, indexPath, item in
            
            // deque = 빼낸다는 의미
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BannerCell", for: indexPath) as? BannerCell else {
                return nil
            }
            cell.configure(item)
            cell.backgroundColor = self.colors[indexPath.item]  // 배경 색으로 아이템 구별
            return cell
        })

        // data: snapshot
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections([.main])    // snapshot에 section 붙여주기
        snapshot.appendItems(bannerInfos, toSection: .main)// snapshot에 item 추가하기
        datasource.apply(snapshot)
        
        // layout: compositional layout
        collectionView.collectionViewLayout = layout()
        
    }
    
    private func layout() -> UICollectionViewCompositionalLayout {
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.8), heightDimension: .absolute(200))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
        
        let section = NSCollectionLayoutSection(group: group)
        
        let layout = UICollectionViewCompositionalLayout(section: section)
        return layout
    }
}

 

여기까지 하면,,

 

 

근데,, 위 코드에서 

let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])

로 했는데 왜 collectionView의 cell들이 수직으로 움직일까?

→ 이유는, 옆으로는 공간 부족이라 받아들여져서 아래로 배치가 된 것이기 때문!

let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .continuous   // 수평방향으로!

요롷게 해주면 수평방향으로 움직이게 됨!

 

 

이제 촥촥 페이지별로 넘어가게 해보자

let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .groupPagingCentered
section.interGroupSpacing = 20  // 아이템들이 서로 떨어지게

groupPagingCentered는 촥촥 넘어갔을때 자동으로 가운데에 맞춰지게 해준다.

groupPaging으로 설정하면, 그냥 촥촥 넘어가게만!

 

 

 

4️⃣ 더 꾸며보기!

- 페이지 둘레를 둥글게

//  BannerCell.swift

import UIKit

class BannerCell: UICollectionViewCell {

// ...
    
    override func awakeFromNib() {
        super.awakeFromNib()
        self.layer.cornerRadius = 16
    }
    
// ...
    
}

 

 

5️⃣ Page Control

- Page Control을 설정하기 위해서는, collectionView 안의 cell들의 index를 정확히 알아야 함

- index를 정확히 알기 위해서는, content size와 offset 등도 알아야 함!

let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .groupPagingCentered   // 수평방향으로!
section.interGroupSpacing = 20  // 아이템들이 서로 떨어지게
section.visibleItemsInvalidationHandler = { (item, offset, env) in

    print(">>> \(env.container.contentSize)")
}

Content Size 알아보기

 

// offset과 contentSize를 계산해서 index 결정하기
let index = Int((offset.x / env.container.contentSize.width).rounded(.up))
print(">>> \(index)")

 

마무리

//
//  PaywallViewController.swift
//  SpotifyPaywall
//
//  Created by 오예진 on 2022/06/28.
//

import UIKit

class PaywallViewController: UIViewController {

    // ...
    override func viewDidLoad() {
        // ...
        
        // 전체 페이지의 개수?
        pageControl.numberOfPages = bannerInfos.count
        
    }
    
    private func layout() -> UICollectionViewCompositionalLayout {
        // ...
        section.visibleItemsInvalidationHandler = { (item, offset, env) in
            
            // offset과 contentSize를 계산해서 index 결정하기
            let index = Int((offset.x / env.container.contentSize.width).rounded(.up))
            print(">>> \(index)")
            self.pageControl.currentPage = index
        }
        // ...
    }
}

 

근데 또 오류가 하나 나던데,, 저 collectionView를 위아래로 움직여보면 살짝쿵 움직인다

해결하기 위해!

// layout: compositional layout
collectionView.collectionViewLayout = layout()

// 위아래로 움직이는 거 막기
collectionView.alwaysBounceVertical = false

// 전체 페이지의 개수?
pageControl.numberOfPages = bannerInfos.count

 

 

 


📌 정리,

  • Presentation → Diffable DataSource
  • Data → Snapshot
  • Layout → Compositional Layout
  • visibleItemsInvalidationHandler 클로저를 통해 Paging을 위한 index 구하기
section.visibleItemsInvalidationHandler = { (item, offset, env) in
    // offset과 contentSize를 계산해서 index 결정하기
    let index = Int((offset.x / env.container.contentSize.width).rounded(.up))
    self.pageControl.currentPage = index
}

 

 


Reference

패스트캠퍼스 온라인 강의