diff --git a/Poppool/Poppool/Presentation/Admin/AdminBottomSheetViewController.swift b/Poppool/Poppool/Presentation/Admin/AdminBottomSheetViewController.swift index 933f5c26..dbee91f3 100644 --- a/Poppool/Poppool/Presentation/Admin/AdminBottomSheetViewController.swift +++ b/Poppool/Poppool/Presentation/Admin/AdminBottomSheetViewController.swift @@ -21,7 +21,7 @@ final class AdminBottomSheetViewController: BaseViewController, View { // MARK: - Initialization init(reactor: AdminBottomSheetReactor) { - super.init() // BaseViewController의 init() 호출 + super.init() self.reactor = reactor } required init?(coder: NSCoder) { @@ -43,9 +43,7 @@ final class AdminBottomSheetViewController: BaseViewController, View { view.backgroundColor = .clear Logger.log(message: "초기 뷰 계층:", category: .debug) -// print(view.value(forKey: "recursiveDescription") ?? "") - // mainView 설정 및 추가 view.addSubview(mainView) mainView.isUserInteractionEnabled = true mainView.containerView.isUserInteractionEnabled = true @@ -60,16 +58,14 @@ final class AdminBottomSheetViewController: BaseViewController, View { } Logger.log(message: "mainView 추가 후 계층:", category: .debug) -// print(view.value(forKey: "recursiveDescription") ?? "") - // dimmedView 설정 및 추가 dimmedView.backgroundColor = .black.withAlphaComponent(0.4) dimmedView.alpha = 0 - dimmedView.isUserInteractionEnabled = true + dimmedView.isUserInteractionEnabled = false let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dimmedViewTapped)) dimmedView.addGestureRecognizer(tapGesture) - tapGesture.cancelsTouchesInView = false // 터치 이벤트가 다른 뷰로 전달되도록 설정 + tapGesture.cancelsTouchesInView = true // 터치 이벤트가 다른 뷰로 전달되도록 설정 view.insertSubview(dimmedView, belowSubview: mainView) dimmedView.snp.makeConstraints { make in @@ -77,7 +73,6 @@ final class AdminBottomSheetViewController: BaseViewController, View { } Logger.log(message: "최종 뷰 계층:", category: .debug) -// print(view.value(forKey: "recursiveDescription") ?? "") } private func setupCollectionView() { @@ -89,12 +84,8 @@ final class AdminBottomSheetViewController: BaseViewController, View { // MARK: - Binding func bind(reactor: Reactor) { - // Action mainView.segmentedControl.rx.selectedSegmentIndex .do(onNext: { index in - Logger.log(message: "세그먼트 변경 시도: \(index)", category: .event) - Logger.log(message: "View 상태: \(self.view.window != nil)", category: .debug) - Logger.log(message: "MainView 상태: \(self.mainView.window != nil)", category: .debug) }) .map { Reactor.Action.segmentChanged($0) } .bind(to: reactor.action) @@ -118,7 +109,6 @@ final class AdminBottomSheetViewController: BaseViewController, View { .bind(to: reactor.action) .disposed(by: disposeBag) - // State reactor.state.map { state in let items = state.activeSegment == 0 ? state.statusOptions : diff --git a/Poppool/Poppool/Presentation/Admin/AdminReactor.swift b/Poppool/Poppool/Presentation/Admin/AdminReactor.swift index a2e737b0..1d0f2cbd 100644 --- a/Poppool/Poppool/Presentation/Admin/AdminReactor.swift +++ b/Poppool/Poppool/Presentation/Admin/AdminReactor.swift @@ -65,7 +65,6 @@ final class AdminReactor: Reactor { return .just(.navigateToRegister(true)) case let .tapEditButton(storeId): - // ✅ 선택한 storeId에 해당하는 데이터를 찾아서 Mutation으로 전달 if let store = currentState.storeList.first(where: { $0.id == storeId }) { return .just(.navigateToEdit(store)) } else { diff --git a/Poppool/Poppool/Presentation/Admin/AdminViewController.swift b/Poppool/Poppool/Presentation/Admin/AdminViewController.swift index a827f0f1..d475ab45 100644 --- a/Poppool/Poppool/Presentation/Admin/AdminViewController.swift +++ b/Poppool/Poppool/Presentation/Admin/AdminViewController.swift @@ -210,7 +210,6 @@ final class AdminViewController: BaseViewController, View { // MARK: - Reactor Binding func bind(reactor: Reactor) { - // Search input mainView.searchInput.rx.text.orEmpty .distinctUntilChanged() .debounce(.milliseconds(300), scheduler: MainScheduler.instance) diff --git a/Poppool/Poppool/Presentation/Admin/Data/DTO/GetAdminPopUpStoreListResponseDTO.swift b/Poppool/Poppool/Presentation/Admin/Data/DTO/GetAdminPopUpStoreListResponseDTO.swift index 242fcad0..8be73a2b 100644 --- a/Poppool/Poppool/Presentation/Admin/Data/DTO/GetAdminPopUpStoreListResponseDTO.swift +++ b/Poppool/Poppool/Presentation/Admin/Data/DTO/GetAdminPopUpStoreListResponseDTO.swift @@ -9,8 +9,8 @@ struct StoreListRequestDTO: Encodable { enum CodingKeys: String, CodingKey { case query - case page = "pageable.page" - case size = "pageable.size" + case page + case size } } @@ -31,7 +31,6 @@ struct CreatePopUpStoreRequestDTO: Encodable { let markerSnippet: String let startDateBeforeEndDate: Bool - /// - 만약 대표 이미지 URL(mainImageUrl)이 비어 있지 않다면 배너 이미지로 간주하여 bannerYn을 true로 설정합니다. init(name: String, categoryId: Int64, desc: String, diff --git a/Poppool/Poppool/Presentation/Map/MapSearchInput.swift b/Poppool/Poppool/Presentation/Map/MapSearchInput.swift index 4e1ed32f..2e725823 100644 --- a/Poppool/Poppool/Presentation/Map/MapSearchInput.swift +++ b/Poppool/Poppool/Presentation/Map/MapSearchInput.swift @@ -53,7 +53,8 @@ final class MapSearchInput: UIView, View { init() { super.init(frame: .zero) setupLayout() - setupActions() +// setupActions() +// setupGesture() searchTextField.isEnabled = false @@ -79,12 +80,12 @@ final class MapSearchInput: UIView, View { func bind(reactor: MapReactor) { // 엔터 키로 검색 실행 - searchTextField.rx.controlEvent(.editingDidEndOnExit) - .withLatestFrom(searchTextField.rx.text.orEmpty) - .bind { query in - reactor.action.onNext(.searchTapped(query)) - } - .disposed(by: disposeBag) +// searchTextField.rx.controlEvent(.editingDidEndOnExit) +// .withLatestFrom(searchTextField.rx.text.orEmpty) +// .bind { query in +// reactor.action.onNext(.searchTapped(query)) +// } +// .disposed(by: disposeBag) // 텍스트 입력 로그 searchTextField.rx.text.orEmpty @@ -138,7 +139,6 @@ private extension MapSearchInput { } func setupActions() { - // 텍스트 필드 액션 설정 searchTextField.rx.controlEvent(.editingDidEndOnExit) .withLatestFrom(searchTextField.rx.text.orEmpty) .subscribe(onNext: { [weak self] query in diff --git a/Poppool/Poppool/Presentation/Map/MapViewController.swift b/Poppool/Poppool/Presentation/Map/MapViewController.swift index 864aa39e..7b651734 100644 --- a/Poppool/Poppool/Presentation/Map/MapViewController.swift +++ b/Poppool/Poppool/Presentation/Map/MapViewController.swift @@ -32,7 +32,6 @@ class MapViewController: BaseViewController, View { // MARK: - Properties var currentCarouselStores: [MapPopUpStore] = [] private var markerDictionary: [Int64: GMSMarker] = [:] - // 개별 마커와 클러스터 마커를 각각 관리하는 딕셔너리 private var individualMarkerDictionary: [Int64: GMSMarker] = [:] private var clusterMarkerDictionary: [String: GMSMarker] = [:] private let popUpAPIUseCase = PopUpAPIUseCaseImpl( @@ -71,18 +70,123 @@ class MapViewController: BaseViewController, View { self.filterChipsTopY = frameInView.minY } } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.tabBarController?.tabBar.isHidden = false + } override func viewDidLoad() { super.viewDidLoad() setUp() checkLocationAuthorization() + locationManager.delegate = self locationManager.requestWhenInUseAuthorization() locationManager.desiredAccuracy = kCLLocationAccuracyBest mainView.mapView.isMyLocationEnabled = true // mainView.mapView.settings.myLocationButton = true - +// carouselView.onCardScrolled = { [weak self] pageIndex in +// guard let self = self, +// pageIndex >= 0, +// pageIndex < self.currentCarouselStores.count else { return } +// +// let store = self.currentCarouselStores[pageIndex] +// +// // 1. 현재 선택된 스토어의 마커 찾기 +// if let existingMarker = self.findMarkerForStore(for: store) { +// // 1-1. 이전 마커 선택 해제 +// if let previousMarker = self.currentMarker, previousMarker != existingMarker { +// let markerView = MapMarker() +// let storeCount = (previousMarker.userData as? [MapPopUpStore])?.count ?? 1 +// markerView.injection(with: .init( +// isSelected: false, +// isCluster: false, +// count: storeCount +// )) +// previousMarker.iconView = markerView +// } +// +// // 1-2. 새 마커 선택 상태로 변경 +// let markerView = MapMarker() +// let storeCount = (existingMarker.userData as? [MapPopUpStore])?.count ?? 1 +// markerView.injection(with: .init( +// isSelected: true, +// isCluster: false, +// count: storeCount +// )) +// existingMarker.iconView = markerView +// self.currentMarker = existingMarker +// +// // 2. 툴팁 업데이트 +// if let storeArray = existingMarker.userData as? [MapPopUpStore] { +// // 마커에 연결된 스토어가 2개 이상인 경우에만 툴팁 표시 +// if storeArray.count > 1 { +// // 기존 툴팁이 없거나 다른 마커의 툴팁인 경우 새로 생성 +// if self.currentTooltipView == nil || self.currentTooltipCoordinate != existingMarker.position { +// // 기존 툴팁 제거 +// self.currentTooltipView?.removeFromSuperview() +// +// let tooltipView = MarkerTooltipView() +// tooltipView.configure(with: storeArray) +// tooltipView.onStoreSelected = { [weak self] index in +// guard let self = self, +// index < storeArray.count else { return } +// let selectedStore = storeArray[index] +// if let carouselIndex = self.currentCarouselStores.firstIndex(where: { $0.id == selectedStore.id }) { +// self.carouselView.scrollToCard(index: carouselIndex) +// } +// } +// +// // 마커 위치 기준으로 툴팁 위치 설정 +// let markerPoint = self.mainView.mapView.projection.point(for: existingMarker.position) +// let markerHeight = (existingMarker.iconView as? MapMarker)?.imageView.frame.height ?? 32 +// tooltipView.frame = CGRect( +// x: markerPoint.x - 10, +// y: markerPoint.y - markerHeight - tooltipView.frame.height - 10, +// width: tooltipView.frame.width, +// height: tooltipView.frame.height +// ) +// +// self.mainView.addSubview(tooltipView) +// self.currentTooltipView = tooltipView +// self.currentTooltipStores = storeArray +// self.currentTooltipCoordinate = existingMarker.position +// } +// +// // 툴팁의 선택된 행 업데이트 +// if let tooltipIndex = storeArray.firstIndex(where: { $0.id == store.id }) { +// (self.currentTooltipView as? MarkerTooltipView)?.selectStore(at: tooltipIndex) +// } +// } else { +// // 단일 마커의 경우 툴팁 제거 +// self.currentTooltipView?.removeFromSuperview() +// self.currentTooltipView = nil +// self.currentTooltipStores = [] +// self.currentTooltipCoordinate = nil +// } +// } +// +// // 3. 지도 중심 이동 및 애니메이션 +// let camera = GMSCameraUpdate.setTarget(existingMarker.position) +// self.mainView.mapView.animate(with: camera) +// +// // 4. 리액터에 선택된 스토어 상태 업데이트 +// self.reactor?.action.onNext(.didSelectItem(store)) +// +// // 5. 로깅 +// Logger.log( +// message: """ +// 캐러셀 카드 변경: +// - 페이지 인덱스: \(pageIndex) +// - 선택된 스토어: \(store.name) +// - 마커 위치: (\(existingMarker.position.latitude), \(existingMarker.position.longitude)) +// - 툴팁 표시 여부: \(self.currentTooltipView != nil) +// """, +// category: .debug +// ) +// } +// } carouselView.onCardScrolled = { [weak self] pageIndex in guard let self = self, @@ -90,94 +194,94 @@ class MapViewController: BaseViewController, View { pageIndex < self.currentCarouselStores.count else { return } let store = self.currentCarouselStores[pageIndex] + print("🔄 캐러셀 스크롤") + print("📱 현재 선택된 스토어: \(store.name) (index: \(pageIndex))") + print("🎠 전체 캐러셀 스토어: \(self.currentCarouselStores.map { $0.name })") - // 1. 기존 마커 찾아서 처리 - if let existingMarker = self.findMarkerForStore(for: store) { - // 이전 마커 선택 해제 - if let previousMarker = self.currentMarker, previousMarker != existingMarker { - let markerView = MapMarker() - let storeCount = (previousMarker.userData as? [MapPopUpStore])?.count ?? 1 - markerView.injection(with: .init( - isSelected: false, - isCluster: false, - count: storeCount - )) - previousMarker.iconView = markerView - } - // 새 마커 선택 + // 1. 현재 마커의 스토어 배열 가져오기 + if let existingMarker = self.currentMarker, + let markerStores = existingMarker.userData as? [MapPopUpStore] { + print("📍 마커에 저장된 스토어: \(markerStores.map { $0.name })") + + + // 2. 선택된 마커 업데이트 let markerView = MapMarker() - let storeCount = (existingMarker.userData as? [MapPopUpStore])?.count ?? 1 markerView.injection(with: .init( isSelected: true, isCluster: false, - count: storeCount + count: markerStores.count )) existingMarker.iconView = markerView - self.currentMarker = existingMarker - - // 2. 툴팁 업데이트 (마이크로 클러스터의 경우) - if let storeArray = existingMarker.userData as? [MapPopUpStore], - storeArray.count > 1 { - // 기존 툴팁이 없으면 새로 생성 - if self.currentTooltipView == nil { - let tooltipView = MarkerTooltipView() - tooltipView.configure(with: storeArray) - tooltipView.onStoreSelected = { [weak self] index in - guard let self = self, - index < storeArray.count else { return } - let selectedStore = storeArray[index] - if let carouselIndex = self.currentCarouselStores.firstIndex(where: { $0.id == selectedStore.id }) { - self.carouselView.scrollToCard(index: carouselIndex) - } - } - // 마커 위치 기준으로 툴팁 위치 설정 - let markerPoint = self.mainView.mapView.projection.point(for: existingMarker.position) - let markerHeight = (existingMarker.iconView as? MapMarker)?.imageView.frame.height ?? 32 - tooltipView.frame = CGRect( - x: markerPoint.x - 10, - y: markerPoint.y - markerHeight - tooltipView.frame.height - 10, - width: tooltipView.frame.width, - height: tooltipView.frame.height - ) + // 3. 현재 스토어가 마커의 스토어 배열에 포함되어 있는지 확인 + if let tooltipIndex = markerStores.firstIndex(where: { $0.id == store.id }) { + print("🗨️ 툴팁에서 선택될 인덱스: \(tooltipIndex)") + print("🗨️ 현재 툴팁 스토어: \(self.currentTooltipStores.map { $0.name })") - self.mainView.addSubview(tooltipView) - self.currentTooltipView = tooltipView - self.currentTooltipStores = storeArray - self.currentTooltipCoordinate = existingMarker.position - } - // 툴팁의 선택된 행 업데이트 - if let tooltipIndex = storeArray.firstIndex(where: { $0.id == store.id }) { - (self.currentTooltipView as? MarkerTooltipView)?.selectStore(at: tooltipIndex) + // 4. 툴팁이 존재하고 마커의 스토어가 2개 이상인 경우에만 툴팁 업데이트 + if markerStores.count > 1, + let tooltipView = self.currentTooltipView as? MarkerTooltipView { + tooltipView.selectStore(at: tooltipIndex) } - } else { - // 단일 마커의 경우 툴팁 제거 - self.currentTooltipView?.removeFromSuperview() - self.currentTooltipView = nil - self.currentTooltipStores = [] - self.currentTooltipCoordinate = nil } - - // 3. 지도 중심 이동 (선택적) - let camera = GMSCameraUpdate.setTarget(existingMarker.position) - self.mainView.mapView.animate(with: camera) } } + if let reactor = self.reactor { +// bind(reactor: reactor) // ㅅㅂ 뭐지 ? + bindViewport(reactor: reactor) + reactor.action.onNext(.fetchCategories) + } + } + private func configureTooltip(for marker: GMSMarker, stores: [MapPopUpStore]) { + // 기존 툴팁 제거 + self.currentTooltipView?.removeFromSuperview() - if let reactor = self.reactor { - bind(reactor: reactor) - bindViewport(reactor: reactor) + // 새 툴팁 생성 + let tooltipView = MarkerTooltipView() + tooltipView.configure(with: stores) - reactor.action.onNext(.fetchCategories) + tooltipView.onStoreSelected = { [weak self] index in + guard let self = self, + index < stores.count else { return } + // 1) 선택된 스토어 가져오기 + let selectedStore = stores[index] + + // 2) 캐러셀에서 해당 스토어의 인덱스 찾기 + if let carouselIndex = self.currentCarouselStores.firstIndex(where: { $0.id == selectedStore.id }) { + // 3) 캐러셀 스크롤 + self.carouselView.scrollToCard(index: carouselIndex) + } } + // 툴팁 위치 설정 + let markerPoint = self.mainView.mapView.projection.point(for: marker.position) + let markerHeight = (marker.iconView as? MapMarker)?.imageView.frame.height ?? 32 + tooltipView.frame = CGRect( + x: markerPoint.x - tooltipView.frame.width/2, + y: markerPoint.y - markerHeight - tooltipView.frame.height - 10, + width: tooltipView.frame.width, + height: tooltipView.frame.height + ) + + // 툴팁 표시 + self.mainView.addSubview(tooltipView) + // 상태 저장 + self.currentTooltipView = tooltipView + self.currentTooltipStores = stores + self.currentTooltipCoordinate = marker.position + + // 현재 캐러셀이 보여주고 있는 스토어에 해당하는 툴팁 항목 선택 + if let currentStore = self.currentCarouselStores.first, + let tooltipIndex = stores.firstIndex(where: { $0.id == currentStore.id }) { + tooltipView.selectStore(at: tooltipIndex) + } } @@ -222,7 +326,7 @@ class MapViewController: BaseViewController, View { // setupMarker() } - private let defaultZoomLevel: Float = 15.0 // 기본 줌 레벨 + private let defaultZoomLevel: Float = 15.0 private func setupPanAndSwipeGestures() { @@ -384,12 +488,14 @@ class MapViewController: BaseViewController, View { mainView.searchInput.rx.tapGesture() .when(.recognized) - .take(1) + .throttle(.milliseconds(500), scheduler: MainScheduler.instance) .withUnretained(self) .subscribe(onNext: { owner, _ in + print("tapGesture fired - push 시작") let searchMainVC = SearchMainController() searchMainVC.reactor = SearchMainReactor() owner.navigationController?.pushViewController(searchMainVC, animated: true) + print("pushViewController 호출 완료") }) .disposed(by: disposeBag) @@ -435,20 +541,20 @@ class MapViewController: BaseViewController, View { - reactor.state.map { $0.searchResults.isEmpty } - .distinctUntilChanged() - .skip(1) // 초기값 스킵 - .observe(on: MainScheduler.instance) - .bind { [weak self] isEmpty in - guard let self = self else { return } - if isEmpty { - self.showAlert( - title: "검색 결과 없음", - message: "검색 결과가 없습니다. 다른 키워드로 검색해보세요." - ) - } - } - .disposed(by: disposeBag) +// reactor.state.map { $0.searchResults.isEmpty } +// .distinctUntilChanged() +// .skip(1) // 초기값 스킵 +// .observe(on: MainScheduler.instance) +// .bind { [weak self] isEmpty in +// guard let self = self else { return } +// if isEmpty { +// self.showAlert( +// title: "검색 결과 없음", +// message: "검색 결과가 없습니다. 다른 키워드로 검색해보세요." +// ) +// } +// } +// .disposed(by: disposeBag) } @@ -534,7 +640,6 @@ class MapViewController: BaseViewController, View { targetState = .bottom } - // 최종 상태에 따라 애니메이션 적용 animateToState(targetState) } @@ -1484,3 +1589,8 @@ extension Reactive where Base: GMSMapView { return proxy.idleAtPositionSubject.asObservable() } } +extension CLLocationCoordinate2D: Equatable { + public static func == (lhs: CLLocationCoordinate2D, rhs: CLLocationCoordinate2D) -> Bool { + return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude + } +} diff --git a/Poppool/Poppool/Presentation/Map/MarkerTooltipView.swift b/Poppool/Poppool/Presentation/Map/MarkerTooltipView.swift index 093cbca5..88ba1492 100644 --- a/Poppool/Poppool/Presentation/Map/MarkerTooltipView.swift +++ b/Poppool/Poppool/Presentation/Map/MarkerTooltipView.swift @@ -29,9 +29,9 @@ final class MarkerTooltipView: UIView, UIGestureRecognizerDelegate { // MARK: - Initialization override init(frame: CGRect) { super.init(frame: frame) - self.frame.size = CGSize(width: 200, height: 0) // 고정된 너비, 동적 높이 + self.frame.size = CGSize(width: 200, height: 100) // 임시 높이로 시작 setupLayout() - setupGestures() +// setupGestures() } required init?(coder: NSCoder) { @@ -41,15 +41,11 @@ final class MarkerTooltipView: UIView, UIGestureRecognizerDelegate { // MARK: - Setup private func setupLayout() { addSubview(containerView) - self.isUserInteractionEnabled = true - containerView.isUserInteractionEnabled = true - stackView.isUserInteractionEnabled = true - containerView.addSubview(stackView) containerView.snp.makeConstraints { make in - make.width.equalTo(200) make.edges.equalToSuperview() + make.width.equalTo(200) } stackView.snp.makeConstraints { make in @@ -71,70 +67,82 @@ final class MarkerTooltipView: UIView, UIGestureRecognizerDelegate { // MARK: - Configuration func configure(with stores: [MapPopUpStore]) { + // 기존 뷰 제거 stackView.arrangedSubviews.forEach { $0.removeFromSuperview() } - // stores 배열을 역순으로 처리 - let reversedStores = stores.reversed() - - for (index, store) in reversedStores.enumerated() { - let rowContainer = UIView() - rowContainer.isUserInteractionEnabled = true - rowContainer.tag = stores.count - 1 - index // 인덱스도 반대로 설정 - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleRowTap(_:))) - rowContainer.addGestureRecognizer(tapGesture) - - let horizontalStack = UIStackView() - horizontalStack.axis = .horizontal - horizontalStack.spacing = 8 - horizontalStack.alignment = .center - - let bulletView = UIView() - bulletView.backgroundColor = .clear - bulletView.layer.cornerRadius = 4 - bulletView.snp.makeConstraints { make in - make.width.height.equalTo(8) - } + print("🗨️ 툴팁 구성") + print("📋 입력받은 스토어: \(stores.map { $0.name })") - let label = UILabel() - label.text = store.name - label.font = .systemFont(ofSize: 12) - label.textColor = .blu500 - label.numberOfLines = 1 - - horizontalStack.addArrangedSubview(bulletView) - horizontalStack.addArrangedSubview(label) - - rowContainer.addSubview(horizontalStack) - horizontalStack.snp.makeConstraints { make in - make.edges.equalToSuperview() - } + // stores 배열 순서대로 처리 + for (index, store) in stores.enumerated() { + let rowContainer = createRow(for: store, at: index) stackView.addArrangedSubview(rowContainer) + // 구분선 추가 (마지막 아이템 제외) if index < stores.count - 1 { - let separator = UIView() - separator.backgroundColor = .g50 - separator.snp.makeConstraints { make in - make.height.equalTo(1) - } + let separator = createSeparator() stackView.addArrangedSubview(separator) } } - selectStore(at: 0) - - // 레이아웃 업데이트 - setNeedsLayout() layoutIfNeeded() // 컨텐츠 크기에 맞게 높이 조정 let height = stackView.systemLayoutSizeFitting( CGSize(width: 200, height: UIView.layoutFittingCompressedSize.height) - ).height + 24 // 24는 상하 패딩 + ).height + 24 + // frame 높이 업데이트 self.frame.size.height = height } + private func createRow(for store: MapPopUpStore, at index: Int) -> UIView { + let rowContainer = UIView() + rowContainer.isUserInteractionEnabled = true + rowContainer.tag = index // 정순 인덱스 사용 + + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleRowTap(_:))) + rowContainer.addGestureRecognizer(tapGesture) + + let horizontalStack = UIStackView() + horizontalStack.axis = .horizontal + horizontalStack.spacing = 8 + horizontalStack.alignment = .center + + let bulletView = UIView() + bulletView.backgroundColor = .clear + bulletView.layer.cornerRadius = 4 + bulletView.snp.makeConstraints { make in + make.width.height.equalTo(8) + } + + let label = UILabel() + label.text = store.name + label.font = .systemFont(ofSize: 12) + label.textColor = .blu500 + label.numberOfLines = 1 + + horizontalStack.addArrangedSubview(bulletView) + horizontalStack.addArrangedSubview(label) + + rowContainer.addSubview(horizontalStack) + horizontalStack.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + return rowContainer + } + + private func createSeparator() -> UIView { + let separator = UIView() + separator.backgroundColor = .g50 + separator.snp.makeConstraints { make in + make.height.equalTo(1) + } + return separator + } + // MARK: - Gesture Handling @objc private func handleContainerTap(_ gesture: UITapGestureRecognizer) { gesture.cancelsTouchesInView = true @@ -148,8 +156,10 @@ final class MarkerTooltipView: UIView, UIGestureRecognizerDelegate { guard let row = gesture.view else { return } let index = row.tag - gesture.cancelsTouchesInView = true + print("🗨️ 툴팁 탭") + print("👆 탭된 인덱스: \(index)") + gesture.cancelsTouchesInView = true selectStore(at: index) onStoreSelected?(index) } @@ -166,6 +176,7 @@ final class MarkerTooltipView: UIView, UIGestureRecognizerDelegate { // MARK: - Store Selection func selectStore(at index: Int) { + // 모든 행을 순회하면서 해당 인덱스의 행만 선택 상태로 변경 for case let row as UIView in stackView.arrangedSubviews { guard let horizontalStack = row.subviews.first as? UIStackView, horizontalStack.arrangedSubviews.count >= 2, @@ -174,9 +185,11 @@ final class MarkerTooltipView: UIView, UIGestureRecognizerDelegate { else { continue } if row.tag == index { + // 선택된 행 label.font = .boldSystemFont(ofSize: 12) bulletView.backgroundColor = .jd500 } else { + // 선택되지 않은 행 label.font = .systemFont(ofSize: 12) bulletView.backgroundColor = .clear } diff --git a/Poppool/Poppool/Presentation/Map/StoreListView/StoreListViewController.swift b/Poppool/Poppool/Presentation/Map/StoreListView/StoreListViewController.swift index 8cd6a84a..baa76a8d 100644 --- a/Poppool/Poppool/Presentation/Map/StoreListView/StoreListViewController.swift +++ b/Poppool/Poppool/Presentation/Map/StoreListView/StoreListViewController.swift @@ -132,7 +132,6 @@ final class StoreListViewController: UIViewController, View { // .bind(to: reactor.action) // .disposed(by: disposeBag) - // 5) **필터 상태** 관찰 → 바텀시트 열기/닫기 reactor.state.map { $0.activeFilterType } .distinctUntilChanged() .subscribe(onNext: { [weak self] filterType in diff --git a/Poppool/Poppool/Presentation/Scene/Search/Main/SearchMainController.swift b/Poppool/Poppool/Presentation/Scene/Search/Main/SearchMainController.swift index 2d14fa8a..9a16aa29 100644 --- a/Poppool/Poppool/Presentation/Scene/Search/Main/SearchMainController.swift +++ b/Poppool/Poppool/Presentation/Scene/Search/Main/SearchMainController.swift @@ -96,26 +96,40 @@ extension SearchMainController { }) .disposed(by: disposeBag) +// mainView.searchTextField.rx.controlEvent(.editingDidEndOnExit) +// .withUnretained(self) +// .map { (owner, _) in +// Reactor.Action.returnSearchKeyWord(text: owner.mainView.searchTextField.text ) +// } +// .bind(to: reactor.action) +// .disposed(by: disposeBag) +// +// mainView.searchTextField.rx.controlEvent(.editingDidEndOnExit) +// .withUnretained(self) +// .subscribe(onNext: { (owner, _) in +// if let text = owner.mainView.searchTextField.text { +// if !text.isEmpty { +// owner.scrollToPage(.at(index: 1), animated: false) +// } +// } +// owner.beforeController.reactor?.action.onNext(.returnSearchKeyword(text: owner.mainView.searchTextField.text)) +// }) +// .disposed(by: disposeBag) mainView.searchTextField.rx.controlEvent(.editingDidEndOnExit) + .withLatestFrom(mainView.searchTextField.rx.text.orEmpty) .withUnretained(self) - .map { (owner, _) in - Reactor.Action.returnSearchKeyWord(text: owner.mainView.searchTextField.text ) - } - .bind(to: reactor.action) - .disposed(by: disposeBag) - - mainView.searchTextField.rx.controlEvent(.editingDidEndOnExit) - .withUnretained(self) - .subscribe(onNext: { (owner, _) in - if let text = owner.mainView.searchTextField.text { - if !text.isEmpty { - owner.scrollToPage(.at(index: 1), animated: false) - } + .subscribe(onNext: { (owner, query) in + owner.view.endEditing(true) + // 텍스트가 비어있지 않으면 페이지 전환 + if !query.isEmpty { + owner.scrollToPage(.at(index: 1), animated: false) } - owner.beforeController.reactor?.action.onNext(.returnSearchKeyword(text: owner.mainView.searchTextField.text)) + owner.reactor?.action.onNext(.returnSearchKeyWord(text: query)) + owner.beforeController.reactor?.action.onNext(.returnSearchKeyword(text: query)) }) .disposed(by: disposeBag) - + + mainView.searchTextField.rx.text .withUnretained(self) .subscribe(onNext: { (owner, text) in