From 1ae504e80608091f1b18b09c193bca22fe75b595 Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 29 Apr 2026 18:45:18 +0900 Subject: [PATCH 01/31] =?UTF-8?q?add:=20#428=20=EB=8C=93=EA=B8=80,=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../IconSystem/comment.imageset/Contents.json | 21 +++++++++++++++++++ .../IconSystem/comment.imageset/comment.svg | 3 +++ .../IconSystem/heart.imageset/Contents.json | 12 ----------- .../heart.imageset/Property 1=heart.svg | 3 --- .../heart_off.imageset/Contents.json | 21 +++++++++++++++++++ .../heart_off.imageset/heart_off.svg | 3 +++ .../heart_on.imageset/Contents.json | 21 +++++++++++++++++++ .../IconSystem/heart_on.imageset/heart_on.svg | 3 +++ 8 files changed, 72 insertions(+), 15 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/comment.imageset/Contents.json create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/comment.imageset/comment.svg delete mode 100644 ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart.imageset/Contents.json delete mode 100644 ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart.imageset/Property 1=heart.svg create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_off.imageset/Contents.json create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_off.imageset/heart_off.svg create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_on.imageset/Contents.json create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_on.imageset/heart_on.svg diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/comment.imageset/Contents.json b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/comment.imageset/Contents.json new file mode 100644 index 00000000..d4e54c51 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/comment.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "comment.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/comment.imageset/comment.svg b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/comment.imageset/comment.svg new file mode 100644 index 00000000..44e8b841 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/comment.imageset/comment.svg @@ -0,0 +1,3 @@ + + + diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart.imageset/Contents.json b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart.imageset/Contents.json deleted file mode 100644 index de556b1e..00000000 --- a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "Property 1=heart.svg", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart.imageset/Property 1=heart.svg b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart.imageset/Property 1=heart.svg deleted file mode 100644 index b35689a1..00000000 --- a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart.imageset/Property 1=heart.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_off.imageset/Contents.json b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_off.imageset/Contents.json new file mode 100644 index 00000000..d022b3c5 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_off.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "heart_off.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_off.imageset/heart_off.svg b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_off.imageset/heart_off.svg new file mode 100644 index 00000000..6a2d39c7 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_off.imageset/heart_off.svg @@ -0,0 +1,3 @@ + + + diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_on.imageset/Contents.json b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_on.imageset/Contents.json new file mode 100644 index 00000000..b5e8cbaf --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_on.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "heart_on.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_on.imageset/heart_on.svg b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_on.imageset/heart_on.svg new file mode 100644 index 00000000..70bf32ba --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/heart_on.imageset/heart_on.svg @@ -0,0 +1,3 @@ + + + From 25d3085df52827b656d4ef9b7da14f820f8e611c Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 29 Apr 2026 18:45:38 +0900 Subject: [PATCH 02/31] =?UTF-8?q?style:=20#428=20=ED=80=98=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20content,=20=EB=8C=93=EA=B8=80=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuest/Cells/QuestContentView.swift | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/QuestContentView.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/QuestContentView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/QuestContentView.swift new file mode 100644 index 00000000..df9a737c --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/QuestContentView.swift @@ -0,0 +1,133 @@ +// +// QuestContentView.swift +// ByeBoo-iOS +// +// Created by 이나연 on 4/29/26. +// + +import UIKit + +protocol CommonQuestLikeCommentProtocol: AnyObject { + func likeButtonDidTap() +} + +final class QuestContentView: BaseView { + + private let answerContentLabel = UILabel() + private let writtenDateLabel = UILabel() + + private let likeCommentStackView = UIStackView() + private let likeContainerView = UIStackView() + private(set) var likeButton = UIButton() + private let likeCountLabel = UILabel() + private let commentContainerView = UIStackView() + private let commentIcon = UIImageView() + private let commentCountLabel = UILabel() + + weak var delegate: CommonQuestLikeCommentProtocol? + + private var likeCounts: Int = 0 + + override init(frame: CGRect) { + super.init(frame: frame) + setStyle() + setUI() + setLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func setUI() { + self.addSubviews( + answerContentLabel, + writtenDateLabel, + likeCommentStackView + ) + likeCommentStackView.addArrangedSubviews(likeContainerView, commentContainerView) + likeContainerView.addArrangedSubviews(likeButton, likeCountLabel) + commentContainerView.addArrangedSubviews(commentIcon, commentCountLabel) + } + + override func setStyle() { + answerContentLabel.applyByeBooFont( + style: .body3R16, + color: .grayscale100, + numberOfLines: 2 + ) + writtenDateLabel.applyByeBooFont( + style: .cap2R12, + color: .grayscale400 + ) + likeCommentStackView.do { + $0.axis = .horizontal + $0.spacing = 16.adjustedW + } + [likeContainerView, commentContainerView].forEach { + $0.do { + $0.axis = .horizontal + $0.spacing = 4.adjustedW + } + } + likeButton.do { + $0.setImage(.heartOff, for: .normal) + $0.setImage(.heartOn, for: .selected) + $0.addTarget(self, action: #selector(likeButtonDidTap), for: .touchUpInside) + } + commentIcon.do { + $0.image = .comment + } + [likeCountLabel, commentCountLabel].forEach { + $0.do { + $0.text = "0" + $0.applyByeBooFont(style: .cap2R12, color: .grayscale100) + } + } + } + + override func setLayout() { + answerContentLabel.snp.makeConstraints { + $0.top.equalToSuperview() + $0.horizontalEdges.equalToSuperview() + $0.height.equalTo(47.adjustedH) + } + writtenDateLabel.snp.makeConstraints { + $0.top.equalTo(answerContentLabel.snp.bottom).offset(20.adjustedH) + $0.leading.equalToSuperview() + $0.height.equalTo(16.adjustedH) + } + likeCommentStackView.snp.makeConstraints { + $0.centerY.equalTo(writtenDateLabel) + $0.trailing.equalToSuperview() + } + [likeButton, commentIcon].forEach { + $0.snp.makeConstraints { + $0.size.equalTo(20) + } + } + } + + func configure( + content: String, + writtenAt: String, + isLiked: Bool, + likeCount: Int, + commentCount: Int + ) { + self.likeCounts = likeCount + answerContentLabel.text = content + writtenDateLabel.text = writtenAt + likeButton.isSelected = isLiked + likeCountLabel.text = String(likeCount) + commentCountLabel.text = String(commentCount) + } + + @objc + private func likeButtonDidTap() { + likeButton.isSelected.toggle() + likeCounts += likeButton.isSelected ? 1 : -1 + likeCountLabel.text = String(likeCounts) + delegate?.likeButtonDidTap() + } +} From 33d1a932e4df4a87531061759f5550adce6ea625 Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 29 Apr 2026 18:45:50 +0900 Subject: [PATCH 03/31] =?UTF-8?q?feat:=20#428=20=EC=A2=8B=EC=95=84?= =?UTF-8?q?=EC=9A=94=20=EB=88=84=EB=A5=B4=EA=B8=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Cells/CommonQuestAnswerCell.swift | 34 +++++---------- .../Cells/CommonQuestMyAnswerCell.swift | 43 ++++++++----------- .../CommonQuestMyAnswersViewController.swift | 13 +++++- .../CommonQuestViewController.swift | 7 +++ 4 files changed, 46 insertions(+), 51 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestAnswerCell.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestAnswerCell.swift index 7419c854..034016d9 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestAnswerCell.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestAnswerCell.swift @@ -16,8 +16,7 @@ final class CommonQuestAnswerCell: UITableViewCell { private let containerView = UIView() private let userIconView = UIImageView() private let userNicknameLabel = UILabel() - private let answerContentLabel = UILabel() - private let writtenDateLabel = UILabel() + private(set) var questContentView = QuestContentView() override init( style: UITableViewCell.CellStyle, @@ -47,24 +46,14 @@ final class CommonQuestAnswerCell: UITableViewCell { style: .body6R14, color: .grayscale200 ) - answerContentLabel.applyByeBooFont( - style: .body3R16, - color: .grayscale100, - numberOfLines: 0 - ) - writtenDateLabel.applyByeBooFont( - style: .body6R14, - color: .grayscale400 - ) } private func setUI() { - addSubview(containerView) + contentView.addSubview(containerView) containerView.addSubviews( userIconView, userNicknameLabel, - answerContentLabel, - writtenDateLabel + questContentView ) } @@ -84,14 +73,9 @@ final class CommonQuestAnswerCell: UITableViewCell { $0.leading.equalTo(userIconView.snp.trailing).offset(4.adjustedW) $0.centerY.equalTo(userIconView.snp.centerY) } - answerContentLabel.snp.makeConstraints { - $0.top.equalTo(userIconView.snp.bottom).offset(12.adjustedH) + questContentView.snp.makeConstraints { + $0.top.equalTo(userNicknameLabel.snp.bottom).offset(12.adjustedH) $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) - $0.height.equalTo(47.adjustedH) - } - writtenDateLabel.snp.makeConstraints { - $0.top.equalTo(answerContentLabel.snp.bottom).offset(20.adjustedH) - $0.leading.equalToSuperview().inset(24.adjustedW) $0.bottom.equalToSuperview().inset(16.adjustedH) } } @@ -108,9 +92,13 @@ extension CommonQuestAnswerCell { userIconView.image = profileIcon } userNicknameLabel.text = answer.writer - answerContentLabel.text = answer.content - writtenDateLabel.text = writtenAt answerID = answer.answerID + questContentView.configure( + content: answer.content, + writtenAt: writtenAt, + isLiked: false, + likeCount: 4, + commentCount: 3) } func getAnswewrID() -> Int? { diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestMyAnswerCell.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestMyAnswerCell.swift index 1df86b35..9b33af32 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestMyAnswerCell.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestMyAnswerCell.swift @@ -13,8 +13,7 @@ final class CommonQuestMyAnswerCell: UITableViewCell { private let questionView = UIView() private let questionMarkLabel = UILabel() private let questionContentLabel = UILabel() - private let answerContentLabel = UILabel() - private let writtenDateLabel = UILabel() + private(set) var questContentView = QuestContentView() override init( style: UITableViewCell.CellStyle, @@ -53,23 +52,13 @@ final class CommonQuestMyAnswerCell: UITableViewCell { ) $0.lineBreakStrategy = [] } - answerContentLabel.applyByeBooFont( - style: .body3R16, - color: .grayscale100, - numberOfLines: 2 - ) - writtenDateLabel.applyByeBooFont( - style: .cap2R12, - color: .grayscale400 - ) } private func setUI() { contentView.addSubview(containerView) containerView.addSubviews( questionView, - answerContentLabel, - writtenDateLabel + questContentView ) questionView.addSubviews( questionMarkLabel, @@ -86,25 +75,19 @@ final class CommonQuestMyAnswerCell: UITableViewCell { $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) } questionMarkLabel.snp.makeConstraints { - $0.top.equalTo(questionContentLabel.snp.top) + $0.top.equalToSuperview() $0.leading.equalToSuperview() $0.width.equalTo(17.adjustedW) } questionContentLabel.snp.makeConstraints { - $0.verticalEdges.equalToSuperview() + $0.top.equalToSuperview() $0.leading.equalTo(questionMarkLabel.snp.trailing).offset(4.adjustedW) $0.trailing.equalToSuperview() } - answerContentLabel.snp.makeConstraints { - $0.top.equalTo(questionView.snp.bottom).offset(12.adjustedH) + questContentView.snp.makeConstraints { + $0.top.equalTo(questionContentLabel.snp.bottom).offset(12.adjustedH) $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) - $0.height.equalTo(47.adjustedH) - } - writtenDateLabel.snp.makeConstraints { - $0.top.equalTo(answerContentLabel.snp.bottom).offset(20.adjustedH) - $0.leading.equalToSuperview().inset(24.adjustedW) $0.bottom.equalToSuperview().inset(16.adjustedH) - $0.height.equalTo(16.adjustedH) } } } @@ -114,10 +97,18 @@ extension CommonQuestMyAnswerCell { func bind( question: String, content: String, - writtenAt: String + writtenAt: String, + isLiked: Bool, + likeCount: Int, + commentCount: Int ) { questionContentLabel.text = question - answerContentLabel.text = content - writtenDateLabel.text = writtenAt + questContentView.configure( + content: content, + writtenAt: writtenAt, + isLiked: isLiked, + likeCount: likeCount, + commentCount: commentCount + ) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift index 97473a87..d6a3b842 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift @@ -194,12 +194,21 @@ extension CommonQuestMyAnswersViewController: UITableViewDataSource { } let cell: CommonQuestMyAnswerCell = tableView.dequeueReusableCell(for: indexPath) - + cell.questContentView.delegate = self cell.bind( question: answer.question, content: answer.content, - writtenAt: answer.writtenAt + writtenAt: answer.writtenAt, + isLiked: true, + likeCount: 3, + commentCount: 3 ) return cell } } + +extension CommonQuestMyAnswersViewController: CommonQuestLikeCommentProtocol { + func likeButtonDidTap() { + // TODO: like button + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift index 924c64bd..e54c7b10 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift @@ -260,6 +260,7 @@ extension CommonQuestViewController: UITableViewDataSource { let answer = viewModel.getAnswer(at: indexPath.row - 1) let profileIcon = viewModel.getProfileIcon(at: indexPath.row - 1) let writtenAt = viewModel.getWrittenAt(at: indexPath.row - 1) + cell.questContentView.delegate = self if let answer, let writtenAt { @@ -281,3 +282,9 @@ extension CommonQuestViewController: UITableViewDataSource { return cell } } + +extension CommonQuestViewController: CommonQuestLikeCommentProtocol { + func likeButtonDidTap() { + // TODO: like button + } +} From 23b5086435167af19d8274ceae41953114c84a21 Mon Sep 17 00:00:00 2001 From: yeonee Date: Wed, 29 Apr 2026 23:26:25 +0900 Subject: [PATCH 04/31] =?UTF-8?q?style:=20#428=20=EB=82=98=EC=9D=98?= =?UTF-8?q?=EC=97=AC=EC=A0=95=EC=97=90=EC=84=9C=EB=8F=84=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=EC=95=84=EC=9D=B4=EC=BD=98=EC=9D=B4=20?= =?UTF-8?q?=EB=9C=A8=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuest/Cells/QuestContentView.swift | 36 +++++---- .../CommonQuest/CommonQuestHistoryView.swift | 74 +++++-------------- .../CommonQuestHistoryViewController.swift | 9 ++- .../CommonQuestMyAnswersViewController.swift | 4 +- .../CommonQuestViewController.swift | 7 +- ...WriteQuestionTypeQuestViewController.swift | 2 + 6 files changed, 56 insertions(+), 76 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/QuestContentView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/QuestContentView.swift index df9a737c..66e11cd3 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/QuestContentView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/QuestContentView.swift @@ -14,7 +14,7 @@ protocol CommonQuestLikeCommentProtocol: AnyObject { final class QuestContentView: BaseView { private let answerContentLabel = UILabel() - private let writtenDateLabel = UILabel() + private let writtenDateLabel: UILabel? = nil private let likeCommentStackView = UIStackView() private let likeContainerView = UIStackView() @@ -42,12 +42,15 @@ final class QuestContentView: BaseView { override func setUI() { self.addSubviews( answerContentLabel, - writtenDateLabel, likeCommentStackView ) likeCommentStackView.addArrangedSubviews(likeContainerView, commentContainerView) likeContainerView.addArrangedSubviews(likeButton, likeCountLabel) commentContainerView.addArrangedSubviews(commentIcon, commentCountLabel) + + if let writtenDateLabel { + self.addSubview(writtenDateLabel) + } } override func setStyle() { @@ -56,10 +59,12 @@ final class QuestContentView: BaseView { color: .grayscale100, numberOfLines: 2 ) - writtenDateLabel.applyByeBooFont( - style: .cap2R12, - color: .grayscale400 - ) + if let writtenDateLabel { + writtenDateLabel.applyByeBooFont( + style: .cap2R12, + color: .grayscale400 + ) + } likeCommentStackView.do { $0.axis = .horizontal $0.spacing = 16.adjustedW @@ -92,14 +97,17 @@ final class QuestContentView: BaseView { $0.horizontalEdges.equalToSuperview() $0.height.equalTo(47.adjustedH) } - writtenDateLabel.snp.makeConstraints { - $0.top.equalTo(answerContentLabel.snp.bottom).offset(20.adjustedH) - $0.leading.equalToSuperview() - $0.height.equalTo(16.adjustedH) + if let writtenDateLabel { + writtenDateLabel.snp.makeConstraints { + $0.top.equalTo(answerContentLabel.snp.bottom).offset(20.adjustedH) + $0.leading.equalToSuperview() + $0.height.equalTo(16.adjustedH) + } } likeCommentStackView.snp.makeConstraints { - $0.centerY.equalTo(writtenDateLabel) + $0.top.equalTo(answerContentLabel.snp.bottom).offset(20) $0.trailing.equalToSuperview() + $0.bottom.equalToSuperview() } [likeButton, commentIcon].forEach { $0.snp.makeConstraints { @@ -110,14 +118,16 @@ final class QuestContentView: BaseView { func configure( content: String, - writtenAt: String, + writtenAt: String? = nil, isLiked: Bool, likeCount: Int, commentCount: Int ) { self.likeCounts = likeCount answerContentLabel.text = content - writtenDateLabel.text = writtenAt + if let writtenDateLabel { + writtenDateLabel.text = writtenAt + } likeButton.isSelected = isLiked likeCountLabel.text = String(likeCount) commentCountLabel.text = String(commentCount) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift index 724b42c6..f5a54b1d 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift @@ -9,9 +9,6 @@ import UIKit final class CommonQuestHistoryView: BaseView { - private var profileIcon: UIImage? - private var nickname: String? - private let scrollView = UIScrollView() private let contentView = UIView() private let commonQuestLabel = UILabel() @@ -22,7 +19,8 @@ final class CommonQuestHistoryView: BaseView { private let answerView = UIView() private let profileIconImageView = UIImageView() private let userNicknameLabel = UILabel() - private let answerContentLabel = UILabel() +// private let answerContentLabel = UILabel() + private let questContentView = QuestContentView() override func setStyle() { commonQuestLabel.applyByeBooFont( @@ -55,13 +53,6 @@ final class CommonQuestHistoryView: BaseView { style: .body6R14, color: .grayscale200 ) - answerContentLabel.do { - $0.applyByeBooFont( - style: .body3R16, - color: .grayscale100, - numberOfLines: 0 - ) - } } override func setUI() { @@ -77,18 +68,10 @@ final class CommonQuestHistoryView: BaseView { questionMarkLabel, questionContentLabel ) - - guard let _ = profileIcon, - let _ = nickname - else { - answerView.addSubview(answerContentLabel) - return - } - answerView.addSubviews( profileIconImageView, userNicknameLabel, - answerContentLabel + questContentView ) } @@ -131,18 +114,6 @@ final class CommonQuestHistoryView: BaseView { $0.horizontalEdges.equalToSuperview() $0.bottom.equalToSuperview().inset(24.adjustedH) } - - guard let _ = profileIcon, - let _ = nickname - else { - answerContentLabel.snp.makeConstraints { - $0.top.equalToSuperview().inset(16.adjustedH) - $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) - $0.bottom.equalToSuperview().inset(16.adjustedH) - } - return - } - profileIconImageView.snp.makeConstraints { $0.top.equalToSuperview().inset(16.adjustedH) $0.leading.equalToSuperview().inset(24.adjustedW) @@ -153,7 +124,7 @@ final class CommonQuestHistoryView: BaseView { $0.centerY.equalTo(profileIconImageView) $0.trailing.equalToSuperview().inset(24.adjustedW) } - answerContentLabel.snp.makeConstraints { + questContentView.snp.makeConstraints { $0.top.equalTo(profileIconImageView.snp.bottom).offset(12.adjustedH) $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) $0.bottom.equalToSuperview().inset(16.adjustedH) @@ -166,31 +137,24 @@ extension CommonQuestHistoryView { func configure( question: String, writtenAt: String, - profileIcon: UIImage?, - nickname: String?, - content: String + profileIcon: UIImage, + nickname: String, + content: String, + isLiked: Bool, + likeCount: Int, + commentCount: Int ) { questionContentLabel.text = question dateLabel.text = writtenAt - answerContentLabel.text = content - - answerContentLabel.do { - $0.applyByeBooFont( - style: .body3R16, - text: content, - color: .grayscale100, - numberOfLines: 0 - ) - } - - if let profileIcon { - self.profileIcon = profileIcon - profileIconImageView.image = profileIcon - } + questContentView.configure( + content: content, + isLiked: isLiked, + likeCount: likeCount, + commentCount: commentCount + ) + + profileIconImageView.image = profileIcon + userNicknameLabel.text = nickname - if let nickname { - self.nickname = nickname - userNicknameLabel.text = nickname - } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift index 7c80aab0..c58dd0c9 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -109,8 +109,8 @@ extension CommonQuestHistoryViewController { func configure( question: String, writtenAt: String, - profileIcon: UIImage? = nil, - nickname: String? = nil, + profileIcon: UIImage, + nickname: String, content: String, answerID: Int? = nil, writerID: Int? = nil, @@ -134,7 +134,10 @@ extension CommonQuestHistoryViewController { writtenAt: writtenAt, profileIcon: profileIcon, nickname: nickname, - content: content + content: content, + isLiked: false, + likeCount: 4, + commentCount: 5 ) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift index d6a3b842..0e592468 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestMyAnswersViewController.swift @@ -115,6 +115,8 @@ extension CommonQuestMyAnswersViewController: UITableViewDelegate { historyViewController.configure( question: answer.question, writtenAt: answer.writtenAt, + profileIcon: .relievedBadge, + nickname: "냐냐냐", content: answer.content, answerID: answer.answerID ) @@ -209,6 +211,6 @@ extension CommonQuestMyAnswersViewController: UITableViewDataSource { extension CommonQuestMyAnswersViewController: CommonQuestLikeCommentProtocol { func likeButtonDidTap() { - // TODO: like button + // TODO: like button } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift index e54c7b10..e6cfeb14 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift @@ -137,15 +137,14 @@ extension CommonQuestViewController: UITableViewDelegate { DateFormatter.toDisplayDateString(from: $0) } - guard let formattedWrittenAt else { - return - } + guard let formattedWrittenAt else { return } + guard let profileIcon = viewModel.getProfileIcon(at: answerIndex) else { return } let historyViewController = ViewControllerFactory.shared.makeCommonQuestHistoryViewController() historyViewController.configure( question: viewModel.question, writtenAt: formattedWrittenAt, - profileIcon: viewModel.getProfileIcon(at: answerIndex), + profileIcon: profileIcon, nickname: answer.writer, content: answer.content, answerID: answer.answerID, diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteQuestionTypeQuestViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteQuestionTypeQuestViewController.swift index 0dbd56b0..7f70b05f 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteQuestionTypeQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteQuestionTypeQuestViewController.swift @@ -217,6 +217,8 @@ extension WriteQuestionTypeQuestViewController: ToastPresentable, ToastErrorHand historyVC.configure( question: questTitle, writtenAt: writtenAt, + profileIcon: .relievedBadge, // TODO: 서버 수정 후 고치기 + nickname: "냐냐냐", content: self.rootView.questTextField.textView.text ) } From 8eaeb443a56183e06080331550f865595df8a73b Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 4 May 2026 17:54:54 +0900 Subject: [PATCH 05/31] =?UTF-8?q?refactor:=20#428=20extension=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/Extension/UITextView+.swift | 8 ++++++++ .../{Cells => Common}/QuestContentView.swift | 3 ++- .../Feature/Quest/View/Write/QuestTextField.swift | 14 +++----------- .../WriteQuestionTypeQuestViewController.swift | 2 +- 4 files changed, 14 insertions(+), 13 deletions(-) rename ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/{Cells => Common}/QuestContentView.swift (99%) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UITextView+.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UITextView+.swift index b98111bf..7cf1cc4d 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UITextView+.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UITextView+.swift @@ -37,4 +37,12 @@ extension UITextView { attributedText = NSAttributedString(string: targetText, attributes: attributes) typingAttributes = attributes } + + func applyTextViewStyle(style: FontManager, text: String, color: UIColor) { + self.applyByeBooFont( + style: style, + text: text, + color: color + ) + } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/QuestContentView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift similarity index 99% rename from ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/QuestContentView.swift rename to ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift index 66e11cd3..c5cedb0c 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/QuestContentView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift @@ -30,8 +30,9 @@ final class QuestContentView: BaseView { override init(frame: CGRect) { super.init(frame: frame) - setStyle() + setUI() + setStyle() setLayout() } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/Write/QuestTextField.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/Write/QuestTextField.swift index 7b5e00dc..591dfd99 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/Write/QuestTextField.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/Write/QuestTextField.swift @@ -77,17 +77,17 @@ extension QuestTextField: UITextViewDelegate { func textViewDidBeginEditing(_ textView: UITextView) { if isPlaceholderActive == true { isPlaceholderActive = false - applyTextViewStyle(text: "", color: .grayscale100) + textView.applyTextViewStyle(style: .body3R16 ,text: "", color: .grayscale100) } textView.textColor = .grayscale100 } func textViewDidEndEditing(_ textView: UITextView) { if textView.text.isEmpty { - applyTextViewStyle(text: placeholder, color: .grayscale300) + textView.applyTextViewStyle(style: .body3R16 ,text: placeholder, color: .grayscale300) isPlaceholderActive = true } else { - applyTextViewStyle(text: textView.text, color: .grayscale100) + textView.applyTextViewStyle(style: .body3R16, text: textView.text, color: .grayscale100) } questTextViewDelegate?.textViewDidEndEditing() } @@ -102,14 +102,6 @@ extension QuestTextField: UITextViewDelegate { } extension QuestTextField { - func applyTextViewStyle(text: String, color: UIColor) { - textView.applyByeBooFont( - style: .body3R16, - text: text, - color: color - ) - } - func updateTextViewHeight() -> CGFloat { let width = self.frame.width let fittingSize = CGSize(width: width, height: .greatestFiniteMagnitude) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteQuestionTypeQuestViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteQuestionTypeQuestViewController.swift index 7f70b05f..e027c8dd 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteQuestionTypeQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/WriteQuestionTypeQuestViewController.swift @@ -309,7 +309,7 @@ extension WriteQuestionTypeQuestViewController { private func setQuestTextField(answer: String) { rootView.questTextField.do { - $0.applyTextViewStyle(text: answer, color: .grayscale100) + $0.textView.applyTextViewStyle(style: .body3R16 ,text: answer, color: .grayscale100) $0.isPlaceholderActive = false } rootView.layoutIfNeeded() From 1a38631cdb57acab554aa1acc0e36c5ef6f1249d Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 4 May 2026 17:55:12 +0900 Subject: [PATCH 06/31] =?UTF-8?q?setting:=20#428=20=EB=A6=AC=ED=80=B4?= =?UTF-8?q?=EB=93=9C=EA=B8=80=EB=9E=98=EC=8A=A4=20=EB=AF=B8=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ByeBoo-iOS/ByeBoo-iOS/Resource/Info.plist | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Info.plist b/ByeBoo-iOS/ByeBoo-iOS/Resource/Info.plist index 9687993c..616e9eb9 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Resource/Info.plist +++ b/ByeBoo-iOS/ByeBoo-iOS/Resource/Info.plist @@ -58,5 +58,7 @@ remote-notification + UIDesignRequiresCompatibility + From e695a05225f554a1bade377dbea54f08b546dfcc Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 4 May 2026 17:55:29 +0900 Subject: [PATCH 07/31] =?UTF-8?q?feat:=20#428=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/CommentTextFieldView.swift | 221 ++++++++++++++++++ .../CommonQuest/CommonQuestHistoryView.swift | 25 +- .../CommonQuestHistoryViewController.swift | 62 ++++- 3 files changed, 303 insertions(+), 5 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextFieldView.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextFieldView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextFieldView.swift new file mode 100644 index 00000000..acaf8b3f --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextFieldView.swift @@ -0,0 +1,221 @@ +// +// CommentTextFieldView.swift +// ByeBoo-iOS +// +// Created by 이나연 on 5/4/26. +// + +import UIKit + +final class CommentTextView: BaseView { + private let topBorderLine = UIView() + private let textFieldContainer = UIView() + private let textView = UITextView() + private let countConfirmContainer = UIView() + private let textCountLabel = UILabel() + private let confirmButton = UIButton() + + private let placeholder: String = "댓글로 위로를 남겨보세요." + private var isPlaceholderActive: Bool = true + private let textCountLimit: Int = 500 + + override init(frame: CGRect) { + super.init(frame: frame) + textView.delegate = self + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func setUI() { + addSubviews(topBorderLine,textFieldContainer, confirmButton) + textFieldContainer.addSubviews(textView) + } + + override func setStyle() { + backgroundColor = .grayscale900 + + topBorderLine.do { + $0.backgroundColor = .grayscale800 + } + + textFieldContainer.do { + $0.backgroundColor = .white5 + $0.layer.cornerRadius = 8 + } + + textView.do { + let offset = (FontManager.body6R14.lineHeight - FontManager.body6R14.font.lineHeight) / 2 + $0.backgroundColor = .clear + $0.text = placeholder + $0.font = FontManager.body6R14.font + $0.textColor = .grayscale600 + $0.textContainerInset = .init( + top: offset, + left: 0, + bottom: offset, + right: 0 + ) + $0.isScrollEnabled = false + $0.textContainer.maximumNumberOfLines = 1 + $0.textContainer.lineBreakMode = .byTruncatingTail + } + + textCountLabel.do { + $0.text = "0/\(textCountLimit)" + $0.applyByeBooFont(style: .cap2R12, color: .grayscale400) + } + + confirmButton.do { + $0.applyByeBooFont(style: .body2M16, text: "완료", color: .grayscale600) + } + } + + override func setLayout() { + textFieldContainer.snp.makeConstraints { + $0.top.equalToSuperview().inset(8.adjustedH) + $0.leading.equalToSuperview().inset(24.adjustedW) + $0.trailing.equalTo(confirmButton.snp.leading).offset(-20) + $0.height.equalTo(40.adjustedH) + $0.bottom.equalToSuperview().inset(6.adjustedH) + } + + topBorderLine.snp.makeConstraints { + $0.top.equalToSuperview() + $0.horizontalEdges.equalToSuperview() + $0.height.equalTo(1.adjustedH) + } + + textView.snp.makeConstraints { + $0.verticalEdges.equalToSuperview().inset(9.5.adjustedH) + $0.horizontalEdges.equalToSuperview().inset(12.adjustedW) + } + + confirmButton.snp.makeConstraints { + $0.top.equalToSuperview().inset(17.5.adjustedH) + $0.trailing.equalToSuperview().inset(24.adjustedW) + $0.height.equalTo(21.adjustedH) + } + } + +} + +extension CommentTextView: UITextViewDelegate { + func textViewDidBeginEditing(_ textView: UITextView) { + if isPlaceholderActive { + isPlaceholderActive = false + textView.text = "" + } + // 편집 시작 시 항상 paragraph style을 typingAttributes에 적용 → 줄바꿈 line height 보장 + textView.applyTextViewStyle(style: .body6R14, text: textView.text, color: .grayscale100) + updateTextViewLayoutWhenEditing() + } + + func textViewDidChange(_ textView: UITextView) { + textCountLabel.text = "\(textView.text.count)/500" + let hasText = !textView.text.isEmpty + confirmButton.isEnabled = hasText + confirmButton.applyByeBooFont(style: .body2M16, text: "완료", color: hasText ? .primary300 : .grayscale600) + + if textView.text.count > textCountLimit { + textView.deleteBackward() + return + } + + let maxHeight = 105.adjustedH + let fittingHeight = textView.sizeThatFits(CGSize(width: textView.bounds.width, height: .infinity)).height + if fittingHeight > maxHeight { + if !textView.isScrollEnabled { + textView.isScrollEnabled = true + } + } else { + if textView.isScrollEnabled { + textView.isScrollEnabled = false + textView.contentOffset = .zero + } + } + } + + func textViewDidEndEditing(_ textView: UITextView) { + isPlaceholderActive = textView.text.isEmpty + updateTextViewLayoutWhenEndEditing() + } +} + +extension CommentTextView { + private func updateTextViewLayoutWhenEditing() { + textView.textContainer.maximumNumberOfLines = 0 + textView.textContainer.lineBreakMode = .byWordWrapping + textFieldContainer.backgroundColor = .clear + confirmButton.removeFromSuperview() + addSubviews(countConfirmContainer) + countConfirmContainer.addSubviews(textCountLabel, confirmButton) + + textFieldContainer.snp.remakeConstraints { + $0.top.equalToSuperview().inset(8.adjustedH) + $0.horizontalEdges.equalToSuperview() + $0.bottom.equalTo(countConfirmContainer.snp.top) + } + + textView.snp.remakeConstraints { + $0.top.equalToSuperview().inset(9.adjustedH) + $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) + $0.height.greaterThanOrEqualTo(42.adjustedH) + $0.height.lessThanOrEqualTo(105.adjustedH) + $0.bottom.equalToSuperview().inset(9.adjustedH) + } + + countConfirmContainer.snp.makeConstraints { + $0.trailing.equalToSuperview().inset(24.adjustedW) + $0.bottom.equalToSuperview().inset(8.adjustedH) + $0.height.equalTo(40.adjustedH) + } + + textCountLabel.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.trailing.equalTo(confirmButton.snp.leading).offset(-12.adjustedW) + } + + confirmButton.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.trailing.equalToSuperview() + $0.height.equalTo(40.adjustedH) + } + } + + private func updateTextViewLayoutWhenEndEditing() { + textFieldContainer.backgroundColor = .white5 + countConfirmContainer.removeFromSuperview() + addSubviews(confirmButton) + + textFieldContainer.snp.remakeConstraints { + $0.top.equalToSuperview().inset(8.adjustedH) + $0.leading.equalToSuperview().inset(24.adjustedW) + $0.trailing.equalTo(confirmButton.snp.leading).offset(-20) + $0.height.equalTo(40.adjustedH) + $0.bottom.equalToSuperview().inset(6.adjustedH) + } + + textView.snp.remakeConstraints { + $0.verticalEdges.equalToSuperview().inset(9.5.adjustedH) + $0.horizontalEdges.equalToSuperview().inset(12.adjustedW) + } + + confirmButton.snp.makeConstraints { + $0.top.equalToSuperview().inset(17.5.adjustedH) + $0.trailing.equalToSuperview().inset(24.adjustedW) + $0.height.equalTo(21.adjustedH) + } + + textView.isScrollEnabled = false + textView.textContainer.maximumNumberOfLines = 1 + textView.textContainer.lineBreakMode = .byTruncatingTail + + let text = isPlaceholderActive ? placeholder : (textView.text ?? "") + let color: UIColor = isPlaceholderActive ? .grayscale600 : .grayscale100 + textView.typingAttributes = [.font: FontManager.body6R14.font, .foregroundColor: color] + textView.text = text + textView.textColor = color + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift index f5a54b1d..e35145a4 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift @@ -7,9 +7,13 @@ import UIKit +import SnapKit + final class CommonQuestHistoryView: BaseView { + + private var commentTextViewBottomConstraint: Constraint? - private let scrollView = UIScrollView() + private(set) var scrollView = UIScrollView() private let contentView = UIView() private let commonQuestLabel = UILabel() private let dateLabel = UILabel() @@ -19,8 +23,9 @@ final class CommonQuestHistoryView: BaseView { private let answerView = UIView() private let profileIconImageView = UIImageView() private let userNicknameLabel = UILabel() -// private let answerContentLabel = UILabel() private let questContentView = QuestContentView() + private let commentListView = UITableView() + private let commentTextView = CommentTextView() override func setStyle() { commonQuestLabel.applyByeBooFont( @@ -56,7 +61,7 @@ final class CommonQuestHistoryView: BaseView { } override func setUI() { - addSubview(scrollView) + addSubviews(scrollView, commentTextView) scrollView.addSubview(contentView) contentView.addSubviews( commonQuestLabel, @@ -129,11 +134,23 @@ final class CommonQuestHistoryView: BaseView { $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) $0.bottom.equalToSuperview().inset(16.adjustedH) } + commentTextView.snp.makeConstraints { + $0.horizontalEdges.equalToSuperview() + commentTextViewBottomConstraint = $0.bottom.equalToSuperview().inset(34.adjustedH).constraint + } } } extension CommonQuestHistoryView { - + + func updateLayout(keyboardHeight: CGFloat) { + let inset = keyboardHeight > 0 ? keyboardHeight : 34.adjustedH + commentTextViewBottomConstraint?.update(inset: inset) + } +} + +extension CommonQuestHistoryView { + func configure( question: String, writtenAt: String, diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift index c58dd0c9..00dd03d9 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -26,9 +26,15 @@ final class CommonQuestHistoryViewController: BaseViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.tabBarController?.tabBar.isHidden = true - + addKeyboardObservers() + Mixpanel.mainInstance().track(event: CommonJourneyEvents.Name.commonJourneyOthersAnswerPageview) } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + removeKeyboardObservers() + } override func viewDidLoad() { super.viewDidLoad() @@ -41,6 +47,12 @@ final class CommonQuestHistoryViewController: BaseViewController { secondAction: #selector(bottomUp) ) } + + override func setAddTarget() { + let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard)) + tapGestureRecognizer.cancelsTouchesInView = false + rootView.scrollView.addGestureRecognizer(tapGestureRecognizer) + } } extension CommonQuestHistoryViewController: BackNavigable { @@ -153,3 +165,51 @@ extension CommonQuestHistoryViewController: BlockReportProtocol { } } } + +extension CommonQuestHistoryViewController { + @objc + private func dismissKeyboard() { + view.endEditing(true) + } + + private func addKeyboardObservers() { + NotificationCenter.default.addObserver( + self, + selector: #selector(keyboardWillShow), + name: UIResponder.keyboardWillShowNotification, + object: nil + ) + NotificationCenter.default.addObserver( + self, + selector: #selector(keyboardWillHide), + name: UIResponder.keyboardWillHideNotification, + object: nil + ) + } + + private func removeKeyboardObservers() { + NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil) + NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil) + } + + @objc private func keyboardWillShow(_ notification: Notification) { + guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect, + let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double + else { return } + + UIView.animate(withDuration: duration) { + self.rootView.updateLayout(keyboardHeight: keyboardFrame.height) + self.rootView.layoutIfNeeded() + } + } + + @objc private func keyboardWillHide(_ notification: Notification) { + guard let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double + else { return } + + UIView.animate(withDuration: duration) { + self.rootView.updateLayout(keyboardHeight: 0) + self.rootView.layoutIfNeeded() + } + } +} From a94efc5e1739365479dc4851d42a048d9e602fc3 Mon Sep 17 00:00:00 2001 From: yeonee Date: Mon, 4 May 2026 17:59:56 +0900 Subject: [PATCH 08/31] =?UTF-8?q?chore:=20#428=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Quest/View/CommonQuest/Common/CommentTextFieldView.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextFieldView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextFieldView.swift index acaf8b3f..db3fef89 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextFieldView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextFieldView.swift @@ -107,7 +107,6 @@ extension CommentTextView: UITextViewDelegate { isPlaceholderActive = false textView.text = "" } - // 편집 시작 시 항상 paragraph style을 typingAttributes에 적용 → 줄바꿈 line height 보장 textView.applyTextViewStyle(style: .body6R14, text: textView.text, color: .grayscale100) updateTextViewLayoutWhenEditing() } From 349d4ab47b0a4f66171c72aa2384cf5409cac68c Mon Sep 17 00:00:00 2001 From: yeonee Date: Thu, 7 May 2026 19:53:13 +0900 Subject: [PATCH 09/31] =?UTF-8?q?refactor:=20#428=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EB=90=9C=20DTO=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 추후 서버 연결시 주석해제 --- .../CommonQuestAnswerDetailResponseDTO.swift | 46 ++++++++++++++++ .../CommonQuestAnswerRepliesResponseDTO.swift | 54 +++++++++++++++++++ .../Model/CommonQuestAnswersResponseDTO.swift | 24 +++++++-- 3 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerRepliesResponseDTO.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swift new file mode 100644 index 00000000..42fdd755 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerDetailResponseDTO.swift @@ -0,0 +1,46 @@ +// +// CommonQuestAnswerDetailResponseDTO.swift +// ByeBoo-iOS +// +// Created by 이나연 on 5/5/26. +// + +import Foundation + +struct CommonQuestAnswerDetailResponseDTO: Decodable { + let question: String + let answer: CommonQuestAnswerResponseDTO + let comments: [CommonQuestCommentResponseDTO] +} + +struct CommonQuestCommentResponseDTO: Decodable { + let commentId: Int + let replyCount: Int + let writerId: Int + let writer: String + let profileIcon: String + let writtenAt: String + let content: String +} + +extension CommonQuestAnswerDetailResponseDTO { + func toEntity() -> [CommonQuestCommentEntity] { + comments.map { + $0.toEntity() + } + } +} + +extension CommonQuestCommentResponseDTO { + func toEntity() -> CommonQuestCommentEntity { + .init( + commentID: commentId, + replyCount: replyCount, + writerID: writerId, + writer: writer, + profileIcon: profileIcon, + writtenAt: writtenAt, + content: content + ) + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerRepliesResponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerRepliesResponseDTO.swift new file mode 100644 index 00000000..2c798e0a --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswerRepliesResponseDTO.swift @@ -0,0 +1,54 @@ +// +// CommonQuestAnswerRepliesResponseDTO.swift +// ByeBoo-iOS +// +// Created by 이나연 on 5/5/26. +// + +import Foundation + +struct CommonQuestAnswerRepliesResponseDTO: Decodable { + let totalCount: Int + let comment: CommonQuestAnswerCommentResponseDTO + let replies: [CommonQuestAnswerReplyResponseDTO] +} + +struct CommonQuestAnswerCommentResponseDTO: Decodable { + let content: String + let writer: String + let createdAt: String + let profileIcon: String + let commentId: Int + let writerId: Int +} + +struct CommonQuestAnswerReplyResponseDTO: Decodable { + let content: String + let writer: String + let createdAt: String + let profileIcon: String + let commentId: Int + let writerId: Int +} + +extension CommonQuestAnswerRepliesResponseDTO { + func toEntity() -> [CommonQuestCommentEntity] { + replies.map { + $0.toEntity() + } + } +} + +extension CommonQuestAnswerReplyResponseDTO { + func toEntity() -> CommonQuestCommentEntity { + .init( + commentID: commentId, + replyCount: nil, + writerID: writerId, + writer: writer, + profileIcon: profileIcon, + writtenAt: createdAt, + content: content + ) + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswersResponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswersResponseDTO.swift index 8c3e1aae..32b563fc 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswersResponseDTO.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/CommonQuestAnswersResponseDTO.swift @@ -16,12 +16,17 @@ struct CommonQuestAnswersResponseDTO: Decodable { } struct CommonQuestAnswerResponseDTO: Decodable { +// let likeCount: Int +// let commentCount: Int +// let isLiked: Bool let answerId: Int +// let userId: Int +// let writerId: String let writer: String + let writerId: Int let profileIcon: String let writtenAt: String let content: String - let writerId: Int } extension CommonQuestAnswersResponseDTO { @@ -41,13 +46,26 @@ extension CommonQuestAnswersResponseDTO { extension CommonQuestAnswerResponseDTO { func toEntity(userName: String) -> CommonQuestAnswerEntity { .init( +// isMyAnswer: userName == writerId ? true : false, +// answerID: answerId, +// writerID: writerId, +// profileIcon: profileIcon, +// writtenAt: writtenAt, +// content: content, +// userID: userId, +// likeCount: likeCount, +// commentCount: commentCount, +// isLiked: isLiked isMyAnswer: userName == writer ? true : false, answerID: answerId, - writer: writer, + writerID: writer, profileIcon: profileIcon, writtenAt: writtenAt, content: content, - writerID: writerId + userID: writerId, + likeCount: 3, + commentCount: 4, + isLiked: false, ) } } From 5cb40bfb8935d1acdf46c773f0eb610903b3e8e7 Mon Sep 17 00:00:00 2001 From: yeonee Date: Thu, 7 May 2026 19:53:49 +0900 Subject: [PATCH 10/31] =?UTF-8?q?feat:=20#428=20=EC=9E=AC=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9D=84=20=EC=9C=84=ED=95=B4=20answer=20entity=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=8F=20comment=20entity=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Entity/CommonQuestAnswersEntity.swift | 21 +++++--- .../Entity/CommonQuestCommentEntity.swift | 49 +++++++++++++++++++ 2 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestAnswersEntity.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestAnswersEntity.swift index 8edb6a71..a0d251ff 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestAnswersEntity.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestAnswersEntity.swift @@ -20,11 +20,14 @@ struct CommonQuestAnswersEntity { struct CommonQuestAnswerEntity { let isMyAnswer: Bool let answerID: Int - let writer: String + let writerID: String let profileIcon: String let writtenAt: String let content: String - let writerID: Int + let userID: Int + let likeCount: Int + let commentCount: Int + let isLiked: Bool } extension CommonQuestAnswersEntity { @@ -35,11 +38,14 @@ extension CommonQuestAnswersEntity { CommonQuestAnswerEntity( isMyAnswer: false, answerID: $0, - writer: "유저\($0)", + writerID: "유저\($0)", profileIcon: profileIcons[$0 % 4], writtenAt: Date.now.toString(), content: "\($0)번째 테스트 답변", - writerID: 1 + userID: $0, + likeCount: 1, + commentCount: 1, + isLiked: false ) } @@ -61,11 +67,14 @@ extension CommonQuestAnswerEntity { .init( isMyAnswer: false, answerID: 1, - writer: "장원영", + writerID: "장원영", profileIcon: "SO_SO", writtenAt: "2025-10-12", content: "헤어진 지 벌써 일주일이 지났습니다. 처음에는 실감이 안 나서 눈물조차 나오지 않았어요. 그저 멍하니 천장만 바라보며 시간을 보냈습니다. 그런데 오늘 아침, 습관적으로 휴대폰을 확인하다가 더 이상 '굿모닝' 인사를 보낼 사람이 없다는 사실을 깨닫고 그제야 무너져 내렸습니다. 밥알이 모래알 같아서 잘 넘어가지도 않네요. 친구들은 시간이 약이라고, 더 좋은 사람 만날 거라고 위로하지만 지금 당장은 그 어떤 말도 귀에 들어오지 않습니다.", - writerID: 1 + userID: 12, + likeCount: 3, + commentCount: 4, + isLiked: false ) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift new file mode 100644 index 00000000..3e97accb --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift @@ -0,0 +1,49 @@ +// +// CommonQuestCommentEntity.swift +// ByeBoo-iOS +// +// Created by 이나연 on 5/5/26. +// + +import Foundation + +struct CommonQuestCommentEntity { + let commentID: Int + let replyCount: Int? + let writerID: Int + let writer: String + let profileIcon: String + let writtenAt: String + let content: String +} + +extension CommonQuestCommentEntity { + static func toCommentListStub() -> [Self] { + let stub = toCommentStub() + return [stub, stub, stub, stub, stub] + } + + static func toCommentStub() -> Self { + .init( + commentID: 1, + replyCount: 3, + writerID: 2, + writer: "장원영", + profileIcon: "SADNESS", + writtenAt: "2026-02-19T02:09:43.735730", + content: "나도여ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠ냐냐냐냐냐냐냐냐냐ㅑ냐냐냐냐냐냐ㅑㄴ냐냐ㅑ냐냐냐냔냐냐냐냐냐" + ) + } + + static func toReplyStub() -> Self { + .init( + commentID: 1, + replyCount: nil, + writerID: 2, + writer: "가을이", + profileIcon: "SELF_UNDERSTANDING", + writtenAt: "2026-02-19T02:09:43.735730", + content: "헤어짐" + ) + } +} From 840d3b03e26b534e0809002cb08abe7330b3c1f5 Mon Sep 17 00:00:00 2001 From: yeonee Date: Thu, 7 May 2026 19:54:10 +0900 Subject: [PATCH 11/31] =?UTF-8?q?refactor:=20#428=20profile=20Icon=20Strin?= =?UTF-8?q?g=20->=20Image=20=EC=A0=84=EC=97=AD=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/Enum/ProfileIcon.swift | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/ProfileIcon.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/ProfileIcon.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/ProfileIcon.swift new file mode 100644 index 00000000..da77f388 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Enum/ProfileIcon.swift @@ -0,0 +1,32 @@ +// +// ProfileIcon.swift +// ByeBoo-iOS +// +// Created by 이나연 on 5/5/26. +// + +import UIKit + +enum ProfileIcon: String, CaseIterable { + case sad = "SADNESS" + case selfUnderstanding = "SELF_UNDERSTANDING" + case soso = "SO_SO" + case relieved = "RELIEVED" + + var image: UIImage { + switch self { + case .sad: + return .sadnessBadge + case .selfUnderstanding: + return .selfUnderstandingBadge + case .soso: + return .sosoBadge + case .relieved: + return .relievedBadge + } + } + + static func image(for iconString: String) -> UIImage? { + ProfileIcon(rawValue: iconString)?.image + } +} From 809f5dc59de383e766b03b7c6eae3ad1bf20f405 Mon Sep 17 00:00:00 2001 From: yeonee Date: Thu, 7 May 2026 19:54:40 +0900 Subject: [PATCH 12/31] =?UTF-8?q?feat:=20#428=20Textview=20line=20?= =?UTF-8?q?=EC=88=98=20=EA=B3=84=EC=82=B0=20=EC=9D=B5=EC=8A=A4=ED=85=90?= =?UTF-8?q?=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/Extension/UITextView+.swift | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UITextView+.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UITextView+.swift index 7cf1cc4d..74c2481a 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UITextView+.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UITextView+.swift @@ -45,4 +45,23 @@ extension UITextView { color: color ) } + + func numberOfLine() -> Int { + guard !text.isEmpty else { return 0 } + let size = CGSize(width: frame.width, height: .infinity) + let estimatedSize = sizeThatFits(size) + + let lineHeight: CGFloat + if let paragraphStyle = attributedText?.attribute(.paragraphStyle, at: 0, effectiveRange: nil) as? NSParagraphStyle, + paragraphStyle.minimumLineHeight > 0 { + lineHeight = paragraphStyle.minimumLineHeight + } else if let font = attributedText?.attribute(.font, at: 0, effectiveRange: nil) as? UIFont { + lineHeight = font.lineHeight + } else { + lineHeight = self.font?.lineHeight ?? 0 + } + + guard lineHeight > 0 else { return 0 } + return Int(estimatedSize.height / lineHeight) + } } From 55b01b13f6a79478b634018e4e53112997edfdee Mon Sep 17 00:00:00 2001 From: yeonee Date: Thu, 7 May 2026 19:55:05 +0900 Subject: [PATCH 13/31] =?UTF-8?q?style:=20#428=20=EB=8C=93=EA=B8=80=20UI?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Cells/CommentTableViewCell.swift | 210 ++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift new file mode 100644 index 00000000..43c42103 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift @@ -0,0 +1,210 @@ +// +// CommentCellTableViewCell.swift +// ByeBoo-iOS +// +// Created by 이나연 on 5/4/26. +// + +import UIKit + +final class CommentTableViewCell: UITableViewCell { + + private var replyCommentArrow = UIImageView() + private var replyCommentContainer = UIView() + private let profileIcon = UIImageView(image: .relievedBadge) + private let nicknameLabel = UILabel() + private let dateLabel = UILabel() + private let menuButton = UIImageView() + private let commentTextView = UITextView() + private var moreLabel = UILabel() + private var replyCountContainer = UIStackView() + private var replyCommentIcon = UIImageView() + private var replyCountLabel = UILabel() + + private var replyCount: Int = 0 + + override init( + style: UITableViewCell.CellStyle, + reuseIdentifier: String? + ) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + setUI() + setStyle() + setLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setUI() { + contentView.addSubviews(profileIcon, nicknameLabel, dateLabel, menuButton, commentTextView, moreLabel) + } + + private func setStyle() { + backgroundColor = .grayscale900 + selectionStyle = .none + + nicknameLabel.do { + $0.applyByeBooFont(style: .body5M14, color: .grayscale200) + } + + dateLabel.do { + $0.applyByeBooFont(style: .cap2R12, color: .grayscale400) + } + + menuButton.do { + $0.image = .menu + } + + commentTextView.do { + $0.isEditable = false + $0.isScrollEnabled = false + $0.isUserInteractionEnabled = false + $0.isSelectable = false + $0.backgroundColor = .clear + $0.textContainerInset = .zero + $0.textContainer.lineBreakMode = .byTruncatingTail + $0.textContainer.lineFragmentPadding = 0 + $0.applyByeBooFont(style: .body6R14, color: .grayscale100) + } + + moreLabel.do { + $0.applyByeBooFont(style: .body6R14, text: "더보기", color: .grayscale400) + $0.backgroundColor = .grayscale900 + } + + replyCountContainer.do { + $0.axis = .horizontal + $0.spacing = 4.adjustedW + } + + replyCommentIcon.do { + $0.image = .comment + } + + replyCountLabel.do { + $0.applyByeBooFont(style: .cap2R12, text: String(replyCount), color: .grayscale100) + } + } + + private func setLayout() { + profileIcon.snp.makeConstraints { + $0.top.equalToSuperview().inset(16.adjustedH) + $0.leading.equalToSuperview() + $0.size.equalTo(20.adjustedH) + } + nicknameLabel.snp.makeConstraints { + $0.top.equalToSuperview().inset(16.adjustedH) + $0.leading.equalTo(profileIcon.snp.trailing).offset(4.adjustedW) + } + dateLabel.snp.makeConstraints { + $0.top.equalToSuperview().inset(18.5.adjustedH) + $0.leading.equalTo(nicknameLabel.snp.trailing).offset(4) + } + menuButton.snp.makeConstraints { + $0.top.equalToSuperview().inset(16.adjustedH) + $0.trailing.equalToSuperview() + $0.size.equalTo(24.adjustedH) + } + commentTextView.snp.makeConstraints { + $0.top.equalTo(profileIcon.snp.bottom).offset(4.adjustedH) + $0.leading.equalTo(profileIcon.snp.leading).offset(20) + $0.trailing.equalToSuperview().inset(24.adjustedW) + $0.height.lessThanOrEqualTo(105.adjustedH) + $0.bottom.equalToSuperview().inset(16.adjustedH) + } + moreLabel.snp.makeConstraints { + $0.bottom.equalTo(commentTextView.snp.bottom) + $0.trailing.equalTo(commentTextView.snp.trailing).offset(-20.adjustedW) + } + } + + private func setReplyLayout() { + contentView.addSubviews(replyCommentArrow, replyCountContainer) + replyCountContainer.addArrangedSubviews(replyCommentIcon, replyCountLabel) + + replyCommentArrow.snp.makeConstraints { + $0.top.equalToSuperview().inset(16.adjustedH) + $0.leading.equalToSuperview().inset(24.adjustedH) + $0.size.equalTo(24.adjustedH) + } + replyCountContainer.snp.makeConstraints { + $0.top.equalTo(commentTextView.snp.bottom).offset(8.adjustedH) + $0.leading.equalTo(commentTextView.snp.leading) + $0.height.equalTo(20.adjustedH) + $0.bottom.equalToSuperview().inset(16.adjustedH) + } + replyCommentIcon.snp.makeConstraints { + $0.size.equalTo(20.adjustedH) + } + profileIcon.snp.makeConstraints { + $0.top.equalToSuperview().inset(16.adjustedH) + $0.leading.equalTo(replyCommentArrow.snp.trailing) + $0.size.equalTo(20.adjustedH) + } + commentTextView.snp.remakeConstraints { + $0.top.equalTo(profileIcon.snp.bottom).offset(4.adjustedH) + $0.leading.equalTo(profileIcon.snp.leading).offset(20) + $0.trailing.equalToSuperview().inset(24.adjustedW) + $0.height.lessThanOrEqualTo(105.adjustedH) + } + } +} + +extension CommentTableViewCell { + func configure( + replyCount: Int? = nil, + writer: String, + profileIcon: UIImage, + writtenAt: String, + content: String + ) { + + if let _ = replyCount { + setReplyLayout() + } + nicknameLabel.text = writer + self.profileIcon.image = profileIcon + dateLabel.text = writtenAt + commentTextView.applyTextViewStyle(style: .body6R14, text: content, color: .grayscale100) + + layoutIfNeeded() + + let numberOfLines = commentTextView.numberOfLine() + ByeBooLogger.debug("라인수 \(numberOfLines)") + applyStyleWhenHideText(numberOfLines) + } +} + +extension CommentTableViewCell { + private func applyStyleWhenHideText(_ numberOfLines: Int) { + moreLabel.isHidden = numberOfLines > 5 ? false : true + commentTextView.textContainer.maximumNumberOfLines = 5 + + let moreLabelWidth: CGFloat = moreLabel.intrinsicContentSize.width + let contentHeight = commentTextView.sizeThatFits( + CGSize(width: commentTextView.bounds.width, height: .infinity) + ).height + + + let lineHeight: CGFloat + if let paragraphStyle = commentTextView.attributedText?.attribute(.paragraphStyle, at: 0, effectiveRange: nil) + as? NSParagraphStyle, + paragraphStyle.minimumLineHeight > 0 { + lineHeight = paragraphStyle.minimumLineHeight + } else { + lineHeight = commentTextView.font?.lineHeight ?? 0 + } + + let exclusionRect = CGRect( + x: commentTextView.bounds.width - moreLabelWidth - 10, + y: contentHeight - lineHeight, + width: moreLabelWidth, + height: lineHeight + ) + ByeBooLogger.debug("가로 : \(exclusionRect.width), 세로: \(exclusionRect.height)" ) + commentTextView.textContainer.exclusionPaths = [UIBezierPath(rect: exclusionRect)] + } +} From 70c51087dd6521768faa206f73c32b547624b29f Mon Sep 17 00:00:00 2001 From: yeonee Date: Thu, 7 May 2026 19:58:44 +0900 Subject: [PATCH 14/31] =?UTF-8?q?refactor:=20#428=20entity=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=EC=97=90=20=EB=94=B0=EB=A5=B8=20configure=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EB=82=B4=EB=B6=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/CommonQuest/Cells/CommonQuestAnswerCell.swift | 6 ++++-- .../View/CommonQuest/Cells/CommonQuestMyAnswerCell.swift | 3 ++- .../Quest/ViewController/CommonQuestViewController.swift | 7 +++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestAnswerCell.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestAnswerCell.swift index 034016d9..4cf66ccc 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestAnswerCell.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestAnswerCell.swift @@ -91,14 +91,16 @@ extension CommonQuestAnswerCell { if let profileIcon { userIconView.image = profileIcon } - userNicknameLabel.text = answer.writer + userNicknameLabel.text = answer.writerID answerID = answer.answerID questContentView.configure( content: answer.content, writtenAt: writtenAt, isLiked: false, likeCount: 4, - commentCount: 3) + commentCount: 3, + showAllText: false + ) } func getAnswewrID() -> Int? { diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestMyAnswerCell.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestMyAnswerCell.swift index 9b33af32..650ce1ec 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestMyAnswerCell.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommonQuestMyAnswerCell.swift @@ -108,7 +108,8 @@ extension CommonQuestMyAnswerCell { writtenAt: writtenAt, isLiked: isLiked, likeCount: likeCount, - commentCount: commentCount + commentCount: commentCount, + showAllText: true ) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift index e6cfeb14..f3ca30c7 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestViewController.swift @@ -145,10 +145,10 @@ extension CommonQuestViewController: UITableViewDelegate { question: viewModel.question, writtenAt: formattedWrittenAt, profileIcon: profileIcon, - nickname: answer.writer, + nickname: answer.writerID, content: answer.content, answerID: answer.answerID, - writerID: answer.writerID, + writerID: answer.userID, isMyAnswer: answer.isMyAnswer ) historyViewController.navigationItem.hidesBackButton = true @@ -162,7 +162,7 @@ extension CommonQuestViewController: UITableViewDelegate { _ tableView: UITableView, heightForRowAt indexPath: IndexPath ) -> CGFloat { - indexPath.row == 0 ? UITableView.automaticDimension : 171.adjustedH + UITableView.automaticDimension } func tableView( @@ -260,7 +260,6 @@ extension CommonQuestViewController: UITableViewDataSource { let profileIcon = viewModel.getProfileIcon(at: indexPath.row - 1) let writtenAt = viewModel.getWrittenAt(at: indexPath.row - 1) cell.questContentView.delegate = self - if let answer, let writtenAt { cell.bind( From cac59772d0042a3d01471bda93078dc7784021be Mon Sep 17 00:00:00 2001 From: yeonee Date: Thu, 7 May 2026 19:59:09 +0900 Subject: [PATCH 15/31] =?UTF-8?q?refactor:=20#428=20profileIcon=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=20=EB=B7=B0=EB=AA=A8=EB=8D=B8=20=EB=82=B4?= =?UTF-8?q?=EB=B6=80=EC=97=90=EC=84=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewModel/CommonQuestViewModel.swift | 34 ++----------------- 1 file changed, 3 insertions(+), 31 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift index 06b6f30f..82f1dc2b 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestViewModel.swift @@ -90,27 +90,6 @@ extension CommonQuestViewModel: ViewModelType { } extension CommonQuestViewModel { - - private enum ProfileIcon: String, CaseIterable { - case sad = "SADNESS" - case selfUnderstanding = "SELF_UNDERSTANDING" - case soso = "SO_SO" - case relieved = "RELIEVED" - - var image: UIImage { - switch self { - case .sad: - return .sadnessBadge - case .selfUnderstanding: - return .selfUnderstandingBadge - case .soso: - return .sosoBadge - case .relieved: - return .relievedBadge - } - } - } - var question: String { commonQuest?.question ?? "" } @@ -141,17 +120,10 @@ extension CommonQuestViewModel { } return answers[index] } - + func getProfileIcon(at index: Int) -> UIImage? { - guard index >= 0 && index < answers.count else { - return nil - } - - let iconString = self.answers[index].profileIcon - let profileIcon = ProfileIcon.allCases - .first { $0.rawValue == iconString }? - .image - return profileIcon + guard index >= 0 && index < answers.count else { return nil } + return ProfileIcon.image(for: answers[index].profileIcon) } func getWrittenAt(at index: Int) -> String? { From ba83a4078db88a4f492b6973807f56dce6412d84 Mon Sep 17 00:00:00 2001 From: yeonee Date: Thu, 7 May 2026 19:59:40 +0900 Subject: [PATCH 16/31] =?UTF-8?q?feat:=20#428=20comment=20Table=20View=20V?= =?UTF-8?q?C=EC=97=90=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuestHistoryViewController.swift | 65 ++++++++++++++++--- .../CommonQuestHistoryViewModel.swift | 17 +++++ 2 files changed, 72 insertions(+), 10 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift index 00dd03d9..12a21741 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -12,6 +12,8 @@ import Mixpanel final class CommonQuestHistoryViewController: BaseViewController { private let rootView = CommonQuestHistoryView() + private let viewModel = CommonQuestHistoryViewModel() + private var answerID: Int? private var answer: String? private var question: String? @@ -27,10 +29,10 @@ final class CommonQuestHistoryViewController: BaseViewController { super.viewWillAppear(animated) self.tabBarController?.tabBar.isHidden = true addKeyboardObservers() - + Mixpanel.mainInstance().track(event: CommonJourneyEvents.Name.commonJourneyOthersAnswerPageview) } - + override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) removeKeyboardObservers() @@ -53,6 +55,14 @@ final class CommonQuestHistoryViewController: BaseViewController { tapGestureRecognizer.cancelsTouchesInView = false rootView.scrollView.addGestureRecognizer(tapGestureRecognizer) } + + override func setDelegate() { + rootView.commentListView.do { + $0.delegate = self + $0.dataSource = self + $0.register(CommentTableViewCell.self) + } + } } extension CommonQuestHistoryViewController: BackNavigable { @@ -62,6 +72,41 @@ extension CommonQuestHistoryViewController: BackNavigable { } } +extension CommonQuestHistoryViewController: UITableViewDelegate { + +} + +extension CommonQuestHistoryViewController: UITableViewDataSource { + func tableView( + _ tableView: UITableView, + numberOfRowsInSection section: Int) + -> Int { + ByeBooLogger.debug(viewModel.getCommentsCount()) + return viewModel.getCommentsCount() + } + + func tableView( + _ tableView: UITableView, + cellForRowAt indexPath: IndexPath + ) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell( + withIdentifier: CommentTableViewCell.identifier, + for: indexPath) as? CommentTableViewCell else { return UITableViewCell() } + + let entity = viewModel.commentLists[indexPath.row] + ByeBooLogger.debug(entity) + cell.configure( + replyCount: entity.replyCount, + writer: entity.writer, + profileIcon: ProfileIcon.image(for: entity.profileIcon) ?? .relievedBadge, + writtenAt: entity.writtenAt, + content: entity.content + ) + return cell + } + + +} extension CommonQuestHistoryViewController: CommonQuestBottomSheetDelegate { func didTapEdit( @@ -91,7 +136,7 @@ extension CommonQuestHistoryViewController: CommonQuestBottomSheetDelegate { writtenAt: writtenAt ) setDelegate(bottomSheet: commonQuestBottomSheet) - + if let sheet = commonQuestBottomSheet.sheetPresentationController{ sheet.detents = [.custom { _ in 224.adjustedH }] sheet.prefersGrabberVisible = true @@ -132,7 +177,7 @@ extension CommonQuestHistoryViewController { self.answer = content self.question = question self.writtenAt = writtenAt - + if let isMyAnswer { commonQuestArchiveType = isMyAnswer ? .mine : .other } @@ -171,7 +216,7 @@ extension CommonQuestHistoryViewController { private func dismissKeyboard() { view.endEditing(true) } - + private func addKeyboardObservers() { NotificationCenter.default.addObserver( self, @@ -186,27 +231,27 @@ extension CommonQuestHistoryViewController { object: nil ) } - + private func removeKeyboardObservers() { NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil) } - + @objc private func keyboardWillShow(_ notification: Notification) { guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect, let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else { return } - + UIView.animate(withDuration: duration) { self.rootView.updateLayout(keyboardHeight: keyboardFrame.height) self.rootView.layoutIfNeeded() } } - + @objc private func keyboardWillHide(_ notification: Notification) { guard let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else { return } - + UIView.animate(withDuration: duration) { self.rootView.updateLayout(keyboardHeight: 0) self.rootView.layoutIfNeeded() diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift new file mode 100644 index 00000000..eb0625b5 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestHistoryViewModel.swift @@ -0,0 +1,17 @@ +// +// CommonQuestHistoryViewModel.swift +// ByeBoo-iOS +// +// Created by 이나연 on 5/5/26. +// + +import Combine +import Foundation + +final class CommonQuestHistoryViewModel { + private(set) var commentLists: [CommonQuestCommentEntity] = CommonQuestCommentEntity.toCommentListStub() + + func getCommentsCount() -> Int { + return commentLists.count + } +} From 575545101af75688db8d8081968e89e5596f46cc Mon Sep 17 00:00:00 2001 From: yeonee Date: Thu, 7 May 2026 20:00:11 +0900 Subject: [PATCH 17/31] =?UTF-8?q?fix:=20#428=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EC=9E=AC=EC=82=AC=EC=9A=A9=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=A5=B8=20UI=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuest/Common/QuestContentView.swift | 71 ++++++++++--------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift index c5cedb0c..67a40bb9 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift @@ -11,10 +11,10 @@ protocol CommonQuestLikeCommentProtocol: AnyObject { func likeButtonDidTap() } -final class QuestContentView: BaseView { +final class QuestContentView: UIView { - private let answerContentLabel = UILabel() - private let writtenDateLabel: UILabel? = nil + private let answerContentTextView = UITextView() + private let writtenDateLabel = UILabel() private let likeCommentStackView = UIStackView() private let likeContainerView = UIStackView() @@ -28,8 +28,8 @@ final class QuestContentView: BaseView { private var likeCounts: Int = 0 - override init(frame: CGRect) { - super.init(frame: frame) + init() { + super.init(frame: .zero) setUI() setStyle() @@ -40,31 +40,30 @@ final class QuestContentView: BaseView { fatalError("init(coder:) has not been implemented") } - override func setUI() { + private func setUI() { self.addSubviews( - answerContentLabel, + answerContentTextView, + writtenDateLabel, likeCommentStackView ) likeCommentStackView.addArrangedSubviews(likeContainerView, commentContainerView) likeContainerView.addArrangedSubviews(likeButton, likeCountLabel) commentContainerView.addArrangedSubviews(commentIcon, commentCountLabel) - - if let writtenDateLabel { - self.addSubview(writtenDateLabel) - } } - override func setStyle() { - answerContentLabel.applyByeBooFont( - style: .body3R16, - color: .grayscale100, - numberOfLines: 2 - ) - if let writtenDateLabel { - writtenDateLabel.applyByeBooFont( - style: .cap2R12, - color: .grayscale400 - ) + private func setStyle() { + answerContentTextView.do { + $0.applyByeBooFont(style: .body3R16, color: .grayscale100) + $0.isScrollEnabled = false + $0.isEditable = false + $0.isSelectable = false + $0.isUserInteractionEnabled = false + $0.backgroundColor = .clear + $0.textContainer.lineBreakMode = .byTruncatingTail + } + writtenDateLabel.do { + $0.applyByeBooFont(style: .cap2R12, color: .grayscale400) + $0.isHidden = true } likeCommentStackView.do { $0.axis = .horizontal @@ -92,21 +91,18 @@ final class QuestContentView: BaseView { } } - override func setLayout() { - answerContentLabel.snp.makeConstraints { + private func setLayout() { + answerContentTextView.snp.makeConstraints { $0.top.equalToSuperview() $0.horizontalEdges.equalToSuperview() - $0.height.equalTo(47.adjustedH) } - if let writtenDateLabel { - writtenDateLabel.snp.makeConstraints { - $0.top.equalTo(answerContentLabel.snp.bottom).offset(20.adjustedH) - $0.leading.equalToSuperview() - $0.height.equalTo(16.adjustedH) - } + writtenDateLabel.snp.makeConstraints { + $0.top.equalTo(answerContentTextView.snp.bottom).offset(20.adjustedH) + $0.leading.equalToSuperview() + $0.height.equalTo(16.adjustedH) } likeCommentStackView.snp.makeConstraints { - $0.top.equalTo(answerContentLabel.snp.bottom).offset(20) + $0.top.equalTo(answerContentTextView.snp.bottom).offset(20) $0.trailing.equalToSuperview() $0.bottom.equalToSuperview() } @@ -122,11 +118,16 @@ final class QuestContentView: BaseView { writtenAt: String? = nil, isLiked: Bool, likeCount: Int, - commentCount: Int + commentCount: Int, + showAllText: Bool ) { self.likeCounts = likeCount - answerContentLabel.text = content - if let writtenDateLabel { + answerContentTextView.do { + $0.textContainer.maximumNumberOfLines = showAllText ? 0 : 2 + $0.applyTextViewStyle(style: .body3R16, text: content, color: .grayscale100) + } + if let writtenAt { + writtenDateLabel.isHidden = false writtenDateLabel.text = writtenAt } likeButton.isSelected = isLiked From 4e56be090cd44a4252dbc394043c03f850e60104 Mon Sep 17 00:00:00 2001 From: yeonee Date: Thu, 7 May 2026 20:10:09 +0900 Subject: [PATCH 18/31] style: #428 History View UI --- .../CommonQuest/CommonQuestHistoryView.swift | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift index e35145a4..95241ca0 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift @@ -24,10 +24,13 @@ final class CommonQuestHistoryView: BaseView { private let profileIconImageView = UIImageView() private let userNicknameLabel = UILabel() private let questContentView = QuestContentView() - private let commentListView = UITableView() + private(set) var commentListView = SelfSizingTableView() private let commentTextView = CommentTextView() override func setStyle() { + scrollView.do { + $0.showsVerticalScrollIndicator = false + } commonQuestLabel.applyByeBooFont( style: .body6R14, text: "공통퀘스트", @@ -54,6 +57,9 @@ final class CommonQuestHistoryView: BaseView { $0.layer.cornerRadius = 12 $0.backgroundColor = .white5 } + commentListView.do { + $0.isScrollEnabled = false + } userNicknameLabel.applyByeBooFont( style: .body6R14, color: .grayscale200 @@ -67,7 +73,8 @@ final class CommonQuestHistoryView: BaseView { commonQuestLabel, dateLabel, questionView, - answerView + answerView, + commentListView ) questionView.addSubviews( questionMarkLabel, @@ -84,7 +91,7 @@ final class CommonQuestHistoryView: BaseView { scrollView.snp.makeConstraints { $0.top.equalTo(safeAreaLayoutGuide).offset(16.adjustedH) $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) - $0.bottom.equalToSuperview().inset(24.adjustedH) + $0.bottom.equalTo(commentTextView.snp.top) } contentView.snp.makeConstraints { $0.edges.equalTo(scrollView.contentLayoutGuide) @@ -117,7 +124,6 @@ final class CommonQuestHistoryView: BaseView { answerView.snp.makeConstraints { $0.top.equalTo(questionView.snp.bottom).offset(30.adjustedH) $0.horizontalEdges.equalToSuperview() - $0.bottom.equalToSuperview().inset(24.adjustedH) } profileIconImageView.snp.makeConstraints { $0.top.equalToSuperview().inset(16.adjustedH) @@ -134,6 +140,11 @@ final class CommonQuestHistoryView: BaseView { $0.horizontalEdges.equalToSuperview().inset(24.adjustedW) $0.bottom.equalToSuperview().inset(16.adjustedH) } + commentListView.snp.makeConstraints { + $0.top.equalTo(answerView.snp.bottom).offset(24.adjustedH) + $0.horizontalEdges.equalToSuperview() + $0.bottom.equalToSuperview().inset(24.adjustedH) + } commentTextView.snp.makeConstraints { $0.horizontalEdges.equalToSuperview() commentTextViewBottomConstraint = $0.bottom.equalToSuperview().inset(34.adjustedH).constraint @@ -167,7 +178,8 @@ extension CommonQuestHistoryView { content: content, isLiked: isLiked, likeCount: likeCount, - commentCount: commentCount + commentCount: commentCount, + showAllText: true ) profileIconImageView.image = profileIcon From 5a5b38f98f23a1ec71873d17d6851a1b11b2fc77 Mon Sep 17 00:00:00 2001 From: yeonee Date: Thu, 7 May 2026 20:10:27 +0900 Subject: [PATCH 19/31] =?UTF-8?q?feat:=20#428=20scrollView,=20tableView=20?= =?UTF-8?q?=EC=A4=91=EC=B2=A9=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=9D=B5?= =?UTF-8?q?=EC=8A=A4=ED=85=90=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/Extension/UITableView+.swift | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UITableView+.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UITableView+.swift index d3a196f4..fc2a002b 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UITableView+.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Extension/UITableView+.swift @@ -7,8 +7,22 @@ import UIKit +final class SelfSizingTableView: UITableView { + override var contentSize: CGSize { + didSet { + if oldValue != contentSize { + invalidateIntrinsicContentSize() + } + } + } + + override var intrinsicContentSize: CGSize { + return contentSize + } +} + extension UITableView { - + func dequeueReusableCell(for indexPath: IndexPath) -> T { guard let cell = self.dequeueReusableCell( withIdentifier: T.identifier, From 5997863aca8ca1d178ab06261e57516b7e842cd6 Mon Sep 17 00:00:00 2001 From: yeonee Date: Fri, 8 May 2026 18:49:51 +0900 Subject: [PATCH 20/31] =?UTF-8?q?feat:=20#428=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EB=8D=94=EB=B3=B4=EA=B8=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Entity/CommonQuestCommentEntity.swift | 27 +++--- .../Cells/CommentTableViewCell.swift | 46 ++++++++-- .../CommonQuest/CommonQuestHistoryView.swift | 2 + .../CommonQuestHistoryViewController.swift | 87 +++++++++++++------ 4 files changed, 110 insertions(+), 52 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift index 3e97accb..99532755 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift @@ -7,7 +7,7 @@ import Foundation -struct CommonQuestCommentEntity { +struct CommonQuestCommentEntity: Hashable { let commentID: Int let replyCount: Int? let writerID: Int @@ -19,20 +19,17 @@ struct CommonQuestCommentEntity { extension CommonQuestCommentEntity { static func toCommentListStub() -> [Self] { - let stub = toCommentStub() - return [stub, stub, stub, stub, stub] - } - - static func toCommentStub() -> Self { - .init( - commentID: 1, - replyCount: 3, - writerID: 2, - writer: "장원영", - profileIcon: "SADNESS", - writtenAt: "2026-02-19T02:09:43.735730", - content: "나도여ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠ냐냐냐냐냐냐냐냐냐ㅑ냐냐냐냐냐냐ㅑㄴ냐냐ㅑ냐냐냐냔냐냐냐냐냐" - ) + return (1...5).map { + .init( + commentID: $0, + replyCount: 3, + writerID: 2, + writer: "장원영", + profileIcon: "SADNESS", + writtenAt: "2026-02-19T02:09:43.735730", + content: "나도여ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜ냐냐냐냐냐냐냐냐냐" + ) + } } static func toReplyStub() -> Self { diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift index 43c42103..edd122d2 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift @@ -7,8 +7,14 @@ import UIKit +protocol CommentProtocol: AnyObject { + func moreLabelDidTap(commentID: Int) +} + final class CommentTableViewCell: UITableViewCell { + weak var delegate: CommentProtocol? + private var replyCommentArrow = UIImageView() private var replyCommentContainer = UIView() private let profileIcon = UIImageView(image: .relievedBadge) @@ -22,6 +28,9 @@ final class CommentTableViewCell: UITableViewCell { private var replyCountLabel = UILabel() private var replyCount: Int = 0 + private var commentID: Int = 0 + private var content: String = "" + private var didSetReplyLayout = false override init( style: UITableViewCell.CellStyle, @@ -73,6 +82,9 @@ final class CommentTableViewCell: UITableViewCell { moreLabel.do { $0.applyByeBooFont(style: .body6R14, text: "더보기", color: .grayscale400) $0.backgroundColor = .grayscale900 + $0.isUserInteractionEnabled = true + let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(moreLabelDidTap(_:))) + $0.addGestureRecognizer(tapRecognizer) } replyCountContainer.do { @@ -112,7 +124,6 @@ final class CommentTableViewCell: UITableViewCell { $0.top.equalTo(profileIcon.snp.bottom).offset(4.adjustedH) $0.leading.equalTo(profileIcon.snp.leading).offset(20) $0.trailing.equalToSuperview().inset(24.adjustedW) - $0.height.lessThanOrEqualTo(105.adjustedH) $0.bottom.equalToSuperview().inset(16.adjustedH) } moreLabel.snp.makeConstraints { @@ -122,6 +133,9 @@ final class CommentTableViewCell: UITableViewCell { } private func setReplyLayout() { + guard !didSetReplyLayout else { return } + didSetReplyLayout = true + contentView.addSubviews(replyCommentArrow, replyCountContainer) replyCountContainer.addArrangedSubviews(replyCommentIcon, replyCountLabel) @@ -148,33 +162,42 @@ final class CommentTableViewCell: UITableViewCell { $0.top.equalTo(profileIcon.snp.bottom).offset(4.adjustedH) $0.leading.equalTo(profileIcon.snp.leading).offset(20) $0.trailing.equalToSuperview().inset(24.adjustedW) - $0.height.lessThanOrEqualTo(105.adjustedH) } } } extension CommentTableViewCell { func configure( + commentID: Int, replyCount: Int? = nil, writer: String, profileIcon: UIImage, writtenAt: String, - content: String + content: String, + showAllText: Bool ) { - if let _ = replyCount { setReplyLayout() } + + self.commentID = commentID nicknameLabel.text = writer self.profileIcon.image = profileIcon dateLabel.text = writtenAt + self.content = content commentTextView.applyTextViewStyle(style: .body6R14, text: content, color: .grayscale100) - + layoutIfNeeded() - - let numberOfLines = commentTextView.numberOfLine() - ByeBooLogger.debug("라인수 \(numberOfLines)") - applyStyleWhenHideText(numberOfLines) + + if showAllText { + commentTextView.textContainer.maximumNumberOfLines = 0 + commentTextView.textContainer.exclusionPaths = [] + commentTextView.invalidateIntrinsicContentSize() + moreLabel.isHidden = true + } else { + let numberOfLines = commentTextView.numberOfLine() + applyStyleWhenHideText(numberOfLines) + } } } @@ -207,4 +230,9 @@ extension CommentTableViewCell { ByeBooLogger.debug("가로 : \(exclusionRect.width), 세로: \(exclusionRect.height)" ) commentTextView.textContainer.exclusionPaths = [UIBezierPath(rect: exclusionRect)] } + + @objc + private func moreLabelDidTap(_ tapRecognizer: UITapGestureRecognizer) { + delegate?.moreLabelDidTap(commentID: commentID) + } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift index 95241ca0..d807133c 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestHistoryView.swift @@ -59,6 +59,8 @@ final class CommonQuestHistoryView: BaseView { } commentListView.do { $0.isScrollEnabled = false + $0.rowHeight = UITableView.automaticDimension + $0.estimatedRowHeight = 100 } userNicknameLabel.applyByeBooFont( style: .body6R14, diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift index 12a21741..f493cc70 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -9,8 +9,19 @@ import UIKit import Mixpanel +enum CommentSection: Hashable { + case main +} + +struct CommentItem: Hashable { + let entity: CommonQuestCommentEntity + var showAllText: Bool +} + final class CommonQuestHistoryViewController: BaseViewController { + private var dataSource: UITableViewDiffableDataSource! + private let rootView = CommonQuestHistoryView() private let viewModel = CommonQuestHistoryViewModel() @@ -48,6 +59,9 @@ final class CommonQuestHistoryViewController: BaseViewController { action: #selector(back), secondAction: #selector(bottomUp) ) + + configureDataSource() + applySnapshot() } override func setAddTarget() { @@ -59,7 +73,6 @@ final class CommonQuestHistoryViewController: BaseViewController { override func setDelegate() { rootView.commentListView.do { $0.delegate = self - $0.dataSource = self $0.register(CommentTableViewCell.self) } } @@ -76,37 +89,40 @@ extension CommonQuestHistoryViewController: UITableViewDelegate { } -extension CommonQuestHistoryViewController: UITableViewDataSource { - func tableView( - _ tableView: UITableView, - numberOfRowsInSection section: Int) - -> Int { - ByeBooLogger.debug(viewModel.getCommentsCount()) - return viewModel.getCommentsCount() +extension CommonQuestHistoryViewController { + private func configureDataSource() { + dataSource = UITableViewDiffableDataSource( + tableView: rootView.commentListView + ) { tableView, indexPath, item in + guard let cell = tableView.dequeueReusableCell( + withIdentifier: CommentTableViewCell.identifier, + for: indexPath + ) as? CommentTableViewCell else { return UITableViewCell() } + + let entity = item.entity + cell.configure( + commentID: entity.commentID, + replyCount: entity.replyCount, + writer: entity.writer, + profileIcon: ProfileIcon.image(for: entity.profileIcon) ?? .relievedBadge, + writtenAt: entity.writtenAt, + content: entity.content, + showAllText: item.showAllText + ) + cell.delegate = self + return cell + } } - func tableView( - _ tableView: UITableView, - cellForRowAt indexPath: IndexPath - ) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell( - withIdentifier: CommentTableViewCell.identifier, - for: indexPath) as? CommentTableViewCell else { return UITableViewCell() } - - let entity = viewModel.commentLists[indexPath.row] - ByeBooLogger.debug(entity) - cell.configure( - replyCount: entity.replyCount, - writer: entity.writer, - profileIcon: ProfileIcon.image(for: entity.profileIcon) ?? .relievedBadge, - writtenAt: entity.writtenAt, - content: entity.content - ) - return cell + private func applySnapshot() { + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.main]) + let items = viewModel.commentLists.map { CommentItem(entity: $0, showAllText: false) } + snapshot.appendItems(items, toSection: .main) + dataSource.apply(snapshot, animatingDifferences: false) } - - } + extension CommonQuestHistoryViewController: CommonQuestBottomSheetDelegate { func didTapEdit( @@ -161,6 +177,21 @@ extension CommonQuestHistoryViewController: DeleteCommonQuestDelegate { } } +extension CommonQuestHistoryViewController: CommentProtocol { + func moreLabelDidTap(commentID: Int) { + let updatedItems = dataSource.snapshot().itemIdentifiers.map { item -> CommentItem in + guard item.entity.commentID == commentID else { return item } + return CommentItem(entity: item.entity, showAllText: true) + } + + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.main]) + snapshot.appendItems(updatedItems, toSection: .main) + dataSource.apply(snapshot, animatingDifferences: false) { [weak self] in + self?.rootView.commentListView.performBatchUpdates(nil) + } + } +} extension CommonQuestHistoryViewController { func configure( From c4dbe52ede25a40797d5cdb154809cc54f16fd81 Mon Sep 17 00:00:00 2001 From: yeonee Date: Sun, 10 May 2026 21:53:32 +0900 Subject: [PATCH 21/31] =?UTF-8?q?add:=20#428=20=EB=8B=B5=EA=B8=80=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../IconSystem/replyArrow.imageset/Contents.json | 12 ++++++++++++ .../IconSystem/replyArrow.imageset/icon.svg | 3 +++ 2 files changed, 15 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/replyArrow.imageset/Contents.json create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/replyArrow.imageset/icon.svg diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/replyArrow.imageset/Contents.json b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/replyArrow.imageset/Contents.json new file mode 100644 index 00000000..52ca28db --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/replyArrow.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "icon.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/replyArrow.imageset/icon.svg b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/replyArrow.imageset/icon.svg new file mode 100644 index 00000000..4f6764ab --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Resource/Assets.xcassets/IconSystem/replyArrow.imageset/icon.svg @@ -0,0 +1,3 @@ + + + From 2da80d8ad8b866e9ca1d1e5cea2bdcc1f44971d0 Mon Sep 17 00:00:00 2001 From: yeonee Date: Sun, 10 May 2026 21:53:58 +0900 Subject: [PATCH 22/31] =?UTF-8?q?chore:=20#428=20comment=20entity=20stub?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Entity/CommonQuestCommentEntity.swift | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift index 99532755..035331af 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift @@ -22,7 +22,7 @@ extension CommonQuestCommentEntity { return (1...5).map { .init( commentID: $0, - replyCount: 3, + replyCount: $0 - 1, writerID: 2, writer: "장원영", profileIcon: "SADNESS", @@ -32,6 +32,33 @@ extension CommonQuestCommentEntity { } } + static func toCommentStub() -> Self { + .init( + commentID: 1, + replyCount: 2, + writerID: 2, + writer: "가을이", + profileIcon: "SELF_UNDERSTANDING", + writtenAt: "2026-02-19T02:09:43.735730", + content: "나도여ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜ냐냐냐냐냐냐냐냐냐" + ) + } + + static func toReplyListStub() -> [Self] { + return (1...5).map { + .init( + commentID: $0, + replyCount: nil, + writerID: 2, + writer: "안유진", + profileIcon: "SADNESS", + writtenAt: "2026-02-19T02:09:43.735730", + content: "헤어짐ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠ" + ) + } + } + + static func toReplyStub() -> Self { .init( commentID: 1, From 0d248ecb7e0f7791193d780b64bc60c2bc85515b Mon Sep 17 00:00:00 2001 From: yeonee Date: Sun, 10 May 2026 21:54:29 +0900 Subject: [PATCH 23/31] =?UTF-8?q?feat:=20#428=20=EB=8C=93=EA=B8=80,=20?= =?UTF-8?q?=EB=8B=B5=EA=B8=80=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=EB=B6=84=EA=B8=B0=EC=B2=98=EB=A6=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Cells/CommentTableViewCell.swift | 92 +++++++++++++------ 1 file changed, 66 insertions(+), 26 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift index edd122d2..eed1bea4 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift @@ -9,6 +9,11 @@ import UIKit protocol CommentProtocol: AnyObject { func moreLabelDidTap(commentID: Int) + func replyIconDidTap(commentID: Int) +} + +extension CommentProtocol { + func replyIconDidTap(commentID: Int) { } } final class CommentTableViewCell: UITableViewCell { @@ -31,6 +36,7 @@ final class CommentTableViewCell: UITableViewCell { private var commentID: Int = 0 private var content: String = "" private var didSetReplyLayout = false + private var didSetCommentLayout = false override init( style: UITableViewCell.CellStyle, @@ -41,6 +47,7 @@ final class CommentTableViewCell: UITableViewCell { setUI() setStyle() setLayout() + setAddTarget() } required init?(coder: NSCoder) { @@ -55,6 +62,10 @@ final class CommentTableViewCell: UITableViewCell { backgroundColor = .grayscale900 selectionStyle = .none + replyCommentArrow.do { + $0.image = .replyArrow + } + nicknameLabel.do { $0.applyByeBooFont(style: .body5M14, color: .grayscale200) } @@ -83,8 +94,6 @@ final class CommentTableViewCell: UITableViewCell { $0.applyByeBooFont(style: .body6R14, text: "더보기", color: .grayscale400) $0.backgroundColor = .grayscale900 $0.isUserInteractionEnabled = true - let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(moreLabelDidTap(_:))) - $0.addGestureRecognizer(tapRecognizer) } replyCountContainer.do { @@ -93,7 +102,9 @@ final class CommentTableViewCell: UITableViewCell { } replyCommentIcon.do { - $0.image = .comment + $0.image = .comment.withRenderingMode(.alwaysTemplate) + $0.isUserInteractionEnabled = true + $0.tintColor = .grayscale100 } replyCountLabel.do { @@ -102,11 +113,6 @@ final class CommentTableViewCell: UITableViewCell { } private func setLayout() { - profileIcon.snp.makeConstraints { - $0.top.equalToSuperview().inset(16.adjustedH) - $0.leading.equalToSuperview() - $0.size.equalTo(20.adjustedH) - } nicknameLabel.snp.makeConstraints { $0.top.equalToSuperview().inset(16.adjustedH) $0.leading.equalTo(profileIcon.snp.trailing).offset(4.adjustedW) @@ -120,29 +126,28 @@ final class CommentTableViewCell: UITableViewCell { $0.trailing.equalToSuperview() $0.size.equalTo(24.adjustedH) } - commentTextView.snp.makeConstraints { - $0.top.equalTo(profileIcon.snp.bottom).offset(4.adjustedH) - $0.leading.equalTo(profileIcon.snp.leading).offset(20) - $0.trailing.equalToSuperview().inset(24.adjustedW) - $0.bottom.equalToSuperview().inset(16.adjustedH) - } moreLabel.snp.makeConstraints { $0.bottom.equalTo(commentTextView.snp.bottom) $0.trailing.equalTo(commentTextView.snp.trailing).offset(-20.adjustedW) } } - - private func setReplyLayout() { - guard !didSetReplyLayout else { return } - didSetReplyLayout = true + private func setCommentListLayout() { + guard !didSetCommentLayout else { return } + didSetCommentLayout = true - contentView.addSubviews(replyCommentArrow, replyCountContainer) + contentView.addSubview(replyCountContainer) replyCountContainer.addArrangedSubviews(replyCommentIcon, replyCountLabel) - replyCommentArrow.snp.makeConstraints { + profileIcon.snp.makeConstraints { $0.top.equalToSuperview().inset(16.adjustedH) - $0.leading.equalToSuperview().inset(24.adjustedH) - $0.size.equalTo(24.adjustedH) + $0.leading.equalToSuperview() + $0.size.equalTo(20.adjustedH) + } + commentTextView.snp.makeConstraints { + $0.top.equalTo(profileIcon.snp.bottom).offset(4.adjustedH) + $0.leading.equalTo(profileIcon.snp.leading).offset(20) + $0.trailing.equalToSuperview().inset(24.adjustedW) + $0.bottom.equalToSuperview().inset(44.adjustedH) } replyCountContainer.snp.makeConstraints { $0.top.equalTo(commentTextView.snp.bottom).offset(8.adjustedH) @@ -153,6 +158,19 @@ final class CommentTableViewCell: UITableViewCell { replyCommentIcon.snp.makeConstraints { $0.size.equalTo(20.adjustedH) } + } + + private func setReplySheetLayout() { + guard !didSetReplyLayout else { return } + didSetReplyLayout = true + + contentView.addSubviews(replyCommentArrow) + + replyCommentArrow.snp.makeConstraints { + $0.top.equalToSuperview().inset(16.adjustedH) + $0.leading.equalToSuperview() + $0.size.equalTo(24.adjustedH) + } profileIcon.snp.makeConstraints { $0.top.equalToSuperview().inset(16.adjustedH) $0.leading.equalTo(replyCommentArrow.snp.trailing) @@ -162,8 +180,17 @@ final class CommentTableViewCell: UITableViewCell { $0.top.equalTo(profileIcon.snp.bottom).offset(4.adjustedH) $0.leading.equalTo(profileIcon.snp.leading).offset(20) $0.trailing.equalToSuperview().inset(24.adjustedW) + $0.bottom.equalToSuperview().inset(16.adjustedH) } } + + private func setAddTarget() { + let moreLabelTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(moreLabelDidTap(_:))) + moreLabel.addGestureRecognizer(moreLabelTapRecognizer) + + let replyTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(replyIconDidTap(_:))) + replyCommentIcon.addGestureRecognizer(replyTapRecognizer) + } } extension CommentTableViewCell { @@ -174,10 +201,18 @@ extension CommentTableViewCell { profileIcon: UIImage, writtenAt: String, content: String, - showAllText: Bool + showAllText: Bool, + isReplySheet: Bool ) { if let _ = replyCount { - setReplyLayout() + setCommentListLayout() + } else { + setReplySheetLayout() + } + + if isReplySheet { + replyCommentIcon.tintColor = .grayscale600 + replyCountLabel.textColor = .grayscale600 } self.commentID = commentID @@ -189,9 +224,10 @@ extension CommentTableViewCell { layoutIfNeeded() + commentTextView.textContainer.maximumNumberOfLines = 0 + commentTextView.textContainer.exclusionPaths = [] + if showAllText { - commentTextView.textContainer.maximumNumberOfLines = 0 - commentTextView.textContainer.exclusionPaths = [] commentTextView.invalidateIntrinsicContentSize() moreLabel.isHidden = true } else { @@ -235,4 +271,8 @@ extension CommentTableViewCell { private func moreLabelDidTap(_ tapRecognizer: UITapGestureRecognizer) { delegate?.moreLabelDidTap(commentID: commentID) } + + @objc private func replyIconDidTap(_ tapRecognizer: UITapGestureRecognizer) { + delegate?.replyIconDidTap(commentID: commentID) + } } From a3e1ea019c62b2124e0a80c415da926eba8bc8dc Mon Sep 17 00:00:00 2001 From: yeonee Date: Sun, 10 May 2026 21:54:55 +0900 Subject: [PATCH 24/31] =?UTF-8?q?style:=20#428=20=EB=8B=B5=EA=B8=80=20?= =?UTF-8?q?=EB=B0=94=ED=85=80=EC=8B=9C=ED=8A=B8=20UI=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuest/CommonQuestReplyView.swift | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestReplyView.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestReplyView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestReplyView.swift new file mode 100644 index 00000000..8a2c149a --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/CommonQuestReplyView.swift @@ -0,0 +1,76 @@ +// +// CommonQuestReplyView.swift +// ByeBoo-iOS +// +// Created by 이나연 on 5/8/26. +// + +import UIKit + +import SnapKit + +final class CommonQuestReplyView: BaseView { + private let headerView = UIView() + private(set) var backButton = UIButton() + private let headerLabel = UILabel() + private(set) var commentListView = UITableView() + private let commentTextView = CommentTextView() + + private var commentTextViewBottomConstraint: Constraint? + + override func setUI() { + addSubviews(headerView, commentListView, commentTextView) + headerView.addSubviews(backButton, headerLabel) + } + + override func setStyle() { + backgroundColor = .grayscale900 + + backButton.do { + $0.setImage(.left.withRenderingMode(.alwaysTemplate), for: .normal) + $0.tintColor = .white + } + + headerLabel.do { + $0.applyByeBooFont(style: .sub1Sb20, text: "답글", color: .white) + } + + commentListView.do { + $0.backgroundColor = .grayscale900 + $0.showsVerticalScrollIndicator = false + } + } + + override func setLayout() { + headerView.snp.makeConstraints { + $0.top.equalToSuperview().inset(26.adjustedH) + $0.horizontalEdges.equalToSuperview() + $0.height.equalTo(40.adjustedH) + } + backButton.snp.makeConstraints { + $0.top.equalToSuperview().inset(8.adjustedH) + $0.leading.equalToSuperview().inset(24.adjustedH) + $0.size.equalTo(24.adjustedH) + } + headerLabel.snp.makeConstraints { + $0.top.equalToSuperview().inset(8.adjustedH) + $0.centerX.equalToSuperview() + } + commentListView.snp.makeConstraints { + $0.top.equalTo(headerView.snp.bottom).offset(6.adjustedH) + $0.horizontalEdges.equalToSuperview().inset(20.adjustedW) + $0.bottom.equalTo(commentTextView.snp.top) + } + commentTextView.snp.makeConstraints { + $0.horizontalEdges.equalToSuperview() + commentTextViewBottomConstraint = $0.bottom.equalToSuperview().inset(34.adjustedH).constraint + } + } +} + +extension CommonQuestReplyView { + func updateLayout(keyboardHeight: CGFloat) { + let inset = keyboardHeight > 0 ? keyboardHeight : 34.adjustedH + commentTextViewBottomConstraint?.update(inset: inset) + } +} From 34610bf7a6abf1e15f4d881eb5278531341e732a Mon Sep 17 00:00:00 2001 From: yeonee Date: Sun, 10 May 2026 21:56:06 +0900 Subject: [PATCH 25/31] =?UTF-8?q?feat:=20#428=20=EB=8B=B5=EA=B8=80=20?= =?UTF-8?q?=EB=B7=B0=20=EB=AA=A8=EB=8D=B8=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewModel/CommonQuestReplyViewModel.swift | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestReplyViewModel.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestReplyViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestReplyViewModel.swift new file mode 100644 index 00000000..e0d5c0cd --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestReplyViewModel.swift @@ -0,0 +1,18 @@ +// +// CommonQuestReplyViewModel.swift +// ByeBoo-iOS +// +// Created by 이나연 on 5/8/26. +// + +import Foundation + +final class CommonQuestReplyViewModel { + func getCommentContent() -> CommonQuestCommentEntity { + return CommonQuestCommentEntity.toCommentStub() + } + + func getReplyList() -> [CommonQuestCommentEntity] { + return CommonQuestCommentEntity.toReplyListStub() + } +} From cd2ae9b51f863caff9809f6569ed7d41bd44049a Mon Sep 17 00:00:00 2001 From: yeonee Date: Sun, 10 May 2026 22:09:49 +0900 Subject: [PATCH 26/31] =?UTF-8?q?refactor:=20#428=20=ED=82=A4=EB=B3=B4?= =?UTF-8?q?=EB=93=9C=20=EB=A1=9C=EC=A7=81=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuestHistoryViewController.swift | 77 ++++++------------- .../Protocol/KeyboardHandleProtocol.swift | 41 ++++++++++ 2 files changed, 66 insertions(+), 52 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Protocol/KeyboardHandleProtocol.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift index f493cc70..a1c9578e 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -9,15 +9,6 @@ import UIKit import Mixpanel -enum CommentSection: Hashable { - case main -} - -struct CommentItem: Hashable { - let entity: CommonQuestCommentEntity - var showAllText: Bool -} - final class CommonQuestHistoryViewController: BaseViewController { private var dataSource: UITableViewDiffableDataSource! @@ -44,11 +35,6 @@ final class CommonQuestHistoryViewController: BaseViewController { Mixpanel.mainInstance().track(event: CommonJourneyEvents.Name.commonJourneyOthersAnswerPageview) } - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - removeKeyboardObservers() - } - override func viewDidLoad() { super.viewDidLoad() @@ -64,6 +50,11 @@ final class CommonQuestHistoryViewController: BaseViewController { applySnapshot() } + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + removeKeyboardObservers() + } + override func setAddTarget() { let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard)) tapGestureRecognizer.cancelsTouchesInView = false @@ -72,7 +63,6 @@ final class CommonQuestHistoryViewController: BaseViewController { override func setDelegate() { rootView.commentListView.do { - $0.delegate = self $0.register(CommentTableViewCell.self) } } @@ -85,10 +75,6 @@ extension CommonQuestHistoryViewController: BackNavigable { } } -extension CommonQuestHistoryViewController: UITableViewDelegate { - -} - extension CommonQuestHistoryViewController { private func configureDataSource() { dataSource = UITableViewDiffableDataSource( @@ -107,7 +93,8 @@ extension CommonQuestHistoryViewController { profileIcon: ProfileIcon.image(for: entity.profileIcon) ?? .relievedBadge, writtenAt: entity.writtenAt, content: entity.content, - showAllText: item.showAllText + showAllText: item.showAllText, + isReplySheet: false ) cell.delegate = self return cell @@ -191,7 +178,18 @@ extension CommonQuestHistoryViewController: CommentProtocol { self?.rootView.commentListView.performBatchUpdates(nil) } } + + func replyIconDidTap(commentID: Int) { + let viewController = CommonQuestReplyViewController() + if let sheet = viewController.sheetPresentationController{ + sheet.detents = [.large()] + sheet.prefersGrabberVisible = true + sheet.prefersScrollingExpandsWhenScrolledToEdge = false + } + self.present(viewController, animated: true) + } } + extension CommonQuestHistoryViewController { func configure( @@ -247,42 +245,17 @@ extension CommonQuestHistoryViewController { private func dismissKeyboard() { view.endEditing(true) } - - private func addKeyboardObservers() { - NotificationCenter.default.addObserver( - self, - selector: #selector(keyboardWillShow), - name: UIResponder.keyboardWillShowNotification, - object: nil - ) - NotificationCenter.default.addObserver( - self, - selector: #selector(keyboardWillHide), - name: UIResponder.keyboardWillHideNotification, - object: nil - ) - } - - private func removeKeyboardObservers() { - NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil) - NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil) - } - - @objc private func keyboardWillShow(_ notification: Notification) { - guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect, - let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double - else { return } - +} + +extension CommonQuestHistoryViewController: KeyboardHandleProtocol { + func keyboardWillShow(height: CGFloat, duration: Double) { UIView.animate(withDuration: duration) { - self.rootView.updateLayout(keyboardHeight: keyboardFrame.height) + self.rootView.updateLayout(keyboardHeight: height) self.rootView.layoutIfNeeded() } } - - @objc private func keyboardWillHide(_ notification: Notification) { - guard let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double - else { return } - + + func keyboardWillHide(duration: Double) { UIView.animate(withDuration: duration) { self.rootView.updateLayout(keyboardHeight: 0) self.rootView.layoutIfNeeded() diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Protocol/KeyboardHandleProtocol.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Protocol/KeyboardHandleProtocol.swift new file mode 100644 index 00000000..07fcee60 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Protocol/KeyboardHandleProtocol.swift @@ -0,0 +1,41 @@ +// +// KeyboardHandlable+.swift +// ByeBoo-iOS +// + +import UIKit + +protocol KeyboardHandleProtocol: UIViewController { + func keyboardWillShow(height: CGFloat, duration: Double) + func keyboardWillHide(duration: Double) +} + +extension KeyboardHandleProtocol { + func addKeyboardObservers() { + NotificationCenter.default.addObserver( + forName: UIResponder.keyboardWillShowNotification, + object: nil, + queue: .main + ) { [weak self] notification in + guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect, + let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double + else { return } + self?.keyboardWillShow(height: keyboardFrame.height, duration: duration) + } + + NotificationCenter.default.addObserver( + forName: UIResponder.keyboardWillHideNotification, + object: nil, + queue: .main + ) { [weak self] notification in + guard let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double + else { return } + self?.keyboardWillHide(duration: duration) + } + } + + func removeKeyboardObservers() { + NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil) + NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil) + } +} From d2f185f91d2337ca1890ac9cf09cabf20f668076 Mon Sep 17 00:00:00 2001 From: yeonee Date: Sun, 10 May 2026 22:10:16 +0900 Subject: [PATCH 27/31] =?UTF-8?q?feat:=20#428=20=EB=8B=B5=EA=B8=80=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/CommentDataSource.swift | 22 +++ .../CommonQuestReplyViewController.swift | 133 ++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentDataSource.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentDataSource.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentDataSource.swift new file mode 100644 index 00000000..3b32e980 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentDataSource.swift @@ -0,0 +1,22 @@ +// +// CommentDataSource.swift +// ByeBoo-iOS +// +// Created by 이나연 on 5/8/26. +// + +import Foundation + +enum CommentSection: Hashable { + case main +} + +enum ReplySection: Hashable { + case comment + case replies +} + +struct CommentItem: Hashable { + let entity: CommonQuestCommentEntity + var showAllText: Bool +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift new file mode 100644 index 00000000..b968e122 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift @@ -0,0 +1,133 @@ +// +// CommonQuestReplyViewController.swift +// ByeBoo-iOS +// +// Created by 이나연 on 5/8/26. +// + +import UIKit + +final class CommonQuestReplyViewController: BaseViewController { + + private let rootView = CommonQuestReplyView() + private let viewModel = CommonQuestReplyViewModel() + + private var dataSource: UITableViewDiffableDataSource! + + override func loadView() { + view = rootView + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.tabBarController?.tabBar.isHidden = true + addKeyboardObservers() + } + + override func viewDidLoad() { + super.viewDidLoad() + + configureDataSource() + applySnapshot() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + removeKeyboardObservers() + } + + override func setAddTarget() { + rootView.backButton.addTarget(self, action: #selector(backButtonDidTap), for: .touchUpInside) + } + + override func setDelegate() { + rootView.commentListView.do { + $0.register(CommentTableViewCell.self, forCellReuseIdentifier: "comment") + $0.register(CommentTableViewCell.self, forCellReuseIdentifier: "reply") + } + } +} + +extension CommonQuestReplyViewController { + private func configureDataSource() { + dataSource = UITableViewDiffableDataSource( + tableView: rootView.commentListView + ) { tableView, indexPath, item in + let identifier = item.entity.replyCount != nil ? "comment" : "reply" + guard let cell = tableView.dequeueReusableCell( + withIdentifier: identifier, + for: indexPath + ) as? CommentTableViewCell else { return UITableViewCell() } + + let entity = item.entity + cell.configure( + commentID: entity.commentID, + replyCount: entity.replyCount, + writer: entity.writer, + profileIcon: ProfileIcon.image(for: entity.profileIcon) ?? .relievedBadge, + writtenAt: entity.writtenAt, + content: entity.content, + showAllText: item.showAllText, + isReplySheet: true + ) + cell.delegate = self + return cell + } + } + + private func applySnapshot() { + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.comment, .replies]) + let commentItem = CommentItem(entity: viewModel.getCommentContent(), showAllText: false) + snapshot.appendItems([commentItem], toSection: .comment) + + let replyItems = viewModel.getReplyList().map { CommentItem(entity: $0, showAllText: false) } + snapshot.appendItems(replyItems, toSection: .replies) + + dataSource.apply(snapshot, animatingDifferences: false) + } + + @objc + private func backButtonDidTap() { + self.dismiss(animated: true) + } +} + +extension CommonQuestReplyViewController: CommentProtocol { + func moreLabelDidTap(commentID: Int) { + let currentSnapshot = dataSource.snapshot() + + let commentItems = currentSnapshot.itemIdentifiers(inSection: .comment).map { item -> CommentItem in + guard item.entity.commentID == commentID else { return item } + return CommentItem(entity: item.entity, showAllText: true) + } + let replyItems = currentSnapshot.itemIdentifiers(inSection: .replies).map { item -> CommentItem in + guard item.entity.commentID == commentID else { return item } + return CommentItem(entity: item.entity, showAllText: true) + } + + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.comment, .replies]) + snapshot.appendItems(commentItems, toSection: .comment) + snapshot.appendItems(replyItems, toSection: .replies) + dataSource.apply(snapshot, animatingDifferences: false) { [weak self] in + self?.rootView.commentListView.performBatchUpdates(nil) + } + } +} + +extension CommonQuestReplyViewController: KeyboardHandleProtocol { + func keyboardWillShow(height: CGFloat, duration: Double) { + UIView.animate(withDuration: duration) { + self.rootView.updateLayout(keyboardHeight: height) + self.rootView.layoutIfNeeded() + } + } + + func keyboardWillHide(duration: Double) { + UIView.animate(withDuration: duration) { + self.rootView.updateLayout(keyboardHeight: 0) + self.rootView.layoutIfNeeded() + } + } +} From ee5f919a5dfaa45f9650ef4e898aa354ee21f7c5 Mon Sep 17 00:00:00 2001 From: yeonee Date: Sun, 10 May 2026 22:30:26 +0900 Subject: [PATCH 28/31] =?UTF-8?q?style:=20#428=20separator=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Quest/ViewController/CommonQuestHistoryViewController.swift | 1 + .../Quest/ViewController/CommonQuestReplyViewController.swift | 1 + 2 files changed, 2 insertions(+) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift index a1c9578e..ba8a7828 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -63,6 +63,7 @@ final class CommonQuestHistoryViewController: BaseViewController { override func setDelegate() { rootView.commentListView.do { + $0.separatorStyle = .none $0.register(CommentTableViewCell.self) } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift index b968e122..620c2f03 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift @@ -42,6 +42,7 @@ final class CommonQuestReplyViewController: BaseViewController { override func setDelegate() { rootView.commentListView.do { + $0.separatorStyle = .none $0.register(CommentTableViewCell.self, forCellReuseIdentifier: "comment") $0.register(CommentTableViewCell.self, forCellReuseIdentifier: "reply") } From fdca78ced6024be668cabd1a33fab9ac736f6755 Mon Sep 17 00:00:00 2001 From: yeonee Date: Sun, 10 May 2026 23:07:25 +0900 Subject: [PATCH 29/31] =?UTF-8?q?fix:=20#428=20stub=20id=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift index 035331af..16c286e6 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/CommonQuestCommentEntity.swift @@ -45,7 +45,7 @@ extension CommonQuestCommentEntity { } static func toReplyListStub() -> [Self] { - return (1...5).map { + return (6...10).map { .init( commentID: $0, replyCount: nil, From d8fa92015a2fa5f70296229c8548162cdead8aab Mon Sep 17 00:00:00 2001 From: yeonee Date: Sun, 10 May 2026 23:21:55 +0900 Subject: [PATCH 30/31] =?UTF-8?q?refactor:=20#428=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EB=B6=84=EA=B8=B0=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CommonQuest/Common/CommentTextFieldView.swift | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextFieldView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextFieldView.swift index db3fef89..dab606a8 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextFieldView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/CommentTextFieldView.swift @@ -124,16 +124,10 @@ extension CommentTextView: UITextViewDelegate { let maxHeight = 105.adjustedH let fittingHeight = textView.sizeThatFits(CGSize(width: textView.bounds.width, height: .infinity)).height - if fittingHeight > maxHeight { - if !textView.isScrollEnabled { - textView.isScrollEnabled = true - } - } else { - if textView.isScrollEnabled { - textView.isScrollEnabled = false - textView.contentOffset = .zero - } - } + + let shouldScroll = fittingHeight > maxHeight + textView.isScrollEnabled = shouldScroll + if !shouldScroll { textView.contentOffset = .zero } } func textViewDidEndEditing(_ textView: UITextView) { From 35423f37395a725e014628bc334dbd54ced323cb Mon Sep 17 00:00:00 2001 From: yeonee Date: Tue, 19 May 2026 18:01:29 +0900 Subject: [PATCH 31/31] =?UTF-8?q?refactor:=20#428=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Cells/CommentTableViewCell.swift | 2 +- .../CommonQuest/Common/QuestContentView.swift | 20 ++++--------------- .../CommonQuestHistoryViewController.swift | 9 +-------- .../CommonQuestReplyViewController.swift | 9 +-------- .../Protocol/KeyboardHandleProtocol.swift | 15 +++++++++----- 5 files changed, 17 insertions(+), 38 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift index eed1bea4..8704e6e0 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Cells/CommentTableViewCell.swift @@ -204,7 +204,7 @@ extension CommentTableViewCell { showAllText: Bool, isReplySheet: Bool ) { - if let _ = replyCount { + if replyCount != nil { setCommentListLayout() } else { setReplySheetLayout() diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift index 67a40bb9..c405fcc8 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/View/CommonQuest/Common/QuestContentView.swift @@ -11,7 +11,7 @@ protocol CommonQuestLikeCommentProtocol: AnyObject { func likeButtonDidTap() } -final class QuestContentView: UIView { +final class QuestContentView: BaseView { private let answerContentTextView = UITextView() private let writtenDateLabel = UILabel() @@ -28,19 +28,7 @@ final class QuestContentView: UIView { private var likeCounts: Int = 0 - init() { - super.init(frame: .zero) - - setUI() - setStyle() - setLayout() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func setUI() { + override func setUI() { self.addSubviews( answerContentTextView, writtenDateLabel, @@ -51,7 +39,7 @@ final class QuestContentView: UIView { commentContainerView.addArrangedSubviews(commentIcon, commentCountLabel) } - private func setStyle() { + override func setStyle() { answerContentTextView.do { $0.applyByeBooFont(style: .body3R16, color: .grayscale100) $0.isScrollEnabled = false @@ -91,7 +79,7 @@ final class QuestContentView: UIView { } } - private func setLayout() { + override func setLayout() { answerContentTextView.snp.makeConstraints { $0.top.equalToSuperview() $0.horizontalEdges.equalToSuperview() diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift index ba8a7828..0abf8f73 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestHistoryViewController.swift @@ -249,17 +249,10 @@ extension CommonQuestHistoryViewController { } extension CommonQuestHistoryViewController: KeyboardHandleProtocol { - func keyboardWillShow(height: CGFloat, duration: Double) { + func keyboardWillShowOrHide(height: CGFloat, duration: Double) { UIView.animate(withDuration: duration) { self.rootView.updateLayout(keyboardHeight: height) self.rootView.layoutIfNeeded() } } - - func keyboardWillHide(duration: Double) { - UIView.animate(withDuration: duration) { - self.rootView.updateLayout(keyboardHeight: 0) - self.rootView.layoutIfNeeded() - } - } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift index 620c2f03..5c492016 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewController/CommonQuestReplyViewController.swift @@ -118,17 +118,10 @@ extension CommonQuestReplyViewController: CommentProtocol { } extension CommonQuestReplyViewController: KeyboardHandleProtocol { - func keyboardWillShow(height: CGFloat, duration: Double) { + func keyboardWillShowOrHide(height: CGFloat, duration: Double) { UIView.animate(withDuration: duration) { self.rootView.updateLayout(keyboardHeight: height) self.rootView.layoutIfNeeded() } } - - func keyboardWillHide(duration: Double) { - UIView.animate(withDuration: duration) { - self.rootView.updateLayout(keyboardHeight: 0) - self.rootView.layoutIfNeeded() - } - } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Protocol/KeyboardHandleProtocol.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Protocol/KeyboardHandleProtocol.swift index 07fcee60..3d98320b 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Protocol/KeyboardHandleProtocol.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Protocol/KeyboardHandleProtocol.swift @@ -5,9 +5,14 @@ import UIKit -protocol KeyboardHandleProtocol: UIViewController { - func keyboardWillShow(height: CGFloat, duration: Double) - func keyboardWillHide(duration: Double) +protocol KeyboardHandleProtocol where Self: UIViewController { + func keyboardWillShowOrHide(height: CGFloat, duration: Double) +} + +extension KeyboardHandleProtocol { + func keyboardWillShowOrHide(duration: Double) { + keyboardWillShowOrHide(height: 0, duration: duration) + } } extension KeyboardHandleProtocol { @@ -20,7 +25,7 @@ extension KeyboardHandleProtocol { guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect, let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else { return } - self?.keyboardWillShow(height: keyboardFrame.height, duration: duration) + self?.keyboardWillShowOrHide(height: keyboardFrame.height, duration: duration) } NotificationCenter.default.addObserver( @@ -30,7 +35,7 @@ extension KeyboardHandleProtocol { ) { [weak self] notification in guard let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else { return } - self?.keyboardWillHide(duration: duration) + self?.keyboardWillShowOrHide(duration: duration) } }