diff --git a/Projects/App/Sources/Application/AppComponent+Search.swift b/Projects/App/Sources/Application/AppComponent+Search.swift index 2618b4e42..08e8f8c20 100644 --- a/Projects/App/Sources/Application/AppComponent+Search.swift +++ b/Projects/App/Sources/Application/AppComponent+Search.swift @@ -5,6 +5,12 @@ import SearchFeature import SearchFeatureInterface extension AppComponent { + var searchGlobalScrollState: any SearchGlobalScrollProtocol { + shared { + SearchGlobalScrollState() + } + } + var searchFactory: any SearchFactory { SearchComponent(parent: self) } diff --git a/Projects/Features/SearchFeature/Resources/Search.storyboard b/Projects/Features/SearchFeature/Resources/Search.storyboard index 5b285e430..179b8c015 100644 --- a/Projects/Features/SearchFeature/Resources/Search.storyboard +++ b/Projects/Features/SearchFeature/Resources/Search.storyboard @@ -20,13 +20,14 @@ + - + @@ -73,7 +74,7 @@ - + @@ -148,7 +149,9 @@ + + @@ -159,6 +162,9 @@ + + + diff --git a/Projects/Features/SearchFeature/Sources/Components/AfterSearchComponent.swift b/Projects/Features/SearchFeature/Sources/Components/AfterSearchComponent.swift index ac29d949d..0fafabecd 100644 --- a/Projects/Features/SearchFeature/Sources/Components/AfterSearchComponent.swift +++ b/Projects/Features/SearchFeature/Sources/Components/AfterSearchComponent.swift @@ -8,6 +8,7 @@ import SongsDomainInterface public protocol AfterSearchDependency: Dependency { var songSearchResultFactory: any SongSearchResultFactory { get } var listSearchResultFactory: any ListSearchResultFactory { get } + var searchGlobalScrollState: any SearchGlobalScrollProtocol { get } } public final class AfterSearchComponent: Component { @@ -15,6 +16,7 @@ public final class AfterSearchComponent: Component { return AfterSearchViewController.viewController( songSearchResultFactory: dependency.songSearchResultFactory, listSearchResultFactory: dependency.listSearchResultFactory, + searchGlobalScrollState: dependency.searchGlobalScrollState, reactor: .init(text: text) ) } diff --git a/Projects/Features/SearchFeature/Sources/Components/ListSearchResultComponent.swift b/Projects/Features/SearchFeature/Sources/Components/ListSearchResultComponent.swift index 6f36b2ff6..df83f4566 100644 --- a/Projects/Features/SearchFeature/Sources/Components/ListSearchResultComponent.swift +++ b/Projects/Features/SearchFeature/Sources/Components/ListSearchResultComponent.swift @@ -11,6 +11,7 @@ public protocol ListSearchResultDependency: Dependency { var fetchSearchPlaylistsUseCase: any FetchSearchPlaylistsUseCase { get } var searchSortOptionComponent: SearchSortOptionComponent { get } var playlistDetailFactory: any PlaylistDetailFactory { get } + var searchGlobalScrollState: any SearchGlobalScrollProtocol { get } } public final class ListSearchResultComponent: Component, ListSearchResultFactory { @@ -21,7 +22,8 @@ public final class ListSearchResultComponent: Component, SearchFactory { @@ -16,7 +17,8 @@ public final class SearchComponent: Component, SearchFactory { reactor: SearchReactor(), beforeSearchComponent: self.dependency.beforeSearchComponent, afterSearchComponent: self.dependency.afterSearchComponent, - textPopUpFactory: dependency.textPopUpFactory + textPopUpFactory: dependency.textPopUpFactory, + searchGlobalScrollState: dependency.searchGlobalScrollState ) } } diff --git a/Projects/Features/SearchFeature/Sources/Components/SongSearchResultComponent.swift b/Projects/Features/SearchFeature/Sources/Components/SongSearchResultComponent.swift index f42380ba4..cc12bab02 100644 --- a/Projects/Features/SearchFeature/Sources/Components/SongSearchResultComponent.swift +++ b/Projects/Features/SearchFeature/Sources/Components/SongSearchResultComponent.swift @@ -10,6 +10,7 @@ public protocol SongSearchResultDependency: Dependency { var fetchSearchSongsUseCase: any FetchSearchSongsUseCase { get } var searchSortOptionComponent: SearchSortOptionComponent { get } var containSongsFactory: any ContainSongsFactory { get } + var searchGlobalScrollState: any SearchGlobalScrollProtocol { get } } public final class SongSearchResultComponent: Component, SongSearchResultFactory { @@ -20,7 +21,8 @@ public final class SongSearchResultComponent: Component { get } + var expandSearchHeaderObservable: Observable { get } + + var songResultScrollToTopObservable: Observable { get } + var listResultScrollToTopObservable: Observable { get } + + func scrollTo(source: (CGFloat, CGFloat)) + func expand() + func scrollToTop(page: SearchResultPage) +} + +public final class SearchGlobalScrollState: SearchGlobalScrollProtocol { + private let scrollAmountSubject = PublishSubject<(CGFloat, CGFloat)>() + private let expandSearchHeaderSubject = PublishSubject() + private let songResultScrollToTopSubject = PublishSubject() + private let listResultScrollToTopSubject = PublishSubject() + + public init() {} + + public var scrollAmountObservable: Observable<(CGFloat, CGFloat)> { + scrollAmountSubject + } + + public func scrollTo(source: (CGFloat, CGFloat)) { + scrollAmountSubject.onNext(source) + } + + public var expandSearchHeaderObservable: Observable { + expandSearchHeaderSubject + } + + public func expand() { + expandSearchHeaderSubject.onNext(()) + } + + public var songResultScrollToTopObservable: Observable { + songResultScrollToTopSubject + } + + public var listResultScrollToTopObservable: Observable { + listResultScrollToTopSubject + } + + public func scrollToTop(page: SearchResultPage) { + switch page { + case .song: + songResultScrollToTopSubject.onNext(()) + case .list: + listResultScrollToTopSubject.onNext(()) + } + } +} diff --git a/Projects/Features/SearchFeature/Sources/View/ListResultCell.swift b/Projects/Features/SearchFeature/Sources/View/ListResultCell.swift index e3766d9fe..49a862ba1 100644 --- a/Projects/Features/SearchFeature/Sources/View/ListResultCell.swift +++ b/Projects/Features/SearchFeature/Sources/View/ListResultCell.swift @@ -34,11 +34,11 @@ final class ListResultCell: UICollectionViewCell { $0.numberOfLines = 1 } - private let dateLabel = WMLabel( + private let sharedCountLabel = WMLabel( text: "", textColor: DesignSystemAsset.NewGrayColor.gray900.color, font: .sc7(weight: .score3Light), - alignment: .left, + alignment: .right, lineHeight: UIFont.WMFontSystem.t7().lineHeight, kernValue: -0.5 ).then { @@ -59,7 +59,7 @@ final class ListResultCell: UICollectionViewCell { extension ListResultCell { private func addSubview() { - self.contentView.addSubviews(thumbnailView, titleLabel, creatorLabel, dateLabel) + self.contentView.addSubviews(thumbnailView, titleLabel, creatorLabel, sharedCountLabel) } private func setLayout() { @@ -82,8 +82,8 @@ extension ListResultCell { $0.bottom.equalTo(thumbnailView.snp.bottom) } - dateLabel.snp.makeConstraints { - $0.width.equalTo(70) + sharedCountLabel.snp.makeConstraints { + $0.width.lessThanOrEqualTo(66) $0.centerY.equalTo(thumbnailView.snp.centerY) $0.trailing.equalToSuperview().inset(20) $0.leading.equalTo(titleLabel.snp.trailing).offset(8) @@ -93,7 +93,7 @@ extension ListResultCell { public func update(_ model: SearchPlaylistEntity) { titleLabel.text = model.title creatorLabel.text = model.userName - dateLabel.text = model.date + sharedCountLabel.text = model.date thumbnailView.kf.setImage(with: URL(string: model.image), placeholder: nil, options: [.transition(.fade(0.2))]) } } diff --git a/Projects/Features/SearchFeature/Sources/View/SongResultCell.swift b/Projects/Features/SearchFeature/Sources/View/SongResultCell.swift index 052b3904f..41cc2dd2f 100644 --- a/Projects/Features/SearchFeature/Sources/View/SongResultCell.swift +++ b/Projects/Features/SearchFeature/Sources/View/SongResultCell.swift @@ -17,7 +17,7 @@ final class SongResultCell: UICollectionViewCell { textColor: DesignSystemAsset.NewGrayColor.gray900.color, font: .t6(weight: .medium), alignment: .left, - lineHeight: UIFont.WMFontSystem.t6().lineHeight, + lineHeight: UIFont.WMFontSystem.t6(weight: .medium).lineHeight, kernValue: -0.5 ).then { $0.numberOfLines = 1 @@ -38,7 +38,7 @@ final class SongResultCell: UICollectionViewCell { text: "", textColor: DesignSystemAsset.NewGrayColor.gray900.color, font: .sc7(weight: .score3Light), - alignment: .left, + alignment: .right, lineHeight: UIFont.WMFontSystem.t7().lineHeight, kernValue: -0.5 ).then { @@ -83,7 +83,7 @@ extension SongResultCell { } dateLabel.snp.makeConstraints { - $0.width.equalTo(70) + $0.width.lessThanOrEqualTo(66) $0.centerY.equalTo(thumbnailView.snp.centerY) $0.trailing.equalToSuperview().inset(20) $0.leading.equalTo(titleLabel.snp.trailing).offset(8) diff --git a/Projects/Features/SearchFeature/Sources/ViewControllers/AfterSearchViewController.swift b/Projects/Features/SearchFeature/Sources/ViewControllers/AfterSearchViewController.swift index 9d028650d..beb296f7d 100644 --- a/Projects/Features/SearchFeature/Sources/ViewControllers/AfterSearchViewController.swift +++ b/Projects/Features/SearchFeature/Sources/ViewControllers/AfterSearchViewController.swift @@ -19,6 +19,7 @@ public final class AfterSearchViewController: TabmanViewController, ViewControll private var songSearchResultFactory: SongSearchResultFactory! private var listSearchResultFactory: ListSearchResultFactory! + private var searchGlobalScrollState: SearchGlobalScrollProtocol! public var disposeBag = DisposeBag() private var viewControllers: [UIViewController] = [ @@ -34,15 +35,26 @@ public final class AfterSearchViewController: TabmanViewController, ViewControll public static func viewController( songSearchResultFactory: SongSearchResultFactory, listSearchResultFactory: ListSearchResultFactory, + searchGlobalScrollState: any SearchGlobalScrollProtocol, reactor: AfterSearchReactor ) -> AfterSearchViewController { let viewController = AfterSearchViewController.viewController(storyBoardName: "Search", bundle: Bundle.module) viewController.songSearchResultFactory = songSearchResultFactory viewController.listSearchResultFactory = listSearchResultFactory + viewController.searchGlobalScrollState = searchGlobalScrollState viewController.reactor = reactor return viewController } + override public func pageboyViewController( + _ pageboyViewController: PageboyViewController, + didScrollToPageAt index: TabmanViewController.PageIndex, + direction: PageboyViewController.NavigationDirection, + animated: Bool + ) { + searchGlobalScrollState.expand() + } + deinit { DEBUG_LOG("❌ \(Self.self)") } @@ -56,7 +68,7 @@ public final class AfterSearchViewController: TabmanViewController, ViewControll extension AfterSearchViewController { func bindState(reacotr: AfterSearchReactor) { let currentState = reacotr.state.share() - #warning("첫 진입 시 text가 안내려옴 바인딩이 안되서") + currentState.map(\.text) .filter { !$0.isEmpty } .distinctUntilChanged() @@ -141,10 +153,8 @@ extension AfterSearchViewController: PageboyViewControllerDataSource, TMBarDataS extension AfterSearchViewController { func scrollToTop() { - #warning("구현이 끝난 후 연결") -// let current: Int = self.currentIndex ?? 0 -// let searchContent = self.viewControllers.compactMap { $0 as? AfterSearchContentViewController } -// guard searchContent.count > current else { return } -// searchContent[current].scrollToTop() + let current: Int = self.currentIndex ?? 0 + + searchGlobalScrollState.scrollToTop(page: current == 0 ? .song : .list) } } diff --git a/Projects/Features/SearchFeature/Sources/ViewControllers/ListSearchResultViewController.swift b/Projects/Features/SearchFeature/Sources/ViewControllers/ListSearchResultViewController.swift index 8335e5601..6df4c0856 100644 --- a/Projects/Features/SearchFeature/Sources/ViewControllers/ListSearchResultViewController.swift +++ b/Projects/Features/SearchFeature/Sources/ViewControllers/ListSearchResultViewController.swift @@ -20,6 +20,8 @@ final class ListSearchResultViewController: BaseReactorViewController, ContainerViewType, +final class SearchViewController: BaseStoryboardReactorViewController, ContainerViewType, EqualHandleTappedType { private enum Font { static let headerFontSize: CGFloat = 16 @@ -30,14 +29,21 @@ internal final class SearchViewController: BaseStoryboardReactorViewController SearchViewController { let viewController = SearchViewController.viewController(storyBoardName: "Search", bundle: Bundle.module) @@ -58,6 +65,7 @@ internal final class SearchViewController: BaseStoryboardReactorViewController 0 && offsetY > absoluteTop + let isScrollingUp = scrollDiff < 0 && offsetY < absoluteBottom + + guard offsetY < absoluteBottom else { return } + var newHeight = owner.searchHeaderViewTopConstraint.constant + + if isScrollingDown { + newHeight = max(owner.maxHeight, owner.searchHeaderViewTopConstraint.constant - abs(scrollDiff)) + } else if isScrollingUp { + if offsetY <= abs(owner.maxHeight) { + newHeight = min(0, owner.searchHeaderViewTopConstraint.constant + abs(scrollDiff)) + } + } + + if newHeight != owner.searchHeaderViewTopConstraint.constant { + owner.searchHeaderViewTopConstraint.constant = newHeight + owner.updateHeader() + } + owner.view.layoutIfNeeded() + owner.previousScrollOffset = offsetY + }) + .disposed(by: disposeBag) + + searchGlobalScrollState.expandSearchHeaderObservable + .skip(1) + .asDriver(onErrorJustReturn: ()) + .drive(with: self, onNext: { owner, _ in + let openAmount = owner.searchHeaderViewTopConstraint.constant + abs(owner.maxHeight) + let percentage = openAmount / abs(owner.maxHeight) + + guard percentage != 1 else { return } + owner.searchHeaderContentView.alpha = 1 + owner.searchHeaderViewTopConstraint.constant = 0 + owner.searchHeaderView.backgroundColor = .white + }) + .disposed(by: disposeBag) + } + + private func updateHeader() { + // percentage == 1 ? 확장 : 축소 + let openAmount = self.searchHeaderViewTopConstraint.constant + abs(self.maxHeight) + let percentage = openAmount / abs(self.maxHeight) + self.searchHeaderContentView.alpha = percentage + self.searchHeaderView.backgroundColor = percentage == 0 ? + DesignSystemAsset.BlueGrayColor.gray100.color : .white } override public func bindState(reactor: SearchReactor) { diff --git a/Projects/Features/SearchFeature/Sources/ViewControllers/SongSearchResultViewController.swift b/Projects/Features/SearchFeature/Sources/ViewControllers/SongSearchResultViewController.swift index b79b13fcf..b6c262470 100644 --- a/Projects/Features/SearchFeature/Sources/ViewControllers/SongSearchResultViewController.swift +++ b/Projects/Features/SearchFeature/Sources/ViewControllers/SongSearchResultViewController.swift @@ -11,7 +11,6 @@ import Then import UIKit import Utility -#warning("오버 스크롤 처리") final class SongSearchResultViewController: BaseReactorViewController, SongCartViewType { var songCartView: SongCartView! @@ -21,6 +20,8 @@ final class SongSearchResultViewController: BaseReactorViewController bounds.height + } } diff --git a/Projects/UsertInterfaces/DesignSystem/Sources/WMFontSystem.swift b/Projects/UsertInterfaces/DesignSystem/Sources/WMFontSystem.swift index 83fc3f805..d6cd8d723 100644 --- a/Projects/UsertInterfaces/DesignSystem/Sources/WMFontSystem.swift +++ b/Projects/UsertInterfaces/DesignSystem/Sources/WMFontSystem.swift @@ -25,7 +25,7 @@ public extension UIFont { case t4(weight: WMFontWeight = .medium) /// size: 16 height: 24 case t5(weight: WMFontWeight = .medium) - /// size: 14 height: 24 + /// size: 14 height: 22 case t6(weight: WMFontWeight = .medium) /// size: 14 height: 20 case t6_1(weight: WMFontWeight = .medium) @@ -98,7 +98,7 @@ private extension UIFont.WMFontSystem { case .t3: return 32 case .t4: return 28 case .t5: return 24 - case .t6: return 20 + case .t6: return 22 case .t6_1: return 20 case .t7, .sc7: return 18 case .t7_1: return 14