diff --git a/HealthFoodMe/HealthFoodMe.xcodeproj/project.pbxproj b/HealthFoodMe/HealthFoodMe.xcodeproj/project.pbxproj index 9fdc9945..3a825722 100644 --- a/HealthFoodMe/HealthFoodMe.xcodeproj/project.pbxproj +++ b/HealthFoodMe/HealthFoodMe.xcodeproj/project.pbxproj @@ -240,6 +240,7 @@ 695758E42881852E00E36789 /* CopingEmptyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopingEmptyView.swift; sourceTree = ""; }; 695758EB28830E3000E36789 /* SplashVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashVC.swift; sourceTree = ""; }; 695758ED28830E3F00E36789 /* Splash.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Splash.storyboard; sourceTree = ""; }; + 6E596B9EBEC8B18D4DE266B6 /* Pods-HealthFoodMe.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HealthFoodMe.release.xcconfig"; path = "Target Support Files/Pods-HealthFoodMe/Pods-HealthFoodMe.release.xcconfig"; sourceTree = ""; }; A9325273287D3065001EDF50 /* SearchResultTVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultTVC.swift; sourceTree = ""; }; A9325276287D4F10001EDF50 /* SearchResultModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultModel.swift; sourceTree = ""; }; A932527D287DD261001EDF50 /* SearchResultVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultVC.swift; sourceTree = ""; }; @@ -423,6 +424,33 @@ path = HamburgerBarScene; sourceTree = ""; }; + 3B3DCF472882B534009256BD /* Common */ = { + isa = PBXGroup; + children = ( + 3BDE7F282882054200EE7F47 /* ReviewEmptyViewCVC.swift */, + 3BC01F88287F1DFA006C2181 /* ReviewHeaderCVC.swift */, + ); + path = Common; + sourceTree = ""; + }; + 3B3DCF482882B540009256BD /* BlogReview */ = { + isa = PBXGroup; + children = ( + 3BC01F83287EAE48006C2181 /* BlogReviewCVC.swift */, + ); + path = BlogReview; + sourceTree = ""; + }; + 3B3DCF492882B55D009256BD /* Review */ = { + isa = PBXGroup; + children = ( + 3B723C902880037100822B7C /* ReviewPhotoCVC.swift */, + 3BC01F7A287E89D2006C2181 /* ReviewCVC.swift */, + 3B723C8A287FF81800822B7C /* TagCVC.swift */, + ); + path = Review; + sourceTree = ""; + }; 3B723CA72881D43B00822B7C /* VC */ = { isa = PBXGroup; children = ( @@ -435,12 +463,9 @@ 3BC01F79287E8917006C2181 /* Cells */ = { isa = PBXGroup; children = ( - 3BC01F83287EAE48006C2181 /* BlogReviewCVC.swift */, - 3BC01F88287F1DFA006C2181 /* ReviewHeaderCVC.swift */, - 3BC01F7A287E89D2006C2181 /* ReviewCVC.swift */, - 3B723C8A287FF81800822B7C /* TagCVC.swift */, - 3B723C902880037100822B7C /* ReviewPhotoCVC.swift */, - 3BDE7F282882054200EE7F47 /* ReviewEmptyViewCVC.swift */, + 3B3DCF492882B55D009256BD /* Review */, + 3B3DCF482882B540009256BD /* BlogReview */, + 3B3DCF472882B534009256BD /* Common */, ); path = Cells; sourceTree = ""; diff --git a/HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/Cells/BlogReviewCVC.swift b/HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/Cells/BlogReview/BlogReviewCVC.swift similarity index 100% rename from HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/Cells/BlogReviewCVC.swift rename to HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/Cells/BlogReview/BlogReviewCVC.swift diff --git a/HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/Cells/ReviewEmptyViewCVC.swift b/HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/Cells/Common/ReviewEmptyViewCVC.swift similarity index 100% rename from HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/Cells/ReviewEmptyViewCVC.swift rename to HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/Cells/Common/ReviewEmptyViewCVC.swift diff --git a/HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/Cells/ReviewHeaderCVC.swift b/HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/Cells/Common/ReviewHeaderCVC.swift similarity index 100% rename from HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/Cells/ReviewHeaderCVC.swift rename to HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/Cells/Common/ReviewHeaderCVC.swift diff --git a/HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/Cells/ReviewCVC.swift b/HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/Cells/Review/ReviewCVC.swift similarity index 83% rename from HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/Cells/ReviewCVC.swift rename to HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/Cells/Review/ReviewCVC.swift index c1edac30..e3aef419 100644 --- a/HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/Cells/ReviewCVC.swift +++ b/HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/Cells/Review/ReviewCVC.swift @@ -20,10 +20,11 @@ class ReviewCVC: UICollectionViewCell, UICollectionViewRegisterable { } } - var setEnumValue = 0 + var layoutEnumValue = 0 let width = UIScreen.main.bounds.width - + var clickedEvent: ((Int) -> Void)? + var isFolded: Bool = true // MARK: - UI Components private var nameLabel: UILabel = { @@ -83,6 +84,16 @@ class ReviewCVC: UICollectionViewCell, UICollectionViewRegisterable { return view }() + lazy var moreTapButton: UIButton = { + let button = UIButton() + button.backgroundColor = .clear + button.press { + guard let index = self.getIndexPath() else { return } + self.clickedEvent?(index) + } + return button + }() + // MARK: - Life Cycle Part override init(frame: CGRect) { @@ -121,7 +132,7 @@ extension ReviewCVC { func setDefaultLayout() { contentView.addSubviews(nameLabel, starView, tagCV, - reviewPhotoCV, reviewContents, reviewSeperatorView) + reviewPhotoCV, reviewContents, reviewSeperatorView,moreTapButton) let width = UIScreen.main.bounds.width @@ -166,10 +177,19 @@ extension ReviewCVC { make.bottom.equalToSuperview().offset(-28) make.width.equalTo(width - 40) } + + moreTapButton.snp.makeConstraints { make in + make.bottom.equalToSuperview().offset(-28) + make.trailing.equalToSuperview().offset(-30) + make.width.equalTo(width - 40) + make.height.equalToSuperview() + } + + } func setLayout() { - switch setEnumValue { + switch layoutEnumValue { case 0: setLayoutWithImageAndContents() case 1: @@ -253,15 +273,59 @@ extension ReviewCVC { } } - func setData(reviewData: ReviewDataModel) { + func setData(reviewData: ReviewDataModel, + text: String, + isFoldRequired: Bool, + expanded: Bool) { nameLabel.text = reviewData.reviewer nameLabel.sizeToFit() starView.rate = CGFloat(reviewData.starLate) self.cellViewModel = reviewData - reviewContents.text = reviewData.reviewContents + reviewContents.text = text reviewContents.sizeToFit() + + if isFoldRequired { + moreTapButton.isHidden = false + } else { + moreTapButton.isHidden = true + } + + if !expanded { + setPartContentsAttributes() + } else { + let attributedString = NSMutableAttributedString(string: text) + reviewContents.attributedText = attributedString + } + self.contentView.layoutIfNeeded() } + + private func getIndexPath() -> Int? { + guard let superView = self.superview as? UICollectionView else { + return nil + } + let indexPath = superView.indexPath(for: self) + return indexPath?.row + } + + func setPartContentsAttributes() { + var textCount = 0 + var length = 0 + if reviewContents.text?.count ?? 0 < 3 { + textCount = 3 + length = reviewContents.text?.count ?? 0 + } else { + textCount = reviewContents.text?.count ?? 0 + length = 3 + } + let fullText = reviewContents.text + let range = NSRange(location: textCount - 3, length: length) + + let attributedString = NSMutableAttributedString(string: fullText ?? "") + attributedString.addAttribute(.font, value: UIFont.NotoRegular(size: 12), range: range) + attributedString.addAttribute(.foregroundColor, value: UIColor.helfmeGray2, range: range) + reviewContents.attributedText = attributedString + } } extension ReviewCVC: UICollectionViewDelegate { diff --git a/HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/Cells/ReviewPhotoCVC.swift b/HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/Cells/Review/ReviewPhotoCVC.swift similarity index 100% rename from HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/Cells/ReviewPhotoCVC.swift rename to HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/Cells/Review/ReviewPhotoCVC.swift diff --git a/HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/Cells/TagCVC.swift b/HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/Cells/Review/TagCVC.swift similarity index 100% rename from HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/Cells/TagCVC.swift rename to HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/Cells/Review/TagCVC.swift diff --git a/HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/VC/ReviewDetailVC.swift b/HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/VC/ReviewDetailVC.swift index ecc4f930..bf29e123 100644 --- a/HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/VC/ReviewDetailVC.swift +++ b/HealthFoodMe/HealthFoodMe/Presentation/Detail/ReviewTabScene/VC/ReviewDetailVC.swift @@ -7,6 +7,13 @@ import UIKit +enum ReviewDetailCellLayoutType: Int { + case withImageAndContents = 1 + case withImage = 2 + case withContents = 3 + case withoutImageAndContents = 4 +} + class ReviewDetailVC: UIViewController { private let withImageAndContents = 0 @@ -17,6 +24,14 @@ class ReviewDetailVC: UIViewController { private var reviewData: [ReviewCellViewModel] = [] weak var delegate: ScrollDeliveryDelegate? var topScrollAnimationNotFinished: Bool = true + private var reviewData: [ReviewCellViewModel] = [] { didSet { + fetchCutStringList() + fetchExpendStateList() + }} + private var blogReviewData: [BlogReviewDataModel] = [] + private var cutLabelList: [String] = [] + private var expendStateList: [Bool] = [] + var moreContentsButtonRect: CGRect = CGRect(x: 0, y: 0, width: 0, height: 0) var selectedCustomSegment = 0 { didSet { @@ -42,6 +57,7 @@ class ReviewDetailVC: UIViewController { setLayout() setDelegate() registerCell() + fetchData() } } @@ -87,20 +103,87 @@ extension ReviewDetailVC { } } + private func fetchCutStringList() { + for viewModel in reviewData { + if let reviewText = viewModel.data.reviewContents { + let cutText = cutReviewContents(reviewText) + cutLabelList.append(cutText) + } else { + cutLabelList.append("") + } + } + } + + private func fetchExpendStateList() { + expendStateList = Array(repeating: false, count: reviewData.count) + } + private func fetchData() { // 데이터를 서버에서 받아와야 함 - let data = ReviewDataModel.sampleData // 서버에서 받아와야 할 데이터 - processViewModel(data) + let reviewData = ReviewDataModel.sampleData // 서버에서 받아와야 할 데이터 + let blogReviewData = BlogReviewDataModel.sampleData + processViewModel(reviewData, blogReviewData) } - private func processViewModel(_ dataList: [ReviewDataModel]) { - var result: [ReviewCellViewModel] = [] - for data in dataList { - let height = calculateReviewHeight(data.reviewContents ?? "") - result.append(ReviewCellViewModel.init(data: data, + private func processViewModel(_ reviewDataList: [ReviewDataModel], + _ blogReviewDataList: [BlogReviewDataModel]) { + var reviewResult: [ReviewCellViewModel] = [] + var blogReviewResult: [BlogReviewDataModel] = [] + for reviewData in reviewDataList { + let height = calculateReviewHeight(reviewData.reviewContents ?? "") + reviewResult.append(ReviewCellViewModel.init(data: reviewData, foldRequired: height > 55)) } - self.reviewData = result + + for blogReviewData in blogReviewDataList { + blogReviewResult.append( + BlogReviewDataModel.init(blogReviewTitle: blogReviewData.blogReviewTitle, + blogReviewContents: blogReviewData.blogReviewContents)) + } + + self.reviewData = reviewResult + self.blogReviewData = blogReviewResult + } + + private func calculateTextInSize(review: String) -> (Int,String) { + var calculatedText: String = "" + var previousHeight: CGFloat = 0 + var lineCount: Int = 0 + + for char in review { + calculatedText += String(char) + if (previousHeight != calculateReviewHeight(calculatedText)) { + previousHeight = calculateReviewHeight(calculatedText) + lineCount += 1 + } + if lineCount == 4 { + return (4,calculatedText) + } + } + return (lineCount,calculatedText) + } + + private func cutReviewContents(_ reviewDataContents: String) -> String { + var eraseCount: Int = 0 + + var (lineCount,cutText) = calculateTextInSize(review: reviewDataContents) + if lineCount > 3 { + + for char in cutText { + eraseCount += 1 + cutText.popLast() + if eraseCount > 7 { + cutText.append(" 더보기") + break + } else { + if char == " " { + continue + } + } + } + } + + return cutText } private func calculateReviewHeight(_ text: String) -> CGFloat { @@ -163,16 +246,16 @@ extension ReviewDetailVC: UICollectionViewDataSource { return 1 case 1: if selectedCustomSegment == 0 { - if ReviewDataModel.sampleData.count == 0 { + if reviewData.count == 0 { return 1 } else { - return ReviewDataModel.sampleData.count + return reviewData.count } } else { - if BlogReviewDataModel.sampleData.count == 0 { + if blogReviewData.count == 0 { return 1 } else { - return BlogReviewDataModel.sampleData.count + return blogReviewData.count } } default: @@ -189,26 +272,47 @@ extension ReviewDetailVC: UICollectionViewDataSource { return header case 1: if selectedCustomSegment == 0 { - if ReviewDataModel.sampleData.count == 0 { + if reviewData.count == 0 { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ReviewEmptyViewCVC.className, for: indexPath) as? ReviewEmptyViewCVC else { return UICollectionViewCell() } return cell } else { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ReviewCVC.className, for: indexPath) as? ReviewCVC else { return UICollectionViewCell() } cell.reviewSeperatorView.isHidden = indexPath.item == 0 - cell.setData(reviewData: ReviewDataModel.sampleData[indexPath.row]) - cell.setEnumValue = setEnumValue(data: ReviewDataModel.sampleData[indexPath.row]) + cell.clickedEvent = { clickedIndex in + self.expendStateList[clickedIndex].toggle() + self.reviewCV.reloadData() + } + + let isFoldRequired = reviewData[indexPath.row].foldRequired + if isFoldRequired { + let originalText = reviewData[indexPath.row].data.reviewContents + let cutText = cutLabelList[indexPath.row] + let reviewText = expendStateList[indexPath.row] ? originalText : cutText + cell.setData(reviewData: reviewData[indexPath.row].data, + text: reviewText ?? "", + isFoldRequired: true, + expanded: expendStateList[indexPath.row]) + + } else { + cell.setData(reviewData: reviewData[indexPath.row].data, + text: reviewData[indexPath.row].data.reviewContents ?? "", + isFoldRequired: false, expanded: false) + } + + // 레이아웃 분기처리 코드 + cell.layoutEnumValue = setEnumValue(data: reviewData[indexPath.row].data) cell.setLayout() return cell } } else { - if BlogReviewDataModel.sampleData.count == 0 { + if blogReviewData.count == 0 { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ReviewEmptyViewCVC.className, for: indexPath) as? ReviewEmptyViewCVC else { return UICollectionViewCell() } return cell } else { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: BlogReviewCVC.className, for: indexPath) as? BlogReviewCVC else { return UICollectionViewCell() } cell.reviewSeperatorView.isHidden = indexPath.item == 0 - cell.setData(blogReviewData: BlogReviewDataModel.sampleData[indexPath.row]) + cell.setData(blogReviewData: blogReviewData[indexPath.row]) return cell } } @@ -232,12 +336,14 @@ extension ReviewDetailVC: UICollectionViewDelegateFlowLayout { return CGSize(width: cellWidth, height: cellHeight) } else { let cellWidth = width - let cellHeight = calculateReviewCellHeight(containsPhoto: ReviewDataModel.sampleData[indexPath.row].reviewImageURLList?.count != 0, - reviewText: ReviewDataModel.sampleData[indexPath.row].reviewContents) + let cellHeight = calculateReviewCellHeight(containsPhoto: reviewData[indexPath.row].data.reviewImageURLList?.count != 0, + reviewText: reviewData[indexPath.row].data.reviewContents, + isExpandState: expendStateList[indexPath.row]) + return CGSize(width: cellWidth, height: cellHeight) } } else { - if BlogReviewDataModel.sampleData.count == 0 { + if blogReviewData.count == 0 { let cellWidth = width let cellHeight = width * 200/width return CGSize(width: cellWidth, height: cellHeight) @@ -252,7 +358,7 @@ extension ReviewDetailVC: UICollectionViewDelegateFlowLayout { } } - private func calculateReviewCellHeight(containsPhoto: Bool, reviewText: String? ) -> CGFloat { + private func calculateReviewCellHeight(containsPhoto: Bool, reviewText: String?,isExpandState: Bool) -> CGFloat { var cellHeight: CGFloat = 0 let topPadding: CGFloat = 28 let nameLabelHeight: CGFloat = 20 @@ -276,7 +382,11 @@ extension ReviewDetailVC: UICollectionViewDelegateFlowLayout { let textViewHeight = calculateReviewHeight(reviewText ?? "") if textViewHeight >= threeLineHeight { - cellHeight += (threeLineHeight + bottomPadding) + if !isExpandState { + cellHeight += (threeLineHeight + bottomPadding) + } else { + cellHeight += (textViewHeight + bottomPadding) + } } else { cellHeight += (textViewHeight + bottomPadding) }