본문 바로가기

Mobile/iOS

[iOS] CollectionView - 댓글창 만들기(2)

반응형

CollectionView의 CompositionalLayout을 설정해보도록 하겠습니다.
DataSource를 추가해주는 과정은 3편에서 진행합니다.


1. ViewController

급하신 분들은 아래 "더보기"를 클릭하셔서 전체 코드를 사용하세요 !

더보기
//  ViewController.swift

import UIKit
import SnapKit
import Then


class ViewController: UIViewController {
    
    lazy var commentCollectionView: UICollectionView = UICollectionView(
        frame: .zero,
        collectionViewLayout: createLayout()
    ).then {
        $0.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    }
    
    var dataSource:UICollectionViewDiffableDataSource<Int, Int>! = nil
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = .white
        self.setupUI()
        self.setConstarints()
        self.configureDataSource()
    }
    
    fileprivate func setupUI() {
        self.view.addSubview(commentCollectionView)
    }
    
    fileprivate func setConstarints() {
        let safeArea = self.view.safeAreaLayoutGuide.snp
        
        commentCollectionView.snp.makeConstraints {
            $0.edges.equalTo(safeArea.edges).inset(5)
        }
    }
}


extension ViewController {
    fileprivate func createLayout() -> UICollectionViewLayout {
        let config = UICollectionViewCompositionalLayoutConfiguration()
        config.interSectionSpacing = 10
        
        let layout = UICollectionViewCompositionalLayout(
            sectionProvider: {(
                sectionIndex: Int,
                layoutEnvironment: NSCollectionLayoutEnvironment
            ) -> NSCollectionLayoutSection? in
                
                let item = NSCollectionLayoutItem(
                    layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                                       heightDimension: .estimated(50)))
                
                let containerGroup = NSCollectionLayoutGroup.vertical(
                    layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                                       heightDimension: .estimated(1)),
                    subitems: [item])
                
                let section = NSCollectionLayoutSection(group: containerGroup)
                
                let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
                    layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                                       heightDimension: .estimated(50)),
                    elementKind: "header",
                    alignment: .top)
                
                let sectionFooter = NSCollectionLayoutBoundarySupplementaryItem(
                    layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                                       heightDimension: .estimated(9)),
                    elementKind: "footer",
                    alignment: .bottom)
                
                section.boundarySupplementaryItems = [sectionHeader, sectionFooter]
                
                return section
            }, configuration: config)
        
        return layout
    }
}

extension ViewController {
    fileprivate func configureDataSource() {

        let cellRegistration = UICollectionView.CellRegistration<CommentCollectionViewCell, Int> {
            (cell, indexPath, identifier) in
            cell.nickName.text = "\(indexPath.item)"
            cell.comment.text = "안녕하세요 안녕하세요 "
        }

        dataSource = UICollectionViewDiffableDataSource<Int, Int>(
            collectionView: commentCollectionView
        ) {(
            collectionView: UICollectionView,
            indexPath: IndexPath,
            identifier: Int
        ) -> UICollectionViewCell? in
            return collectionView.dequeueConfiguredReusableCell(
                using: cellRegistration,
                for: indexPath,
                item: identifier)}

        let headerRegistration = UICollectionView
            .SupplementaryRegistration<CommentSupplementaryHeaderView>(elementKind: "header") {
                (supplementaryView, string, indexPath) in
                supplementaryView.nickName.text = "\(indexPath.section)"
                supplementaryView.comment.text = "안녕하세요 안녕하세요 안녕하세요 "
            }

        let footerRegistration = UICollectionView
            .SupplementaryRegistration<CommentSupplementaryFooterView>(elementKind: "footer") {
                (supplementaryView, string, indexPath) in
            }

        dataSource.supplementaryViewProvider = {(view, kind, index) in
            if kind == "header" {
                return self.commentCollectionView
                    .dequeueConfiguredReusableSupplementary(using: headerRegistration,
                                                            for: index)
            } else {
                return self.commentCollectionView
                    .dequeueConfiguredReusableSupplementary(using: footerRegistration,
                                                            for: index)
            }
        }
        
        var snapshot = NSDiffableDataSourceSnapshot<Int, Int>()
        var identifierOffset = 0
        let itemPerSection = 3
        
        for idx in 0...10 {
            snapshot.appendSections([idx])
            
            let maxIdentifier = identifierOffset + itemPerSection
            snapshot.appendItems(Array(identifierOffset..<maxIdentifier))
            identifierOffset += itemPerSection
        }
        
        dataSource.apply(snapshot)
    }
}

시간적 여유가 있으신 분들은 아래 코드를 따라가면서 공부해보록 하시죠 !


2. ViewController 초기값 설정

CollectionView를 만들어주고, ViewController에 추가해줍니다. 저는 setupUI( ), setConstraints( )로 UI 관련된 함수를 따로 만들어서 추후에 extension으로 빼줍니다. ViewController 클래스의 가독성이 좋게 하기 위해서에요.  그 전에 일단 ViewController 내부에 함수를 만들어 나중에 분리하기 쉽도록 해줍니다.

class ViewController: UIViewController {
    
    lazy var commentCollectionView: UICollectionView = UICollectionView(
        frame: .zero, // CollectionView의 초기 Frame 값이 들어갑니다.
        collectionViewLayout: // 여기에 Layout이 들어갑니다.
    ).then {
    // An integer bit mask that determines how the receiver
    // resizes itself when its superview’s bounds change.
    // 아래 옵션을 설정하면 superview의 크기 변화에 맞춰 resize 해준다고 하네요.
    // 아래 옵션에 대해서도 추후에 자세히 보겠습니다. 일단은 아래 옵션으로 해주도록 합니다.
        $0.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    }
    
    // 저는 CollectionView에서 받을 데이터소스를 DiffableDataSource로 정했습니다.
    // DiffableDataSource에 대한 내용은 추후에 추가하도록 하겠습니다.
    var dataSource:UICollectionViewDiffableDataSource<Int, Int>! = nil
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = .white
        self.setupUI()
        self.setConstarints()
    }
    
    fileprivate func setupUI() {
        self.view.addSubview(commentCollectionView)
    }
    
    fileprivate func setConstarints() {
        let safeArea = self.view.safeAreaLayoutGuide.snp
        
        commentCollectionView.snp.makeConstraints {
            $0.edges.equalTo(safeArea.edges).inset(5)
        }
    }
}

3. CompositionalLayout 설정

제가 시작할 때 보여드렸던 댓글창의 구성이 기억나시나요? 그 댓글창의 구성을 생각하면서 해야하는데요. 기억이 안나시는 분들을 위해 한번 더 보여드릴게요.

CollectionView의 Layout을 설정할 때는 안쪽부터 하나씩 설정을 해줘야합니다. Item > Group > Section 순으로 말이죠. 그럼 저희가 해야 할 부분은 Item 부분입니다. 우선 Layout을 만들어 줄 함수를 만들어볼게요.

extension ViewController {
    fileprivate func createLayout() -> UICollectionViewLayout {
        let config = UICollectionViewCompositionalLayoutConfiguration()
        config.interSectionSpacing = 10
    }
 }

createLayout을 ViewController에 넣어줄 겁니다. 거기서 실행시켜주면 Layout이 설정되는거에요.  UICollectionViewCompositionalLayoutConfigureation( )  함수는 Layout의 전체적인 설정을 해주는 부분입니다.

(1) Layout 설정

// Layout 설정
extension ViewController {
    fileprivate func createLayout() -> UICollectionViewLayout {
        let config = UICollectionViewCompositionalLayoutConfiguration()
        config.interSectionSpacing = 10
        
        let layout = UICollectionViewCompositionalLayout(
            sectionProvider: {(
                // Section을 구분하기 위한 Index의 타입입니다.
                sectionIndex: Int,
                layoutEnvironment: NSCollectionLayoutEnvironment
            ) -> NSCollectionLayoutSection? in
            
                // 여기에 Item, Group, Section에 대한 설정이 추가됩니다.
                // 내부에서는 NSCollectionLayoutSection 타입을 반환합니다.

            }, configuration: config)
            
        // 그리고 createLayout()은 layout을 반환합니다.
        return layout
    }
}

(2) Item 설정

 let item = NSCollectionLayoutItem(
    layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                       heightDimension: .estimated(50)))

아이템의 Layout을 설정합니다. Dimension 방법은 여러가지 방법이 있습니다.

1. fractional : 비율(0 ~ 1.0)
2. estimated : 추정값
3. absolute : 고정값

비율은 말 그대로 비율로 설정합니다. 추정값은 초기값을 갖고, 실제 아이템의 크기에 따라 변하도록 설정하는 값입니다. 저희는 댓글의 길이에 따라 각 아이템의 높이가 달라지기 때문에 estimated로 설정하겠습니다. 또 높이의 초기값을 50을 주도록 하겠습니다. 왜냐하면 제가 Cell을 설정할 때 ProfileImageView의 크기를 높이 40, 넓이 40으로 설정해두었기 때문입니다. 최소 40의 높이를 가져야 하고 다른 댓글과 조금의 간격이 필요하기 때문에 50을 주었습니다.

여기서 너비는 .fractionalWidth 로 설정되었는데, 아이템의 가로 길이는 아이템의 내용에 따라 바뀌지 않기 때문입니다.  아이템에서 비율의 기준은 Group 입니다.

(3) Group 설정

let containerGroup = NSCollectionLayoutGroup.vertical(
    layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                       heightDimension: .estimated(1)),
    subitems: [item])

다음으로 Group을 설정해줄게요. estimated를 1을 주었습니다. 그 이유는 댓글이 처음 달리면 대댓글은 없는 상태일 가능성이 크기 때문입니다. 그렇다고 0을 주게되면 경고메세지가 뜨니까 1이라도 주셔야합니다 !

subitems에는 item 배열을 넣어주었습니다. 여기서 그룹에 추가된 이 아이템들이 대댓글이 될겁니다.

(4) Section 설정

// Group을 Section에 주가합니다.
let section = NSCollectionLayoutSection(group: containerGroup)

// SectionHeader설정
let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
    layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                       heightDimension: .estimated(50)),
    // 개인적으로 설정 가능. 단, DataSource와 맞춰주어야 합니다.
    elementKind: "header",
    alignment: .top)

let sectionFooter = NSCollectionLayoutBoundarySupplementaryItem(
    layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                       heightDimension: .estimated(9)),
    elementKind: "footer",
    alignment: .bottom)

// section에 Header와 Footer를 추가해주는 부분입니다.
section.boundarySupplementaryItems = [sectionHeader, sectionFooter]

return section

Section에 대한 설정입니다. Group으로 위에서 설정한 containerGroup을 넣어주고, Header와 Footer를 구분해줍니다. Header에 대한 부분은 원댓글이 들어갈 부분이기 때문에 위에 Item과 같이 높이를 50을 주었습니다.

SectionFooter는 더보기, 댓글달기 밖에 없어 크기를 9를 주었습니다.

이제 Layout을 CollectionView에 아래와 같이 넣어주면 됩니다.

class ViewController: UIViewController {
    
    lazy var commentCollectionView: UICollectionView = UICollectionView(
        frame: .zero,
        collectionViewLayout: createLayout()
    ).then {
        $0.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    }

Layout 설정에 관한 전체 코드를 보고 싶으시면 아래 "더보기"를 클릭해주세요.

더보기
extension ViewController {
    fileprivate func createLayout() -> UICollectionViewLayout {
        let config = UICollectionViewCompositionalLayoutConfiguration()
        config.interSectionSpacing = 10
        
        let layout = UICollectionViewCompositionalLayout(
            sectionProvider: {(
                sectionIndex: Int,
                layoutEnvironment: NSCollectionLayoutEnvironment
            ) -> NSCollectionLayoutSection? in
                
                let item = NSCollectionLayoutItem(
                    layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                                       heightDimension: .estimated(50)))
                
                let containerGroup = NSCollectionLayoutGroup.vertical(
                    layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                                       heightDimension: .estimated(0)),
                    subitems: [item])
                
                let section = NSCollectionLayoutSection(group: containerGroup)
                
                let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
                    layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                                       heightDimension: .estimated(50)),
                    elementKind: "header",
                    alignment: .top)
                
                let sectionFooter = NSCollectionLayoutBoundarySupplementaryItem(
                    layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                                       heightDimension: .estimated(9)),
                    elementKind: "footer",
                    alignment: .bottom)
                
                section.boundarySupplementaryItems = [sectionHeader, sectionFooter]
                
                return section
            }, configuration: config)
        
        return layout
    }
}

다음 3편에서는 DataSource 설정에 관한 글을 작성해보도록 하겠습니다.


 

반응형