본문 바로가기

Mobile/iOS

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

반응형

CollectionView로 댓글창을 만들어보려고 합니다. 아래를 따라오시면 다음과 같은 댓글 창을 만드실 수 있습니다 !

 Tool 
 UIKit  SnapKit  ,  Then  ,  CollectionView  ,  CompositionalLayout


1. 구상

구상은 위와 같습니다.

Section: 댓글 전체
Section Header: 댓글
Cell Item: Section Header에 대한 대댓글
Section Footer: 더보기, 댓글달기 등의 기능

즉, Section Header가 원 댓글을 의미하고 Cell Item들은 각각 대댓글을 의미합니다. 그리고 Section은 하나의 댓글입니다.


2. SectionHeader

우선 SectionHeader를 만들어보도록 하겠습니다. 사실 SectionHeader와 Cell Item은 같은 형태이기 때문에 SectionHeader를 먼저 만들어도 되고 Cell Item을 먼저 만들어도 됩니다.

저는 SnapKit과 Then을 이용해서 구성을 만들었습니다. 특이점이라고 한다면 layoutSubviews를 오버라이드 한 부분인데, 이 부분은 profileImageView를 동그란 형태로 자르기 위한 부분입니다. 아래에 Extension을 남겨둘게요.

"더보기"를 누르면 코드를 확인하실 수 있습니다.

더보기
// CommentSupplementaryHeaderView.swift

import Foundation
import UIKit
import SnapKit
import Then

class CommentSupplementaryHeaderView: UICollectionViewCell {
    
    lazy var commentStackView: UIStackView = UIStackView().then {
        $0.axis = .horizontal
        $0.spacing = 3
        $0.alignment = .center
        $0.distribution = .fill
        $0.addArrangedSubview(profileImageView)
        $0.addArrangedSubview(centerView)
        $0.addArrangedSubview(likeView)
        
    }
    
    lazy var profileImageView: UIImageView = UIImageView().then {
        $0.image = UIImage(systemName: "person.circle.fill")
        $0.tintColor = .black
        $0.contentMode = .scaleAspectFit
        $0.clipsToBounds = true
    }
    
    lazy var centerView: UIView = UIView().then {
        $0.addSubview(nickName)
        $0.addSubview(date)
        $0.addSubview(comment)
    }
    
    lazy var nickName: UILabel = UILabel().then {
        $0.text = "별명"
        $0.font = .systemFont(ofSize: 14, weight: .bold)
        $0.numberOfLines = 1
    }
    
    lazy var date: UILabel = UILabel().then {
        $0.text = "2023.08.05 13:10"
        $0.font = .systemFont(ofSize: 10)
        $0.numberOfLines = 1
    }
    
    lazy var comment: UILabel = UILabel().then {
        $0.text = "댓글"
        $0.font = .systemFont(ofSize: 14)
        $0.numberOfLines = 0
        $0.textAlignment = .left
        $0.lineBreakMode = .byCharWrapping
    }
    
    lazy var likeView: UIView = UIView().then {
        $0.addSubview(likeButton)
        $0.addSubview(likeCount)
    }
    
    lazy var likeButton: UIButton = UIButton().then {
        $0.setImage(UIImage(systemName: "heart"), for: .normal)
        $0.tintColor = .red
    }
    
    lazy var likeCount: UILabel = UILabel().then {
        $0.text = "9999"
        $0.font = .systemFont(ofSize: 10)
        $0.numberOfLines = 1
        $0.textAlignment = .center
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.setupUI()
        self.setConstraints()
        self.backgroundColor = .systemGray5
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }
    
    fileprivate func setupUI() {
        self.addSubview(commentStackView)
    }
    
    fileprivate func setConstraints() {
        
        commentStackView.snp.makeConstraints {
            $0.edges.equalToSuperview()
        }
        
        profileImageView.snp.makeConstraints {
            $0.height.equalTo(40)
            $0.width.equalTo(40)
        }
        
        nickName.setContentHuggingPriority(.defaultHigh, for: .vertical)
        nickName.setContentHuggingPriority(.defaultHigh, for: .horizontal)
        date.setContentCompressionResistancePriority(.required, for: .horizontal)
        
        centerView.snp.makeConstraints {
            $0.bottom.equalTo(comment.snp.bottom).offset(5)
        }
        
        nickName.snp.makeConstraints {
            $0.leading.top.equalToSuperview()
        }
        
        date.snp.makeConstraints {
            $0.leading.equalTo(nickName.snp.trailing).offset(5)
            $0.bottom.equalTo(nickName.snp.bottom)
            $0.trailing.lessThanOrEqualToSuperview()
        }
        
        comment.snp.makeConstraints {
            $0.leading.equalTo(nickName.snp.leading)
            $0.top.equalTo(nickName.snp.bottom).offset(5)
            $0.trailing.lessThanOrEqualToSuperview()
            $0.bottom.lessThanOrEqualToSuperview()
        }
        
        likeView.snp.makeConstraints {
            $0.height.equalTo(35)
            $0.width.equalTo(35)
        }
        
        likeButton.snp.makeConstraints {
            $0.horizontalEdges.equalToSuperview()
        }
        
        likeCount.snp.makeConstraints {
            $0.top.equalTo(likeButton.snp.bottom)
            $0.horizontalEdges.equalToSuperview()
            $0.centerX.equalToSuperview()
        }
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        self.profileImageView.setRounded()
    }
}

extension CommentSupplementaryHeaderView {
    func updateUI() {
        // ViewController에서 요소를 설정할 때 사용할 함수입니다.
    }
}
//  UIImageView+Ext.swift

import Foundation
import UIKit

extension UIImageView {
    func setRounded() {
        layer.cornerRadius = bounds.height/2
        layer.masksToBounds = true
    }
}

3. Cell

이제 Cell 부분을 만들어줍니다. 이 부분은 SectionHeader와 거의 비슷합니다. 다른 부분이라면 상속받는 클래스, StackView로 들어가는 요소가 하나 늘어난 거 뿐입니다.

"더보기"를 누르면 코드를 확인하실 수 있습니다.

더보기
// CommentCollectionViewCell.swift

import Foundation
import UIKit
import SnapKit
import Then

// UICollectionViewCell을 상속받습니다.
class CommentCollectionViewCell: UICollectionViewCell {
    
    lazy var commentStackView: UIStackView = UIStackView().then {
        $0.axis = .horizontal
        $0.spacing = 3
        $0.alignment = .center
        $0.distribution = .fill
        $0.addArrangedSubview(commentSperatorImageView)
        $0.addArrangedSubview(profileImageView)
        $0.addArrangedSubview(centerView)
        $0.addArrangedSubview(likeView)
        
    }
    
    // 대댓글이라는 것을 구분해주기 위해 추가해주었습니다.
    lazy var commentSperatorImageView: UIImageView = UIImageView().then {
        $0.image = UIImage(systemName: "ellipsis")
        $0.tintColor = .black
        $0.contentMode = .scaleAspectFit
    }
    
    lazy var profileImageView: UIImageView = UIImageView().then {
        $0.image = UIImage(systemName: "person.circle.fill")
        $0.tintColor = .black
        $0.contentMode = .scaleAspectFit
        $0.clipsToBounds = true
    }
    
    lazy var centerView: UIView = UIView().then {
        $0.addSubview(nickName)
        $0.addSubview(date)
        $0.addSubview(comment)
    }
    
    lazy var nickName: UILabel = UILabel().then {
        $0.text = "별명"
        $0.font = .systemFont(ofSize: 14, weight: .bold)
        $0.numberOfLines = 1
    }
    
    lazy var date: UILabel = UILabel().then {
        $0.text = "2023.08.05 13:10"
        $0.font = .systemFont(ofSize: 10)
        $0.numberOfLines = 1
    }
    
    lazy var comment: UILabel = UILabel().then {
        $0.text = "댓글"
        $0.font = .systemFont(ofSize: 14)
        $0.numberOfLines = 0
        $0.textAlignment = .left
        $0.lineBreakMode = .byCharWrapping
    }
    
    lazy var likeView: UIView = UIView().then {
        $0.addSubview(likeButton)
        $0.addSubview(likeCount)
    }
    
    lazy var likeButton: UIButton = UIButton().then {
        $0.setImage(UIImage(systemName: "heart"), for: .normal)
        $0.tintColor = .red
    }
    
    lazy var likeCount: UILabel = UILabel().then {
        $0.text = "9999"
        $0.font = .systemFont(ofSize: 10)
        $0.numberOfLines = 1
        $0.textAlignment = .center
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.setupUI()
        self.setConstraints()
        self.backgroundColor = .systemGray6
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }
    
    fileprivate func setupUI() {
        self.addSubview(commentStackView)
    }
    
    fileprivate func setConstraints() {
        
        commentStackView.snp.makeConstraints {
            $0.edges.equalToSuperview()
        }
        
        commentSperatorImageView.snp.makeConstraints {
            $0.height.equalTo(10)
            $0.width.equalTo(10)
        }
        
        profileImageView.snp.makeConstraints {
            $0.height.equalTo(40)
            $0.width.equalTo(40)
        }
        
        nickName.setContentHuggingPriority(.defaultHigh, for: .vertical)
        nickName.setContentHuggingPriority(.defaultHigh, for: .horizontal)
        date.setContentCompressionResistancePriority(.required, for: .horizontal)
        
        centerView.snp.makeConstraints {
            $0.bottom.equalTo(comment.snp.bottom).offset(5)
        }
        
        nickName.snp.makeConstraints {
            $0.leading.top.equalToSuperview()
        }
        
        date.snp.makeConstraints {
            $0.leading.equalTo(nickName.snp.trailing).offset(5)
            $0.bottom.equalTo(nickName.snp.bottom)
            $0.trailing.lessThanOrEqualToSuperview()
        }
        
        comment.snp.makeConstraints {
            $0.leading.equalTo(nickName.snp.leading)
            $0.top.equalTo(nickName.snp.bottom).offset(5)
            $0.trailing.lessThanOrEqualToSuperview()
            $0.bottom.lessThanOrEqualToSuperview()
        }
        
        likeView.snp.makeConstraints {
            $0.height.equalTo(35)
            $0.width.equalTo(35)
        }
        
        likeButton.snp.makeConstraints {
            $0.horizontalEdges.equalToSuperview()
        }
        
        likeCount.snp.makeConstraints {
            $0.top.equalTo(likeButton.snp.bottom)
            $0.horizontalEdges.equalToSuperview()
            $0.centerX.equalToSuperview()
        }
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        self.profileImageView.setRounded()
    }
}

extension CommentCollectionViewCell {
    func updateUI() {
        
    }
}

4. SectionFooter

더보기 기능과 댓글달기 기능이 들어갈 Footer를 만들어줍니다.

"더보기"를 누르면 코드를 확인하실 수 있습니다.

더보기
//  SectionFooterView.swift

import Foundation
import UIKit
import Then
import SnapKit

class CommentSupplementaryFooterView: UICollectionReusableView {
    
    lazy var moreButton: UIButton = UIButton().then {
        $0.setTitle("더보기", for: .normal)
        $0.setTitleColor(.black, for: .normal)
        $0.titleLabel?.font = .systemFont(ofSize: 10)
    }
    
    lazy var replyButton: UIButton = UIButton().then {
        $0.setTitle("이어달기", for: .normal)
        $0.setTitleColor(.black, for: .normal)
        $0.titleLabel?.font = .systemFont(ofSize: 10)
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.setupUI()
        self.setConstraints()
        self.backgroundColor = .systemGray5
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }
    
    fileprivate func setupUI() {
        self.addSubview(moreButton)
        self.addSubview(replyButton)
    }
    
    fileprivate func setConstraints() {
        moreButton.snp.makeConstraints {
            $0.verticalEdges.leading.equalToSuperview()
        }
        
        replyButton.snp.makeConstraints {
            $0.verticalEdges.trailing.equalToSuperview()
        }
    }
}

extension CommentSupplementaryFooterView {
    fileprivate func updateUI() {
        
    }
}

여기까지 CollectionView에 들어갈 요소들의 설정이 끝났습니다. 다음 편에서 적용할 수 있는 ViewController를 만들어보도록 할게요!


 

반응형