[UIKit] UICollectionView: Headers & Footers 2

Junyoung Park·2022년 10월 28일
0

UIKit

목록 보기
60/142
post-thumbnail
post-custom-banner

CollectionView Headers & Footers (Swift 5, Xcode 12, 2020) - iOS Development

UICollectionView: Headers & Footers 2

구현 목표

  • 컬렉션 뷰의 커스텀 헤더 및 푸터 뷰를 보다 디테일하게 사용하기

구현 태스크

  • 커스텀 컬렉션 뷰 사용
  • 헤더 뷰 및 푸터 뷰 구현
  • flowLayout 구현을 통해 아이템 간격, 셀 사이즈 조정
  • configure 함수를 통해 헤더 및 푸터 데이터 바인딩

핵심 코드

import UIKit

class CustomHeaderCollectionView: UICollectionReusableView {
    static let identifier = "CustomHeaderCollectionView"
    private let imageView: UIImageView = {
        let imageView = UIImageView()
        imageView.clipsToBounds = true
        imageView.contentMode = .scaleAspectFill
        return imageView
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setUI()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        imageView.frame = bounds
    }
    
    private func setUI() {
        backgroundColor = .systemPink
        addSubview(imageView)
    }
    
    func configure(with image: UIImage) {
        imageView.image = image
    }
}
  • 커스텀 헤더 뷰
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
        if kind == UICollectionView.elementKindSectionHeader {
            guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: CustomHeaderCollectionView.identifier, for: indexPath) as? CustomHeaderCollectionView else {
                return UICollectionViewCell()
            }
            if let image = UIImage(named: "header") {
                header.configure(with: image)
            }
            return header
        } else {
            guard let footer = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: CustomFooterCollectionView.identifier, for: indexPath) as? CustomFooterCollectionView else {
                return UICollectionViewCell()
            }
            if let image = UIImage(named: "footer") {
                footer.configure(with: image)
            }
            return footer
        }
    }
  • 컬렉션 뷰 데이터 소스를 바인딩할 때 등록한 kind에 따라 헤더/푸터 뷰를 캐스팅, 커스텀 뷰를 사용하는 코드

소스 코드

import UIKit

class CollectionViewController: UIViewController {
    private let collectionView: UICollectionView = {
        let layout = UICollectionViewFlowLayout()
        layout.minimumLineSpacing = 1
        layout.minimumInteritemSpacing = 1
        layout.scrollDirection = .vertical
        layout.sectionInset = UIEdgeInsets(top: 1, left: 1, bottom: 1, right: 1)
        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
        collectionView.register(CustomCollectionViewCell.self, forCellWithReuseIdentifier: CustomCollectionViewCell.identifier)
        collectionView.register(CustomHeaderCollectionView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: CustomHeaderCollectionView.identifier)
        collectionView.register(CustomFooterCollectionView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: CustomFooterCollectionView.identifier)
        collectionView.alwaysBounceVertical = true
        return collectionView
    }()
    

    override func viewDidLoad() {
        super.viewDidLoad()
        setUI()
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        collectionView.frame = view.bounds
    }
    
    private func setUI() {
        view.backgroundColor = .systemBackground
        view.addSubview(collectionView)
        collectionView.delegate = self
        collectionView.dataSource = self
    }

}

extension CollectionViewController: UICollectionViewDelegate {
    
}

extension CollectionViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CustomCollectionViewCell.identifier, for: indexPath) as? CustomCollectionViewCell else {
            return UICollectionViewCell()
        }
        return cell
    }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 10
    }
    
    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
        if kind == UICollectionView.elementKindSectionHeader {
            guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: CustomHeaderCollectionView.identifier, for: indexPath) as? CustomHeaderCollectionView else {
                return UICollectionViewCell()
            }
            if let image = UIImage(named: "header") {
                header.configure(with: image)
            }
            return header
        } else {
            guard let footer = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: CustomFooterCollectionView.identifier, for: indexPath) as? CustomFooterCollectionView else {
                return UICollectionViewCell()
            }
            if let image = UIImage(named: "footer") {
                footer.configure(with: image)
            }
            return footer
        }
    }
    
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 3
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
        if section == 1 {
            return .zero
        }
        return CGSize(width: view.frame.size.width, height: view.frame.size.width / 2)
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
        return CGSize(width: view.frame.size.width, height: view.frame.size.width / 2)
    }
}

extension CollectionViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let width = view.frame.size.width / 2
        return CGSize(width: width - 3, height: width - 3 )
    }
}
  • 헤더 / 푸터 뷰를 캐스팅할 때 섹션에 따라 조건을 줄 수도 있음
import UIKit

class CustomCollectionViewCell: UICollectionViewCell {
    static let identifier = "CustomCollectionViewCell"
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setUI()
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setUI() {
        contentView.backgroundColor = .link
    }
    
    func configure(with model: String) {
        
    }
}
import UIKit

class CustomHeaderCollectionView: UICollectionReusableView {
    static let identifier = "CustomHeaderCollectionView"
    private let imageView: UIImageView = {
        let imageView = UIImageView()
        imageView.clipsToBounds = true
        imageView.contentMode = .scaleAspectFill
        return imageView
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setUI()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        imageView.frame = bounds
    }
    
    private func setUI() {
        backgroundColor = .systemPink
        addSubview(imageView)
    }
    
    func configure(with image: UIImage) {
        imageView.image = image
    }
}
import UIKit

class CustomFooterCollectionView: UICollectionReusableView {
    static let identifier = "CustomFooterCollectionView"
    private let imageView: UIImageView = {
        let imageView = UIImageView()
        imageView.clipsToBounds = true
        imageView.contentMode = .scaleAspectFill
        return imageView
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setUI()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        imageView.frame = bounds
    }
    
    private func setUI() {
        backgroundColor = .systemYellow
        addSubview(imageView)
    }
    
    func configure(with image: UIImage) {
        imageView.image = image
    }
}
  • 커스텀 헤더 / 푸터 뷰의 커스텀 함수를 사용하려면 컬렉션 뷰의 헤더 / 푸터 뷰를 임의의 뷰로 캐스팅해야 함

구현 화면

헤더 / 푸터 뷰를 사용하는 부분을 다시 한 번 체크!

profile
JUST DO IT
post-custom-banner

0개의 댓글