[UIKit] Modern Collection View: List Configuration

Junyoung Park·2022년 12월 4일
0

UIKit

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

Modern Collection View [2] - List in Collection View | List Cell & Content Configuration

Modern Collection View: List Configuration

구현 목표

  • 컬렉션 뷰 리스트 컨피규레이션을 통한 구현

구현 태스크

  • 컬렉션 뷰 셀 및 헤더 뷰 등록
  • UICollectionViewLayoutListConfiguration을 통한 리스트 타입 선택
  • 셀 액세서리 커스텀

핵심 코드

private var listAppearance: UICollectionLayoutListConfiguration.Appearance = .insetGrouped
    private lazy var listLayout: UICollectionViewLayout = {
        return UICollectionViewCompositionalLayout { _ , layoutEnvironment -> NSCollectionLayoutSection? in
            var listConfig = UICollectionLayoutListConfiguration(appearance: self.listAppearance)
            listConfig.headerMode = .supplementary
            return NSCollectionLayoutSection.list(using: listConfig, layoutEnvironment: layoutEnvironment)
        }
    }()
  • 이니셜라이즈 단에서 미리 선언해둔 리스트 구성 요소
  • 클로저 내부에서 해당 컴포지셔널 레이아웃을 구성
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: listLayout)
  • 컬렉션 뷰를 이니셜라이즈할 때 위의 레이아웃을 사용
private var cellRegistration: UICollectionView.CellRegistration<UICollectionViewListCell, Character>!
  • 셀 등록 또한 UICollectionViewListCell이라는 디폴트로 제공하는 셀 사용
cellRegistration = .init(handler: { [weak self] cell, _, model in
            guard let self = self else { return }
            var content = cell.defaultContentConfiguration()
            content.text = model.name
            content.secondaryText = model.job
            content.image = UIImage(named: model.imageName)
            content.imageProperties.maximumSize = .init(width: 60, height: 60)
            content.imageProperties.cornerRadius = 30
            cell.contentConfiguration = content
            
            if self.selectedCharacters.contains(model) {
                cell.accessories = [.checkmark(), .disclosureIndicator()]
            } else {
                cell.accessories = [.disclosureIndicator()]
            }
            
        })
  • 위의 셀은 contentConfiguration이라는 프로퍼티를 통해 UI를 그림
  • 텍스트, 세컨더리 텍스트, 이미지, 이미지 구성 요소 등 리스트 내 상세한 부분을 커스텀 가능
  • 액세서리 커스텀 가능
    private var headerRegistration: UICollectionView.SupplementaryRegistration<UICollectionViewListCell>!
  • 마찬가지로 리스트 셀을 서플리멘터리로 등록
headerRegistration = .init(elementKind: UICollectionView.elementKindSectionHeader, handler: { [weak self] cell, _, indexPath in
            guard let self = self else { return }
            let sectionedCharacter = self.sectionedCharacters[indexPath.section]
            var content = cell.defaultContentConfiguration()
            content.text = sectionedCharacter.headerTitleText
            cell.contentConfiguration = content
        })
  • 위와 상동. defaultContentConfiguration()을 이번에도 사용하기

소스 코드

import UIKit
import SwiftUI

class CharacterListViewController: UIViewController {
    private var collectionView: UICollectionView!
    private lazy var segmentedControl: UISegmentedControl = {
        let control = UISegmentedControl(items: ["Inset", "Plain", "Grouped", "Sidebar"])
        control.selectedSegmentIndex = 0
        control.addTarget(self, action: #selector(didTapControl), for: .valueChanged)
        return control
    }()
    private var sectionedCharacters: [SectionCharacters] = Universe.ff7r.sectionedStubs {
        didSet {
            collectionView.reloadData()
        }
    }
    private var selectedCharacters = Set<Character>()
    private var cellRegistration: UICollectionView.CellRegistration<UICollectionViewListCell, Character>!
    private var headerRegistration: UICollectionView.SupplementaryRegistration<UICollectionViewListCell>!
    private var listAppearance: UICollectionLayoutListConfiguration.Appearance = .insetGrouped
    private lazy var listLayout: UICollectionViewLayout = {
        return UICollectionViewCompositionalLayout { _ , layoutEnvironment -> NSCollectionLayoutSection? in
            var listConfig = UICollectionLayoutListConfiguration(appearance: self.listAppearance)
            listConfig.headerMode = .supplementary
            return NSCollectionLayoutSection.list(using: listConfig, layoutEnvironment: layoutEnvironment)
        }
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setUI()
    }
    
    private func setUI() {
        view.backgroundColor = .systemBackground
        navigationItem.titleView = segmentedControl
        collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: listLayout)
        collectionView.backgroundColor = .systemBackground
        collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(collectionView)
        cellRegistration = .init(handler: { [weak self] cell, _, model in
            guard let self = self else { return }
            var content = cell.defaultContentConfiguration()
            content.text = model.name
            content.secondaryText = model.job
            content.image = UIImage(named: model.imageName)
            content.imageProperties.maximumSize = .init(width: 60, height: 60)
            content.imageProperties.cornerRadius = 30
            cell.contentConfiguration = content
            
            if self.selectedCharacters.contains(model) {
                cell.accessories = [.checkmark(), .disclosureIndicator()]
            } else {
                cell.accessories = [.disclosureIndicator()]
            }
            
        })
        headerRegistration = .init(elementKind: UICollectionView.elementKindSectionHeader, handler: { [weak self] cell, _, indexPath in
            guard let self = self else { return }
            let sectionedCharacter = self.sectionedCharacters[indexPath.section]
            var content = cell.defaultContentConfiguration()
            content.text = sectionedCharacter.headerTitleText
            cell.contentConfiguration = content
        })
        collectionView.dataSource = self
        collectionView.delegate = self
    }
    
    @objc private func didTapControl() {
        switch segmentedControl.selectedSegmentIndex {
        case 0: listAppearance = .insetGrouped
        case 1: listAppearance = .plain
        case 2: listAppearance = .grouped
        default: listAppearance = .sidebar
        }
        collectionView.collectionViewLayout.invalidateLayout()
    }
}
  • 컬렉션 뷰 리스트를 구현
  • 세그멘트 컨트롤을 통해 현재 리스트 레이아웃의 appearance 결정

extension CharacterListViewController: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        let item = sectionedCharacters[indexPath.section].characters[indexPath.item]
        if selectedCharacters.contains(item) {
            selectedCharacters.remove(item)
        } else {
            selectedCharacters.insert(item)
        }
        collectionView.performBatchUpdates { [weak self] in
            self?.collectionView.reloadItems(at: [indexPath])
        }
    }
}

extension CharacterListViewController: UICollectionViewDataSource {
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return sectionedCharacters.count
    }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return sectionedCharacters[section].characters.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let model = sectionedCharacters[indexPath.section].characters[indexPath.item]
        let cell = collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: model)
        return cell
    }
    
    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
        let header = collectionView.dequeueConfiguredReusableSupplementary(using: headerRegistration, for: indexPath)
        return header
    }
}
  • 셀 및 헤더 뷰를 선언할 때 클로저 내부에서 어떤 형식으로 UI를 그릴지 선언했기 때문에 해당 모델(아이템)만 파라미터에 넘겨주기
  • 특정 셀 선택 시 전역 변수로 관리하고 있는 집합 내 아이템을 넣거나 뺌으로써 체크 여부를 판단할 수 있도록 관리

구현 화면

profile
JUST DO IT
post-custom-banner

0개의 댓글