생각하기
1. 댓글에 대댓글이 존재한다면, 어떻게 화면에 표현할까?
✔️ 댓글 tableView에 대댓글 tableView를 add해서 보여주면 되지 않을까?
✔️ 댓글 Cell과 대댓글 Cell, 두 개의 Cell을 가지고 하나의 tableView에서 보여주면 되지 않을까?
2. 댓글에 대댓글이 존재하지 않는다면?
✔️ 대댓글이 존재할 때만 대댓글 보기 버튼을 보여주면 되지 않을까?
✔️ 대댓글 보기 버튼의 이벤트는 어떻게 처리해야할까?
✔️ 대댓글 보기 버튼의 isHidden을 사용하면 레이아웃도 수정해야되지 않을까?
3. 대댓글 보기 버튼이 있으면 대댓글 닫기 버튼도 있어야하지 않을까?
✔️ 토글을 구현하면 되지 않을까?
4. 셀의 높이는 동적이어야하지 않을까?
✔️ 높이를 정적으로 하면 댓글의 길이에 따라 고려해야하기 때문에 복잡하지 않을까?
구현하기
1. 댓글에 대댓글이 존재한다면, 어떻게 화면에 표현할까?
✔️ 댓글 tableView에 대댓글 tableView를 add해서 보여주면 되지 않을까? ❌
➡️ 댓글 밑에 대댓글을 따로 표시하는 것이기 때문에 댓글과 대댓글 간의 관계를 명확하게 나타낼 수 있지만, 대댓글이 많이 쌓일 경우 스크롤이 매우 길어질 수 있고, 가장 큰 이유는 UI의 복잡도가 증가할 수 있을 것이라고 판단 되었다.
✔️ 댓글 Cell과 대댓글 Cell, 두 개의 Cell을 가지고 하나의 tableView에서 보여주면 되지 않을까? ⭕️
➡️ 댓글과 대댓글이 같은 셀 내에서 표시되므로, 스크롤이 길어질 문제가 없고, UI의 구조가 단순할 것이라고 판단 되었다. 또, 댓글과 대댓글을 하나의 TableView에서 관리하므로 데이터 구조를 간단하게 유지할 수 있지 않을까 싶어서 이 방법을 선택했다.
2. 댓글에 대댓글이 존재하지 않는다면?
✔️ 대댓글이 존재할 때만 대댓글 보기 버튼을 보여주면 되지 않을까?
➡️ 대댓글이 존재하면 대댓글 보기 버튼의 isHidden 속성을 false로, 대댓글이 존재하지 않으면 대댓글 보기 버튼의 isHidden 속성을 true로 설정한다.
✔️ 대댓글 보기 버튼의 isHidden을 사용하면 레이아웃도 수정해야되지 않을까?
➡️ 대댓글이 존재하면 대댓글 보기 버튼의 bottom 속성을 설정하고, 대댓글이 존재하지 않으면 bottom의 기준이 될 다른 UI Component의 bottom 속성을 설정한다.
✔️ 대댓글 보기 버튼의 이벤트는 어떻게 처리해야할까?
➡️ Cell에서의 버튼 이벤트이기 때문에, 클로저로 ViewController에 넘겨서 구현한다.
3. 대댓글 보기 버튼이 있으면 대댓글 닫기 버튼도 있어야하지 않을까?
✔️ 토글을 구현하면 되지 않을까?
➡️ 대댓글이 보이는 상태인지 아닌지 구분하는 식별자를 구현해서 토글 기능을 구현한다.
4. 셀의 높이는 동적이어야하지 않을까?
✔️ 높이를 정적으로 하면 댓글의 길이에 따라 고려해야하기 때문에 복잡하지 않을까?
➡️ TableView의 automaticDimension을 사용해서 구현한다.
구현하기 전에, 생각해 볼 것이 있다.
첫번째, 대댓글이 어떤 댓글의 대댓글인지 어떻게 추적할지?
두번째, 대댓글의 표시 여부를 어떻게 추적할지?
private var isRepliesVisible: [String: Bool] = [:]
나는 각 댓글이 고유한 ID를 가지는 것을 이용해서 구현했다. 각 댓글의 ID를 Key로 사용해서 해당 댓글에 대한 대댓글이 표시되는지 여부를 Value로 저장하는 isRepliesVisible 딕셔너리를 구현했다.
이 딕셔너리는 댓글의 ID를 키로 사용하여 해당 댓글에 대한 대댓글이 표시되는지 여부를 추적한다. 이 딕셔너리를 통해 사용자가 특정 댓글의 대댓글을 토글할 때마다 해당 댓글의 키를 사용해서 표시 여부를 업데이트할 수 있다.
그럼 이 딕셔너리를 가지고 기능을 구현해보자.
func numberOfSections(in tableView: UITableView) -> Int {
return comments.count
}
테이블 뷰에 표시할 섹션의 개수를 반환한다. comments 배열의 요소 수만큼 섹션을 반환한다.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let comment = comments[section]
let isReplyVisible = isRepliesVisible[comment.id] ?? false
return isReplyVisible ? comment.replies.count + 1 : 1
}
주어진 섹션에 포함된 행의 수를 반환한다. 각 섹션은 댓글 하나를 나타낸다. 먼저 comments 배열에서 해당 섹션의 댓글을 가져온다. 그런 다음 해당 댓글의 id를 사용하여 isRepliesVisible 딕셔너리에서 대댓글이 화면에 표시되었는지 여부를 확인한다. 만약 대댓글이 보이는 상태라면 true, 해당 댓글의 대댓글 수에 1을 더해준다. 대댓글이 화면에 보여지지 않은 상태라면 false, 해당 댓글만 표시하므로 1을 반환한다.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let comment = comments[indexPath.section]
if indexPath.row == 0 {
guard let cell = tableView.dequeueReusableCell(withIdentifier: CommentTableViewCell.id, for: indexPath) as? CommentTableViewCell else {
return UITableViewCell()
}
cell.configuration(comment: comment, isRepliesVisible: isRepliesVisible[comment.id] ?? false)
// showRepliesAction 설정
cell.showRepliesAction = { [weak self] in
self?.toggleReplies(for: comment.id)
}
return cell
} else {
let reply = comment.replies[indexPath.row - 1]
guard let cell = tableView.dequeueReusableCell(withIdentifier: ReplyTableViewCell.id, for: indexPath) as? ReplyTableViewCell else {
return UITableViewCell()
}
cell.configuration(reply: reply)
return cell
}
}
주어진 indexPath에 해당하는 테이블 뷰의 셀을 반환한다. 우선 comments 배열에서 해당 섹션에 있는 댓글을 가져온다. indexPath의 row가 0이면 댓글 셀을 의미하므로, 해당 셀은 CommentTableViewCell로부터 재사용 된다.
그 후, 댓글 셀을 구성하고, 해당 셀에 대해 isRepliesVisible 딕셔너리에서 대댓글이 화면에 표시되고 있는지 여부를 확인한다. 이 정보를 사용하여 댓글 셀이 특정 댓글의 대댓글을 표시하거나 숨기는 작업을 한다.
func configuration(comment: Comment, isRepliesVisible: Bool) {
...
if comment.replies.isEmpty {
showReplyButton.isHidden = true
addReplyButton.snp.remakeConstraints {
$0.top.equalTo(commentLabel.snp.bottom).offset(5)
$0.leading.equalTo(profileImageView.snp.trailing).offset(10)
$0.height.equalTo(16)
$0.bottom.equalToSuperview().inset(10)
}
} else {
showReplyButton.isHidden = false
let replyButtonText = isRepliesVisible ? "답글 숨기기" : "답글 \(comment.replies.count)개 보기"
showReplyButton.setTitle(replyButtonText, for: .normal)
addReplyButton.snp.remakeConstraints {
$0.top.equalTo(commentLabel.snp.bottom).offset(5)
$0.leading.equalTo(profileImageView.snp.trailing).offset(10)
$0.height.equalTo(16)
}
showReplyButton.snp.remakeConstraints {
$0.top.equalTo(addReplyButton.snp.bottom).offset(5)
$0.leading.equalTo(profileImageView.snp.trailing).offset(10)
$0.height.equalTo(16)
$0.bottom.equalToSuperview().inset(10)
}
}
}
CommentTableViewCell의 configuration이다. 먼저 대댓글이 존재하는지 여부를 판단하여 답글 보기 버튼의 isHidden 속성을 설정한다. 그리고 버튼의 레이아웃을 설정한다.
만약 isHidden 속성의 여부에 따라 레이아웃을 설정하지 않으면, 버튼이 화면에 보이지 않을 뿐 자리는 차지하고 있기 때문에 셀의 높이에 영향을 준다.
isHidden 속성의 여부에 따라 레이아웃을 설정하여 셀의 높이를 동적으로 설정한다.
// CommentTableViewCell
var showRepliesAction: (() -> Void)?
showReplyButton.rx.tap
.subscribe(with: self, onNext: { owner, _ in
owner.showRepliesAction?()
})
.disposed(by: disposeBag)
그 후, 대댓글을 토글할 때 사용하는 showRepliesAction을 처리한다. showRepliesAction은 댓글 셀의 답글 보기 버튼의 Tap 이벤트를 처리하는 클로저이다.
extension CommentViewController: UITableViewDelegate {
private func toggleReplies(for commentId: String) {
let isReplyVisible = isRepliesVisible[commentId] ?? false
isRepliesVisible[commentId] = !isReplyVisible
tableView.reloadData()
}
}
이 메소드는 commentId를 매개변수로 받는다. 이는 특정 댓글의 식별자이다. 함수 내부에서는 해당 댓글의 대댓글이 보이는 상태인지 확인한다. isRepliesVisible 딕셔너리를 사용하여 댓글의 대댓글이 보이는지 여부를 저장한다. 만약 해당 댓글의 대댓글이 보이는 상태라면 true를, 그렇지 않다면 false를 반환한다. 이때 ?? false를 사용하여 해당 댓글의 대댓글이 보이지 않는 경우를 기본값으로 설정한다.
그런 다음, 현재 상태를 반전시킨다. 이를 통해 대댓글이 보이는 상태에서는 감추고, 감춰진 상태에서는 보이도록 변경한다.
isRepliesVisible 딕셔너리를 업데이트한 후에는 tableView.reloadData()를 호출하여 테이블 뷰를 새로고침한다.
다음은 indexPath의 row가 0이 아닌 경우, 해당 댓글에 대한 대댓글을 나타낸다. 따라서 대댓글 셀을 의미하며, ReplyTableViewCell로부터 재사용된다. 대댓글은 댓글 셀 아래에 표시되므로, comment.replies 배열에서 해당 댓글의 대댓글을 가져와 대댓글 셀을 구성한다.
마지막으로 구성된 댓글 셀 또는 대댓글 셀을 반환한다.
'ToyProject - 사카마카 (살까말까 고민 될 때는 사카마카)' 카테고리의 다른 글
[사카마카/회고] 사카마카 개발을 마무리 하며 (1) | 2024.06.11 |
---|---|
[사카마카] 키보드 이벤트를 감지하여 화면의 레이아웃을 업데이트 해보자. (0) | 2024.06.01 |
[사카마카/문제해결] 앱 내에서 웹을 보여줄 때 발생하는 스킴 문제를 해결해보자. (0) | 2024.05.30 |
[사카마카] 앱 내에서 웹을 보여주는 방법에 대해 알아보자. (0) | 2024.05.30 |
[사카마카] Lottie로 애니메이션을 사용해보자. (0) | 2024.05.29 |