Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] #300 - DetailSearchView 상세탐색 기능 2차 구현 #302

Open
wants to merge 35 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
0b76a5e
[Network] #294 - 상세탐색 API 기본 세팅
Guryss Oct 22, 2024
a23d55a
[Fix] #294 - NormalSearchNovel -> SearchNovel 네이밍 변경으로 인한 코드 수정
Guryss Oct 22, 2024
ecb60d6
[Fix] #294 - DetailSearchInfoView 내 컬렉션뷰 UI 레이아웃 수정
Guryss Oct 22, 2024
2fa09b1
[Chore] #294 - NormalSearchNovel -> SearchNovel 네이밍 변경으로 인한 코드 수정
Guryss Oct 22, 2024
2a6e4b9
[Feat] #294 - 정보뷰 내 장르 cell 다중선택 구현 및 API 쿼리 연결
Guryss Oct 22, 2024
3f31f36
[Feat] #294 - CompletedStatus로 연재상태 관리 및 커스텀 버튼 클래스 생성
Guryss Oct 23, 2024
b6024ee
[Fix] #294 - isCompleted 매개변수 옵셔널로 수정
Guryss Oct 23, 2024
b5e9664
[Feat] #294 - 연재상태 버튼 기능 구현 및 api 연결
Guryss Oct 23, 2024
c5d7a93
[Feat] #294 - NovelRatingStatus 열거형으로 버튼 관리 및 커스텀 버튼 클래스 생성
Guryss Oct 23, 2024
ffb7e68
[Fix] #294 - api 연결 함수 내 novelRating 옵셔널 처리
Guryss Oct 23, 2024
73b713a
[Feat] #294 - NovelRating 버튼 기능 구현 및 api 연결
Guryss Oct 23, 2024
79b117d
[Design] #294 - 상세탐색 결과뷰 내 headerView UI 구현
Guryss Oct 23, 2024
72da7a1
[Design] #294 - 상세탐색 결과뷰 내 CollectionView UI 구현
Guryss Oct 23, 2024
731d630
[Feat] #294 - 상세탐색 결과뷰 내 컬렉션뷰 UI 구현 및 바인딩
Guryss Oct 23, 2024
67063ef
[Feat] #294 - NotificationCenter로 결과버튼 클릭 시 결과뷰 나타날 수 있도록 구현
Guryss Oct 23, 2024
ff6844b
[Feat] #294 - 검색된 작품 수 바인딩 구현
Guryss Oct 23, 2024
f51c7b5
[Chore] #300 - DetailSearchVC 내 코드 정리
Guryss Oct 26, 2024
ba22eda
[Fix] #300 - 메인 브랜치 업데이트하면서 누락된 뷰모델 변수 추가
Guryss Oct 26, 2024
fa4b348
[Feat] #300 - 연재상태, 별점 버튼을 재클릭하면 해제될 수 있도록 기능 구현
Guryss Oct 26, 2024
af0ce1a
[Feat] #300 - 정보뷰에서 선택한 값이 있으면 newImageView가 생기도록 구현
Guryss Oct 26, 2024
4746c79
[Feat] #300 - DetailSearchResultVC에서 itemSelected시 작품상세로 이동 구현
Guryss Oct 26, 2024
18d32d2
[Feat] #300 - 검색바 클릭 시 다시 DetailSearchVC 모달 뜰 수 있도록 구현
Guryss Oct 26, 2024
c08f489
[Design] #300 - DetailSearchEmptyView UI 및 레이아웃 구현
Guryss Oct 26, 2024
137a0a1
[Feat] #300 - rx.notification을 활용하여 알림 중첩되는 이슈 해결 및 Cell 바인딩 적용
Guryss Oct 30, 2024
a93a401
[Feat] #300 - 검색결과 없을 때 emptyView 보이도록 구현
Guryss Oct 30, 2024
b398113
[Chore] #300 - emptyView 내 텍스트 StringLiterals 처리
Guryss Oct 30, 2024
7ba6a34
[Feat] #300 - 상세탐색 결과뷰 내에서로 서버 처리 및 무한스크롤 구현
Guryss Oct 30, 2024
32221ef
[Chore] #300 - main branch update & resolve conflicts
Guryss Nov 5, 2024
36d3cfa
[Chore] #300 - genreCollectionViewHeight 관련 뷰모델 코드 삭제
Guryss Nov 6, 2024
0ea11ad
[Feat] #300 - 초기화버튼 클릭 시 모든 선택사항 deselected되도록 구현
Guryss Nov 7, 2024
5a2da88
[Feat] #300 - 이전뷰에 따른 DetailSearchResultView 분기처리 구현
Guryss Nov 7, 2024
edc9128
[Feat] #300 - 재검색 시 선택한 쿼리들이 뷰에 남아있도록 구현
Guryss Nov 7, 2024
7fa5178
[Feat] #300 - 상세탐색 내 장르 선택 시 장르 트래킹 안된 이슈 해결
Guryss Nov 7, 2024
793843e
[Feat] #300 - reset 버튼 클릭 시 다 초기화되지 않는 버그 해결
Guryss Nov 7, 2024
64f44e3
[Chore] #300 - DetailSearchResultView 내 컬렉션뷰 높이 버그 해결
Guryss Nov 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions WSSiOS/WSSiOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@
B92A06672C49858800E27652 /* DetailSearchHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B92A06662C49858800E27652 /* DetailSearchHeaderView.swift */; };
B92A06692C49877700E27652 /* DetailSearchBottomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B92A06682C49877700E27652 /* DetailSearchBottomView.swift */; };
B933BED32C4916DC00365077 /* KeywordBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = B933BED22C4916DC00365077 /* KeywordBox.swift */; };
B948BDB92CCD8DD500A7C771 /* DetailSearchResultEmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B948BDB82CCD8DD500A7C771 /* DetailSearchResultEmptyView.swift */; };
B96098802C27201B0084F697 /* NormalSearchResultCountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B960987F2C27201B0084F697 /* NormalSearchResultCountView.swift */; };
B96098882C27421F0084F697 /* FeedDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B96098872C27421F0084F697 /* FeedDetailViewController.swift */; };
B960988A2C2742680084F697 /* FeedDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B96098892C2742680084F697 /* FeedDetailViewModel.swift */; };
Expand Down Expand Up @@ -687,6 +688,7 @@
B92A06662C49858800E27652 /* DetailSearchHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailSearchHeaderView.swift; sourceTree = "<group>"; };
B92A06682C49877700E27652 /* DetailSearchBottomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailSearchBottomView.swift; sourceTree = "<group>"; };
B933BED22C4916DC00365077 /* KeywordBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeywordBox.swift; sourceTree = "<group>"; };
B948BDB82CCD8DD500A7C771 /* DetailSearchResultEmptyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailSearchResultEmptyView.swift; sourceTree = "<group>"; };
B960987F2C27201B0084F697 /* NormalSearchResultCountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NormalSearchResultCountView.swift; sourceTree = "<group>"; };
B96098872C27421F0084F697 /* FeedDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedDetailViewController.swift; sourceTree = "<group>"; };
B96098892C2742680084F697 /* FeedDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedDetailViewModel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2117,6 +2119,7 @@
B974EA422CC8E57100169639 /* DetailSearchNovelRatingStatusButton.swift */,
B974EA462CC902E800169639 /* DetailSearchResultHeaderView.swift */,
B9DC62012CC9391A00E87DD7 /* DetailSearchResultNovelView.swift */,
B948BDB82CCD8DD500A7C771 /* DetailSearchResultEmptyView.swift */,
);
path = DetailSearchAssistantView;
sourceTree = "<group>";
Expand Down Expand Up @@ -3188,6 +3191,7 @@
39ABD5A02B54D0A900DEA94E /* RegisterNormalDatePicker.swift in Sources */,
39D4A5C92BDA4C5A004FC834 /* NovelDetailViewController.swift in Sources */,
CD2D0AB92C7F0279007CF1F1 /* NovelReviewStatusCollectionViewCell.swift in Sources */,
B948BDB92CCD8DD500A7C771 /* DetailSearchResultEmptyView.swift in Sources */,
2CCBFA042B4DD73D00D787C2 /* MyPageTallyView.swift in Sources */,
2C2491812B4ADC000096F255 /* StringLiterals.swift in Sources */,
39DFB0EF2CABE47B00F87951 /* OnboardingGenreButtonView.swift in Sources */,
Expand Down
2 changes: 2 additions & 0 deletions WSSiOS/WSSiOS/Resource/Constants/Strings/StringLiterals.swift
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,8 @@ enum StringLiterals {
static let vibe = "분위기/전개"

static let placeHolder = "키워드를 검색하세요"

static let empty = "해당하는 작품이 없어요\n검색의 범위를 더 넓혀보세요"
}

enum Memo {
Expand Down
13 changes: 13 additions & 0 deletions WSSiOS/WSSiOS/Resource/Extensions/UIViewController+.swift
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,19 @@ extension UIViewController {
viewController.hidesBottomBarWhenPushed = true
self.navigationController?.pushViewController(viewController, animated: true)
}

func presentToDetailSearchViewController(selectedKeywordList: [KeywordData],
previousViewInfo: PreviousViewType,
selectedFilteredQuery: SearchFilterQuery) {
let detailSearchViewController = DetailSearchViewController(
viewModel: DetailSearchViewModel(
keywordRepository: DefaultKeywordRepository(
keywordService: DefaultKeywordService()),
selectedKeywordList: selectedKeywordList,
previousViewInfo: previousViewInfo,
selectedFilteredQuery: selectedFilteredQuery))
self.presentModalViewController(detailSearchViewController)
}
}

extension UIViewController: UIGestureRecognizerDelegate {
Expand Down
4 changes: 4 additions & 0 deletions WSSiOS/WSSiOS/Source/Data/Base/CompletedStatus.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,8 @@ enum CompletedStatus: String, CaseIterable {
case .notCompleted: return false
}
}

init(isCompleted: Bool) {
self = isCompleted ? .completed : .notCompleted
}
}
15 changes: 15 additions & 0 deletions WSSiOS/WSSiOS/Source/Data/Base/NovelRatingStatus.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,19 @@ enum NovelRatingStatus: String, CaseIterable {
case .aboveFourPointEight: return "4.8이상"
}
}

init(toFloat: Float) {
switch toFloat {
case 3.5:
self = .aboveThreePointFive
case 4.0:
self = .aboveFourPointZero
case 4.5:
self = .aboveFourPointFive
case 4.8:
self = .aboveFourPointEight
default:
self = .aboveFourPointEight
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ final class DetailSearchCompletedStatusButton: UIButton {
//MARK: - Properties

let status: CompletedStatus

//MARK: - Components

private let buttonLabel = UILabel()

//MARK: - Life Cycle
Expand Down Expand Up @@ -63,7 +63,7 @@ final class DetailSearchCompletedStatusButton: UIButton {

// MARK: - Custom Method

func updateButton(selectedCompletedStatus: CompletedStatus) {
func updateButton(selectedCompletedStatus: CompletedStatus?) {
let isSelected = selectedCompletedStatus == status

self.do {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,15 +154,21 @@ final class DetailSearchInfoView: UIView {
}
}

func updateCompletedKeyword(_ selectedCompletedStatus: CompletedStatus) {
func updateCompletedKeyword(_ selectedCompletedStatus: CompletedStatus?) {
completedStatusButtons.forEach {
$0.updateButton(selectedCompletedStatus: selectedCompletedStatus)
}
}

func updateNovelRatingKeyword(_ selectedNovelRatingStatus: NovelRatingStatus) {
func updateNovelRatingKeyword(_ selectedNovelRatingStatus: NovelRatingStatus?) {
novelRatingStatusButtons.forEach {
$0.updateButton(selectedNovelRatingStatus: selectedNovelRatingStatus)
}
}

func resetAllStates() {
genreCollectionView.indexPathsForSelectedItems?.forEach { indexPath in
genreCollectionView.deselectItem(at: indexPath, animated: false)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ final class DetailSearchNovelRatingStatusButton: UIButton {

// MARK: - Custom Method

func updateButton(selectedNovelRatingStatus: NovelRatingStatus) {
func updateButton(selectedNovelRatingStatus: NovelRatingStatus?) {
let isSelected = selectedNovelRatingStatus == status

self.do {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// DetailSearchResultEmptyView.swift
// WSSiOS
//
// Created by Seoyeon Choi on 10/27/24.
//

import UIKit

import SnapKit
import Then

final class DetailSearchResultEmptyView: UIView {

//MARK: - UI Components

private let stackView = UIStackView()
private let emptyImageView = UIImageView()
private let emptyDescriptionLabel = UILabel()

//MARK: - Life Cycle

override init(frame: CGRect) {
super.init(frame: frame)

setUI()
setHierarchy()
setLayout()
}

@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

private func setUI() {
stackView.do {
$0.axis = .vertical
$0.spacing = 10
$0.alignment = .center
}

emptyImageView.do {
$0.image = .imgEmpty
}

emptyDescriptionLabel.do {
$0.applyWSSFont(.body1, with: StringLiterals.DetailSearch.empty)
$0.textColor = .wssGray200
$0.textAlignment = .center
$0.numberOfLines = 2
}
}

private func setHierarchy() {
self.addSubview(stackView)
stackView.addArrangedSubviews(emptyImageView,
emptyDescriptionLabel)
}

private func setLayout() {
stackView.snp.makeConstraints {
$0.center.equalToSuperview()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ final class DetailSearchResultHeaderView: UIView {

let backButton = UIButton()

private let backgroundView = UIView()
let backgroundView = UIView()
private let headerLabel = UILabel()
private let controllerImageView = UIImageView()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ final class DetailSearchResultNovelView: UIView {

contentView.snp.makeConstraints {
$0.edges.equalTo(scrollView.contentLayoutGuide)
$0.height.greaterThanOrEqualTo(self.snp.height).priority(.low)
$0.width.equalTo(scrollView.snp.width)
}

Expand All @@ -97,9 +96,8 @@ final class DetailSearchResultNovelView: UIView {

resultNovelCollectionView.snp.makeConstraints {
$0.top.equalTo(novelTitleLabel.snp.bottom).offset(14)
$0.horizontalEdges.equalToSuperview()
$0.horizontalEdges.bottom.equalToSuperview()
$0.height.equalTo(0)
$0.bottom.equalToSuperview()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ final class DetailSearchResultView: UIView {

let headerView = DetailSearchResultHeaderView()
let novelView = DetailSearchResultNovelView()
let emptyView = DetailSearchResultEmptyView()

//MARK: - Life Cycle

Expand All @@ -34,11 +35,15 @@ final class DetailSearchResultView: UIView {

private func setUI() {
self.backgroundColor = .wssWhite

novelView.isHidden = true
emptyView.isHidden = true
}

private func setHierarchy() {
self.addSubviews(headerView,
novelView)
novelView,
emptyView)
}

private func setLayout() {
Expand All @@ -52,5 +57,17 @@ final class DetailSearchResultView: UIView {
$0.horizontalEdges.equalToSuperview().inset(20)
$0.bottom.equalToSuperview()
}

emptyView.snp.makeConstraints {
$0.top.equalTo(headerView.snp.bottom)
$0.horizontalEdges.bottom.equalToSuperview()
}
}

//MARK: - Custom Method

func showEmptyView(show: Bool) {
emptyView.isHidden = !show
novelView.isHidden = show
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ final class DetailSearchResultViewController: UIViewController, UIScrollViewDele
private let viewModel: DetailSearchResultViewModel
private let disposeBag = DisposeBag()

private let viewDidLoadEvent = PublishRelay<Void>()

//MARK: - Components

private let rootView = DetailSearchResultView()
Expand All @@ -39,6 +41,7 @@ final class DetailSearchResultViewController: UIViewController, UIScrollViewDele
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)

setNavigationBar()
swipeBackGesture()
}

Expand All @@ -47,8 +50,14 @@ final class DetailSearchResultViewController: UIViewController, UIScrollViewDele

registerCell()
setDelegate()

bindViewModel()

viewDidLoadEvent.accept(())
}

private func setNavigationBar() {
self.navigationController?.isNavigationBarHidden = true
}

private func registerCell() {
Expand All @@ -68,7 +77,12 @@ final class DetailSearchResultViewController: UIViewController, UIScrollViewDele
private func bindViewModel() {
let input = DetailSearchResultViewModel.Input(
backButtonDidTap: rootView.headerView.backButton.rx.tap,
novelCollectionViewContentSize: rootView.novelView.resultNovelCollectionView.rx.observe(CGSize.self, "contentSize")
novelCollectionViewContentSize: rootView.novelView.resultNovelCollectionView.rx.observe(CGSize.self, "contentSize"),
novelResultCellSelected: rootView.novelView.resultNovelCollectionView.rx.itemSelected,
searchHeaderViewDidTap: rootView.headerView.backgroundView.rx.tapGesture().when(.recognized).asObservable(),
viewDidLoadEvent: self.viewDidLoadEvent.asObservable(),
novelCollectionViewReachedBottom: observeReachedBottom(rootView.novelView.scrollView),
updateDetailSearchResultNotification: NotificationCenter.default.rx.notification(Notification.Name("PushToUpdateDetailSearchResult"))
)

let output = viewModel.transform(from: input, disposeBag: disposeBag)
Expand All @@ -79,8 +93,8 @@ final class DetailSearchResultViewController: UIViewController, UIScrollViewDele
})
.disposed(by: disposeBag)

viewModel.getDetailSearchNovelsObservable()
.map { $0.novels }
output.filteredNovelsData
.map { $0 }
.bind(to: rootView.novelView.resultNovelCollectionView.rx.items(cellIdentifier: HomeTasteRecommendCollectionViewCell.cellIdentifier, cellType: HomeTasteRecommendCollectionViewCell.self)) { row, element, cell in
cell.bindData(data: element)
}
Expand All @@ -92,12 +106,45 @@ final class DetailSearchResultViewController: UIViewController, UIScrollViewDele
})
.disposed(by: disposeBag)

viewModel.getDetailSearchNovelsObservable()
.map { $0.resultCount }
.observe(on: MainScheduler.instance)
.bind(with: self, onNext: { owner, count in
output.resultCount
.drive(with: self, onNext: { owner, count in
owner.rootView.novelView.updateNovelCountLabel(count: count)
})
.disposed(by: disposeBag)

output.pushToNovelDetailViewController
.subscribe(with: self, onNext: { owner, novelId in
owner.pushToDetailViewController(novelId: novelId)
})
.disposed(by: disposeBag)

output.presentDetailSearchModal
.subscribe(with: self, onNext: { owner, data in
owner.presentToDetailSearchViewController(selectedKeywordList: data.keywords,
previousViewInfo: .resultSearchBar,
selectedFilteredQuery: data)
})
.disposed(by: disposeBag)

output.showEmptyView
.observe(on: MainScheduler.instance)
.subscribe(with: self, onNext: { owner, show in
owner.rootView.showEmptyView(show: show)
})
.disposed(by: disposeBag)
}

// MARK: - Custom Method

private func observeReachedBottom(_ scrollView: UIScrollView) -> Observable<Bool> {
return scrollView.rx.contentOffset
.map { contentOffset in
let contentHeight = scrollView.contentSize.height
let viewHeight = scrollView.frame.size.height
let offsetY = contentOffset.y

return offsetY + viewHeight >= contentHeight
}
.distinctUntilChanged()
}
}
Loading