diff --git a/HikingClub.xcodeproj/project.pbxproj b/HikingClub.xcodeproj/project.pbxproj index 1634f9c..fbc8f7b 100644 --- a/HikingClub.xcodeproj/project.pbxproj +++ b/HikingClub.xcodeproj/project.pbxproj @@ -40,6 +40,10 @@ 06A1ECB5271952B100EA78D8 /* NDTextFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06A1ECB4271952B100EA78D8 /* NDTextFieldView.swift */; }; 06A1ECB7271967E600EA78D8 /* ComponentTestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06A1ECB6271967E600EA78D8 /* ComponentTestViewController.swift */; }; 06A1ECB927198ED200EA78D8 /* UITextField+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06A1ECB827198ED200EA78D8 /* UITextField+.swift */; }; + 06AB8CBA273136F7007F32AA /* SearchCategoryResultViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06AB8CB9273136F7007F32AA /* SearchCategoryResultViewController.swift */; }; + 06AB8CBC27326A3C007F32AA /* SearchCategoryResultViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06AB8CBB27326A3C007F32AA /* SearchCategoryResultViewModel.swift */; }; + 06AB8CBF273287B4007F32AA /* CategoryTabCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06AB8CBD273287B4007F32AA /* CategoryTabCollectionViewCell.swift */; }; + 06AB8CC0273287B4007F32AA /* CategoryTabCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 06AB8CBE273287B4007F32AA /* CategoryTabCollectionViewCell.xib */; }; 06AB8CB827312461007F32AA /* RxTextFieldDelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06AB8CB727312461007F32AA /* RxTextFieldDelegateProxy.swift */; }; 06B68F9727228FF9007174E1 /* NDSearchTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06B68F9627228FF9007174E1 /* NDSearchTextField.swift */; }; 06BC1F74271C1B79001A6584 /* NDAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06BC1F73271C1B79001A6584 /* NDAlert.swift */; }; @@ -139,6 +143,10 @@ 06A1ECB4271952B100EA78D8 /* NDTextFieldView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NDTextFieldView.swift; sourceTree = ""; }; 06A1ECB6271967E600EA78D8 /* ComponentTestViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComponentTestViewController.swift; sourceTree = ""; }; 06A1ECB827198ED200EA78D8 /* UITextField+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextField+.swift"; sourceTree = ""; }; + 06AB8CB9273136F7007F32AA /* SearchCategoryResultViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchCategoryResultViewController.swift; sourceTree = ""; }; + 06AB8CBB27326A3C007F32AA /* SearchCategoryResultViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchCategoryResultViewModel.swift; sourceTree = ""; }; + 06AB8CBD273287B4007F32AA /* CategoryTabCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryTabCollectionViewCell.swift; sourceTree = ""; }; + 06AB8CBE273287B4007F32AA /* CategoryTabCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CategoryTabCollectionViewCell.xib; sourceTree = ""; }; 06AB8CB727312461007F32AA /* RxTextFieldDelegateProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RxTextFieldDelegateProxy.swift; sourceTree = ""; }; 06B68F9627228FF9007174E1 /* NDSearchTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NDSearchTextField.swift; sourceTree = ""; }; 06BC1F73271C1B79001A6584 /* NDAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NDAlert.swift; sourceTree = ""; }; @@ -233,6 +241,8 @@ 06962E94272927A00021402F /* Search.storyboard */, 06962E92272927770021402F /* SearchViewController.swift */, 06962E96272928290021402F /* SearchViewModel.swift */, + 06AB8CB9273136F7007F32AA /* SearchCategoryResultViewController.swift */, + 06AB8CBB27326A3C007F32AA /* SearchCategoryResultViewModel.swift */, ); path = Search; sourceTree = ""; @@ -244,6 +254,8 @@ 06962E98272A86100021402F /* RecentSearchCollectionViewCell.swift */, 06962E9E272BCD400021402F /* CategoryCollectionViewCell.xib */, 06962E9D272BCD400021402F /* CategoryCollectionViewCell.swift */, + 06AB8CBE273287B4007F32AA /* CategoryTabCollectionViewCell.xib */, + 06AB8CBD273287B4007F32AA /* CategoryTabCollectionViewCell.swift */, ); path = Cell; sourceTree = ""; @@ -675,6 +687,7 @@ 283AF07827086DA40033ED71 /* RoadTableViewCell.xib in Resources */, 06962E95272927A00021402F /* Search.storyboard in Resources */, 06A1EC9027088F4400EA78D8 /* HitThemeTableHeaderView.xib in Resources */, + 06AB8CC0273287B4007F32AA /* CategoryTabCollectionViewCell.xib in Resources */, 7755080326F842E6005B8763 /* LaunchScreen.storyboard in Resources */, 06A1ECA12708CEFA00EA78D8 /* HitThemeHeaderCollectionViewCell.xib in Resources */, 7755080026F842E6005B8763 /* Assets.xcassets in Resources */, @@ -701,6 +714,7 @@ 06A1EC992708A7F300EA78D8 /* UIFont+.swift in Sources */, 77E5913827115F890070F381 /* LoginNavigationViewController.swift in Sources */, 77C69BE927294775004C2207 /* SignUpInputViewModel.swift in Sources */, + 06AB8CBF273287B4007F32AA /* CategoryTabCollectionViewCell.swift in Sources */, 775B95AC27204D1C004D4540 /* UITabBarItem+.swift in Sources */, 06A1EC9B2708B12A00EA78D8 /* UILabel+.swift in Sources */, 777B517C2712AEB300299430 /* EmailAuthroizeViewController.swift in Sources */, @@ -743,12 +757,14 @@ 775507F926F842E5005B8763 /* SceneDelegate.swift in Sources */, 283AF06D27086B950033ED71 /* HomeViewController.swift in Sources */, 77C69BEB272CD4E6004C2207 /* KeyboardAccessoryToolbar.swift in Sources */, + 06AB8CBA273136F7007F32AA /* SearchCategoryResultViewController.swift in Sources */, 777B517327129D6500299430 /* CodeBasedProtocol.swift in Sources */, 7760CA8F2725313B006B39A9 /* EmailAPI.swift in Sources */, 773D61A02719D0D0001528B3 /* SignInViewController.swift in Sources */, 7760CA91272540B2006B39A9 /* PlaceAPI.swift in Sources */, 06A1ECA32708D21400EA78D8 /* UITableView+.swift in Sources */, 067DC4F026FEF8A000650862 /* NetworkProvider.swift in Sources */, + 06AB8CBC27326A3C007F32AA /* SearchCategoryResultViewModel.swift in Sources */, 7760CA9E27259F53006B39A9 /* PlaceModel.swift in Sources */, 777B517E2714336700299430 /* InitialSettingViewController.swift in Sources */, 77213CF82708EF1600994C9A /* BaseWebView.swift in Sources */, diff --git a/HikingClub/Feature/Search/Cell/CategoryTabCollectionViewCell.swift b/HikingClub/Feature/Search/Cell/CategoryTabCollectionViewCell.swift new file mode 100644 index 0000000..531f12b --- /dev/null +++ b/HikingClub/Feature/Search/Cell/CategoryTabCollectionViewCell.swift @@ -0,0 +1,36 @@ +// +// CategoryTabCollectionViewCell.swift +// HikingClub +// +// Created by 남수김 on 2021/11/03. +// + +import UIKit + +final class CategoryTabCollectionViewCell: UICollectionViewCell { + + private let tabButton: NDTabButton = NDTabButton() + override var isSelected: Bool { + didSet { + tabButton.setSelected(isSelected) + } + } + + override func awakeFromNib() { + super.awakeFromNib() + addSubview(tabButton) + tabButton.snp.makeConstraints { + $0.leading.top.trailing.bottom.equalToSuperview() + } + tabButton.setEnabledTouch(false) + } + + override func prepareForReuse() { + super.prepareForReuse() + tabButton.setTitle("") + } + + func configure(with model: String) { + tabButton.setTitle(model) + } +} diff --git a/HikingClub/Feature/Search/Cell/CategoryTabCollectionViewCell.xib b/HikingClub/Feature/Search/Cell/CategoryTabCollectionViewCell.xib new file mode 100644 index 0000000..fcf49c7 --- /dev/null +++ b/HikingClub/Feature/Search/Cell/CategoryTabCollectionViewCell.xib @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/HikingClub/Feature/Search/Search.storyboard b/HikingClub/Feature/Search/Search.storyboard index d9b0559..7210ad2 100644 --- a/HikingClub/Feature/Search/Search.storyboard +++ b/HikingClub/Feature/Search/Search.storyboard @@ -52,6 +52,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/HikingClub/Feature/Search/SearchCategoryResultViewController.swift b/HikingClub/Feature/Search/SearchCategoryResultViewController.swift new file mode 100644 index 0000000..f497a5d --- /dev/null +++ b/HikingClub/Feature/Search/SearchCategoryResultViewController.swift @@ -0,0 +1,179 @@ +// +// SearchCategoryResultViewController.swift +// HikingClub +// +// Created by 남수김 on 2021/11/02. +// + +import UIKit +import RxCocoa +import RxSwift + +final class SearchCategoryResultViewController: BaseViewController { + @IBOutlet private weak var naviBar: NaviBar! + @IBOutlet private weak var tableView: UITableView! + private let categoryCollectionView: UICollectionView = { + let flowLayout = UICollectionViewFlowLayout() + flowLayout.scrollDirection = .horizontal + flowLayout.minimumInteritemSpacing = 6 + flowLayout.sectionInset = .zero + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout) + collectionView.showsVerticalScrollIndicator = false + collectionView.showsHorizontalScrollIndicator = false + collectionView.contentInset = .init(top: 0, left: 16, bottom: 0, right: 0) + collectionView.backgroundColor = .gray100 + return collectionView + }() + private var tableViewHeaderView: UIView? + private var tableViewHeaderViewTitleView = UIView() + private let tableViewHeaderViewTitleLabel: UILabel = { + let label = UILabel() + label.setFont(.semiBold24) + label.textColor = .gray900 + return label + }() + private let backButton: UIButton = { + let button = UIButton(type: .custom) + button.setImage(.icon_angleBracket_left_gray900_24) + return button + }() + + private let headerHeight: CGFloat = 108 + 57 + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + // 초기선택설정 + categoryCollectionView.selectItem(at: IndexPath(item: 0, section: 0), animated: true, scrollPosition: .left) + } + + private func setTableHeaderView() { + tableViewHeaderView = UIView(frame: .init(x: 0, y: 0, width: tableView.frame.width, height: headerHeight)) + view.addSubview(tableViewHeaderView!) + view.bringSubviewToFront(naviBar) + tableViewHeaderView?.snp.makeConstraints { + $0.leading.trailing.equalToSuperview() + $0.top.equalTo(view.safeAreaLayoutGuide.snp.top) + $0.height.equalTo(headerHeight) + } + + tableViewHeaderView?.addSubViews(tableViewHeaderViewTitleView, categoryCollectionView) + tableViewHeaderViewTitleView.addSubViews(backButton, tableViewHeaderViewTitleLabel) + tableViewHeaderViewTitleView.snp.makeConstraints { + $0.leading.top.trailing.equalToSuperview() + $0.height.equalTo(108) + } + + backButton.snp.makeConstraints { + $0.top.equalToSuperview().offset(8) + $0.leading.equalToSuperview().offset(16) + } + + tableViewHeaderViewTitleLabel.snp.makeConstraints { + $0.top.equalTo(backButton.snp.bottom).offset(38) + $0.leading.equalToSuperview().offset(20) + } + + categoryCollectionView.snp.makeConstraints { + $0.leading.trailing.bottom.equalToSuperview() + $0.height.equalTo(57) + } + } + + override func attribute() { + super.attribute() + naviBar.setBackItemImage() + naviBar.isHidden = true + tableView.contentInset = .init(top: headerHeight, left: 0, bottom: 0, right: 0) + tableView.register(RoadTableViewCell.self) + tableView.separatorStyle = .none + categoryCollectionView.register(CategoryTabCollectionViewCell.self) + } + + override func layout() { + super.layout() + setTableHeaderView() + } + + override func bind() { + super.bind() + tableView.rx.setDelegate(self).disposed(by: disposeBag) + categoryCollectionView.rx.setDelegate(self).disposed(by: disposeBag) + + naviBar.rx.tapLeftItem + .subscribe(onNext: { [weak self] in + self?.navigationController?.popViewController(animated: true) + }) + .disposed(by: disposeBag) + + backButton.rx.tap + .subscribe(onNext: { [weak self] in + self?.navigationController?.popViewController(animated: true) + }) + .disposed(by: disposeBag) + + viewModel.categoryName + .bind(to: naviBar.rx.title) + .disposed(by: disposeBag) + + viewModel.categoryName + .bind(to: tableViewHeaderViewTitleLabel.rx.text) + .disposed(by: disposeBag) + + // 카테고리 셀 + viewModel.categoryWords + .bind(to: categoryCollectionView.rx.items(cellIdentifier: "CategoryTabCollectionViewCell", + cellType: CategoryTabCollectionViewCell.self)) { item, cellModel, cell in + cell.configure(with: cellModel) + }.disposed(by: disposeBag) + + // 카테고리 텝버튼 클릭시 + categoryCollectionView.rx.itemSelected + .map { $0.item } + .bind(to: viewModel.selectedCategory) + .disposed(by: disposeBag) + + // 길정보 셀 + viewModel.roadDatas + .bind(to: tableView.rx.items(cellIdentifier: "RoadTableViewCell", + cellType: RoadTableViewCell.self)) { row, cellModel, cell in + cell.configure(tags: [cellModel]) + }.disposed(by: disposeBag) + + tableView.rx.itemSelected + .subscribe(onNext: { [weak self] in + // TODO: 길상세페이지 넘기기 + print($0) + let webViewController = WebViewController(WebViewModel()) + webViewController.hidesBottomBarWhenPushed = true + self?.tableView.deselectRow(at: $0, animated: true) + self?.navigationController?.pushViewController(webViewController, animated: true) + }) + .disposed(by: disposeBag) + + // 헤더뷰 스크롤 효과 + tableView.rx.contentOffset + .subscribe(onNext: { [weak self] in + // -165(headerHeight)부터 스크롤시 + + guard let self = self else { return } + let posY = $0.y + self.headerHeight + self.naviBar.isHidden = posY <= 70 + self.tableViewHeaderViewTitleView.alpha = 1 - posY / 70 + self.tableViewHeaderView?.transform = .init(translationX: 0, y: posY <= 70 ? -posY : 40 - 108) + self.tableView.scrollIndicatorInsets = .init(top: self.headerHeight - posY, left: 0, bottom: 0, right: 0) + }) + .disposed(by: disposeBag) + } +} + +extension SearchCategoryResultViewController: UICollectionViewDelegateFlowLayout { + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + let word = viewModel.categoryWords.value[indexPath.item] + let tab: NDTabButton = { + let tab = NDTabButton() + tab.setTitle(word) + return tab + }() + tab.layoutIfNeeded() + return CGSize(width: tab.bounds.width, height: 33) + } +} diff --git a/HikingClub/Feature/Search/SearchCategoryResultViewModel.swift b/HikingClub/Feature/Search/SearchCategoryResultViewModel.swift new file mode 100644 index 0000000..b7cc769 --- /dev/null +++ b/HikingClub/Feature/Search/SearchCategoryResultViewModel.swift @@ -0,0 +1,38 @@ +// +// SearchCategoryResultViewModel.swift +// HikingClub +// +// Created by 남수김 on 2021/11/03. +// + +import Foundation +import RxRelay + +final class SearchCategoryResultViewModel: BaseViewModel { + // MARK: - Output + let roadDatas: BehaviorRelay<[String]> = BehaviorRelay(value: []) + let categoryWords: BehaviorRelay<[String]> = BehaviorRelay(value: []) + let categoryName: BehaviorRelay = BehaviorRelay(value: "") + + // MARK: - Input + let selectedCategory: BehaviorRelay = BehaviorRelay(value: 0) + + init(categoryName: String) { + super.init() + // FIXME: - mock데이터 삭제 + roadDatas.accept(["1","1","1","1"]) + categoryWords.accept(["가가가가나나나나다다다다라라라라","12","13","14","12345","하하하하하하하하하하ㅏ"]) + + bind() + } + + private func bind() { + selectedCategory + .filter { [weak self] in 0 <= $0 && $0 < self?.categoryWords.value.count ?? 0 } + .subscribe(onNext: { [weak self] index in + guard let word = self?.categoryWords.value[index] else { return } + self?.categoryName.accept(word) + }) + .disposed(by: disposeBag) + } +} diff --git a/HikingClub/Feature/Search/SearchViewController.swift b/HikingClub/Feature/Search/SearchViewController.swift index bff9055..2bc3521 100644 --- a/HikingClub/Feature/Search/SearchViewController.swift +++ b/HikingClub/Feature/Search/SearchViewController.swift @@ -150,8 +150,14 @@ final class SearchViewController: BaseViewController { // TODO: 터치시 해당 카테고리로 이동 categoryCollectionView.rx.itemSelected - .subscribe(onNext: { - print($0) + .subscribe(onNext: { [weak self] indexPath in + let item = indexPath.item + let nextViewController = self?.storyboard?.instantiate("SearchCategoryResultViewController") { [weak self] coder -> SearchCategoryResultViewController? in + guard let word = self?.viewModel.categoryWords.value[item] else { return nil } + return .init(coder, SearchCategoryResultViewModel(categoryName: word)) + } + guard let nextViewController = nextViewController else { return } + self?.navigationController?.pushViewController(nextViewController, animated: true) }) .disposed(by: disposeBag) }