diff --git a/Poppool/PresentationLayer/Presentation/Presentation/Scene/Map/FillterSheetView/BalloonBackgroundView.swift b/Poppool/PresentationLayer/Presentation/Presentation/Scene/Map/FillterSheetView/BalloonBackgroundView.swift index e5cc9467..fff36989 100644 --- a/Poppool/PresentationLayer/Presentation/Presentation/Scene/Map/FillterSheetView/BalloonBackgroundView.swift +++ b/Poppool/PresentationLayer/Presentation/Presentation/Scene/Map/FillterSheetView/BalloonBackgroundView.swift @@ -34,7 +34,7 @@ final class BalloonBackgroundView: UIView { let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) collectionView.backgroundColor = .clear collectionView.isScrollEnabled = false - collectionView.register(BalloonChipCell.self, forCellWithReuseIdentifier: BalloonChipCell.identifier) + collectionView.register(BalloonChipCell.self, forCellWithReuseIdentifier: BalloonChipCell.identifiers) return collectionView }() @@ -289,7 +289,7 @@ extension BalloonBackgroundView: UICollectionViewDataSource { cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell( - withReuseIdentifier: BalloonChipCell.identifier, + withReuseIdentifier: BalloonChipCell.identifiers, for: indexPath ) as? BalloonChipCell, let input = tagSection?.inputDataList[indexPath.item] diff --git a/Poppool/PresentationLayer/Presentation/Presentation/Scene/Map/FillterSheetView/BalloonChipCell.swift b/Poppool/PresentationLayer/Presentation/Presentation/Scene/Map/FillterSheetView/BalloonChipCell.swift index 81b1f4b5..558af950 100644 --- a/Poppool/PresentationLayer/Presentation/Presentation/Scene/Map/FillterSheetView/BalloonChipCell.swift +++ b/Poppool/PresentationLayer/Presentation/Presentation/Scene/Map/FillterSheetView/BalloonChipCell.swift @@ -1,88 +1,131 @@ -import SnapKit import UIKit +import Infrastructure + +import SnapKit +import Then + final class BalloonChipCell: UICollectionViewCell { - static let identifier = "BalloonChipCell" - - private let button: PPButton = { - let button = PPButton( - style: .secondary, - text: "", - font: .korFont(style: .medium, size: 11), - cornerRadius: 15 - ) - - button.titleLabel?.lineBreakMode = .byTruncatingTail - button.titleLabel?.adjustsFontSizeToFitWidth = false - return button - }() - - override init(frame: CGRect) { - super.init(frame: frame) - contentView.addSubview(button) - setupLayout() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func setupLayout() { - button.snp.makeConstraints { make in - make.edges.equalToSuperview() - } - } - - func configure(with title: String, isSelected: Bool) { - button.setTitle(title, for: .normal) - if isSelected { - let checkImage = UIImage(named: "icon_check_white")?.withRenderingMode(.alwaysOriginal) - let resizedImage = checkImage?.resize(to: CGSize(width: 16, height: 16)) - button.setImage(resizedImage, for: .normal) - button.semanticContentAttribute = .forceRightToLeft - button.imageEdgeInsets = UIEdgeInsets(top: 0, left: 2, bottom: 0, right: 0) - button.contentEdgeInsets = UIEdgeInsets(top: 4, left: 10, bottom: 6, right: 12) - button.setBackgroundColor(.blu500, for: .normal) - button.setTitleColor(.white, for: .normal) - button.layer.borderWidth = 0 - button.titleLabel?.font = .korFont(style: .bold, size: 11) - - } else { - button.setImage(nil, for: .normal) - button.semanticContentAttribute = .unspecified - button.imageEdgeInsets = .zero - button.contentEdgeInsets = UIEdgeInsets(top: 4, left: 12, bottom: 6, right: 12) - button.setBackgroundColor(.white, for: .normal) - button.setTitleColor(.g400, for: .normal) - button.layer.borderWidth = 1 - button.layer.borderColor = UIColor.g200.cgColor - button.titleLabel?.font = .korFont(style: .medium, size: 11) - - } - } - - private var currentAction: UIAction? - - var buttonAction: (() -> Void)? { - didSet { - if let oldAction = currentAction { - button.removeAction(oldAction, for: .touchUpInside) - } - - let action = UIAction { [weak self] _ in - self?.buttonAction?() - } - button.addAction(action, for: .touchUpInside) - currentAction = action - } - } + + private enum Constant { + static let verticalInset: CGFloat = 6 + static let selectedLeftInset: CGFloat = 10 + static let normalLeftInset: CGFloat = 12 + static let rightInset: CGFloat = 12 + static let checkIconSize: CGSize = .init(width: 16, height: 16) + static let baselineOffset: CGFloat = -1 + static let fontSize: CGFloat = 11 + } + + private let button = PPButton( + style: .secondary, + text: "", + font: .korFont(style: .medium, size: Constant.fontSize), + cornerRadius: 15 + ).then { + $0.titleLabel?.lineBreakMode = .byTruncatingTail + $0.titleLabel?.adjustsFontSizeToFitWidth = false + } + + private var currentAction: UIAction? + + override init(frame: CGRect) { + super.init(frame: frame) + contentView.addSubview(button) + setupLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupLayout() { + button.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } + + func configure(with title: String, isSelected: Bool) { + let attributedTitle = NSMutableAttributedString(string: title).then { + $0.addAttribute( + .baselineOffset, + value: Constant.baselineOffset, + range: NSRange(location: .zero, length: $0.length) + ) + } + + if isSelected { + let checkImage = UIImage(named: "icon_check_white")?.withRenderingMode(.alwaysOriginal).resize(to: Constant.checkIconSize) + + button.then { + $0.setImage(checkImage, for: .normal) + $0.semanticContentAttribute = .forceRightToLeft + $0.imageEdgeInsets = .init(top: .zero, left: 1, bottom: .zero, right: .zero) + $0.contentEdgeInsets = .init( + top: Constant.verticalInset, + left: Constant.selectedLeftInset, + bottom: Constant.verticalInset, + right: Constant.rightInset + ) + $0.setBackgroundColor(.blu500, for: .normal) + $0.setTitleColor(.white, for: .normal) + $0.layer.borderWidth = .zero + } + + attributedTitle.addAttribute( + .font, + value: UIFont.korFont(style: .bold, size: Constant.fontSize)!, + range: NSRange(location: .zero, length: attributedTitle.length) + ) + } else { + button.then { + $0.setImage(nil, for: .normal) + $0.semanticContentAttribute = .unspecified + $0.imageEdgeInsets = .zero + $0.contentEdgeInsets = .init( + top: Constant.verticalInset, + left: Constant.normalLeftInset, + bottom: Constant.verticalInset, + right: Constant.rightInset + ) + $0.setBackgroundColor(.white, for: .normal) + $0.setTitleColor(.g400, for: .normal) + $0.layer.borderWidth = 1 + $0.layer.borderColor = UIColor.g200.cgColor + } + + attributedTitle.addAttribute( + .font, + value: UIFont.korFont(style: .medium, size: Constant.fontSize)!, + range: NSRange(location: .zero, length: attributedTitle.length) + ) + } + + self.button.setAttributedTitle(attributedTitle, for: .normal) + } + + var buttonAction: (() -> Void)? { + didSet { + if let oldAction = currentAction { + self.button.removeAction(oldAction, for: .touchUpInside) + } + + let action = UIAction { [weak self] _ in + guard let self = self else { return } + self.buttonAction?() + } + + self.button.addAction(action, for: .touchUpInside) + self.currentAction = action + } + } } extension UIImage { - func resize(to size: CGSize) -> UIImage? { - UIGraphicsBeginImageContextWithOptions(size, false, 0.0) - defer { UIGraphicsEndImageContext() } - draw(in: CGRect(origin: .zero, size: size)) - return UIGraphicsGetImageFromCurrentImageContext() - } + func resize(to size: CGSize) -> UIImage? { + UIGraphicsBeginImageContextWithOptions(size, false, 0.0) + defer { UIGraphicsEndImageContext() } + self.draw(in: CGRect(origin: .zero, size: size)) + return UIGraphicsGetImageFromCurrentImageContext() + } } diff --git a/Poppool/PresentationLayer/Presentation/Presentation/Scene/Map/FillterSheetView/FilterBottomSheetView.swift b/Poppool/PresentationLayer/Presentation/Presentation/Scene/Map/FillterSheetView/FilterBottomSheetView.swift index 0ecff3df..a1b0dca4 100644 --- a/Poppool/PresentationLayer/Presentation/Presentation/Scene/Map/FillterSheetView/FilterBottomSheetView.swift +++ b/Poppool/PresentationLayer/Presentation/Presentation/Scene/Map/FillterSheetView/FilterBottomSheetView.swift @@ -1,39 +1,43 @@ -import SnapKit import UIKit +import SnapKit +import Then + final class FilterBottomSheetView: UIView { - // MARK: - UI Components - private let containerView: UIView = { - let view = UIView() - view.backgroundColor = .white - view.layer.cornerRadius = 20 - view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] - view.layer.masksToBounds = true - return view - }() + private enum Constant { + static let cornerRadius: CGFloat = 20 + static let topInset: CGFloat = 30 + static let horizontalInset: CGFloat = 16 + static let segmentedTopOffset: CGFloat = 16 + static let scrollViewHeight: CGFloat = 36 + static let categoryHeight: CGFloat = 160 + static let balloonTopOffset: CGFloat = 16 + static let filterChipsHeight: CGFloat = 80 + static let buttonStackSpacing: CGFloat = 12 + static let buttonStackHeight: CGFloat = 52 + } - let titleLabel: PPLabel = { - let label = PPLabel(style: .bold, fontSize: 18, text: "보기 옵션을 선택해주세요") - label.textColor = .black - return label - }() + private let containerView = UIView().then { + $0.backgroundColor = .white + $0.layer.cornerRadius = Constant.cornerRadius + $0.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] + $0.layer.masksToBounds = true + } - let closeButton: UIButton = { - let button = UIButton(type: .system) - button.setImage(UIImage(named: "icon_xmark"), for: .normal) - button.tintColor = .black - return button - }() + let titleLabel = PPLabel(style: .bold, fontSize: 18, text: "보기 옵션을 선택해주세요").then { + $0.textColor = .black + } - let segmentedControl: PPSegmentedControl = { - return PPSegmentedControl(type: .tab, segments: ["지역", "카테고리"], selectedSegmentIndex: 0) - }() + let closeButton = UIButton(type: .system).then { + $0.setImage(UIImage(named: "icon_xmark"), for: .normal) + $0.tintColor = .black + } - let locationScrollView: UIScrollView = { - let scrollView = UIScrollView() - scrollView.showsHorizontalScrollIndicator = false - return scrollView - }() + let segmentedControl = PPSegmentedControl(type: .tab, segments: ["지역", "카테고리"], selectedSegmentIndex: 0) + + let locationScrollView = UIScrollView().then { + $0.showsHorizontalScrollIndicator = false + } let locationContentView = UIView() var categoryHeightConstraint: Constraint? @@ -42,13 +46,13 @@ final class FilterBottomSheetView: UIView { let layout = UICollectionViewCompositionalLayout { section, _ in let itemSize = NSCollectionLayoutSize( widthDimension: .estimated(26), - heightDimension: .estimated(36) + heightDimension: .estimated(Constant.scrollViewHeight) ) let item = NSCollectionLayoutItem(layoutSize: itemSize) let groupSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1.0), - heightDimension: .estimated(36) + heightDimension: .estimated(Constant.scrollViewHeight) ) let group = NSCollectionLayoutGroup.horizontal( layoutSize: groupSize, @@ -67,35 +71,25 @@ final class FilterBottomSheetView: UIView { return section } - - let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) - collectionView.backgroundColor = .clear - collectionView.isScrollEnabled = false - collectionView.register(TagSectionCell.self, forCellWithReuseIdentifier: TagSectionCell.identifiers) - return collectionView + return UICollectionView(frame: .zero, collectionViewLayout: layout).then { + $0.backgroundColor = .clear + $0.isScrollEnabled = false + $0.register(TagSectionCell.self, forCellWithReuseIdentifier: TagSectionCell.identifiers) + } }() let balloonBackgroundView = BalloonBackgroundView() - let resetButton: PPButton = { - return PPButton(style: .secondary, text: "초기화") - }() + let resetButton = PPButton(style: .secondary, text: "초기화") + let saveButton = PPButton(style: .primary, text: "옵션저장", disabledText: "옵션저장") - let saveButton: PPButton = { - return PPButton(style: .primary, text: "옵션저장", disabledText: "옵션저장") - }() - - private let buttonStack: UIStackView = { - let stack = UIStackView() - stack.axis = .horizontal - stack.spacing = 12 - stack.distribution = .fillEqually - return stack - }() + private let buttonStack = UIStackView().then { + $0.axis = .horizontal + $0.spacing = Constant.buttonStackSpacing + $0.distribution = .fillEqually + } - let filterChipsView: FilterChipsView = { - return FilterChipsView() - }() + let filterChipsView = FilterChipsView() private var balloonHeightConstraint: Constraint? @@ -142,25 +136,25 @@ final class FilterBottomSheetView: UIView { } titleLabel.snp.makeConstraints { make in - make.leading.equalToSuperview().offset(16) - make.top.equalToSuperview().offset(30) + make.leading.equalToSuperview().offset(Constant.horizontalInset) + make.top.equalToSuperview().offset(Constant.topInset) } closeButton.snp.makeConstraints { make in - make.trailing.equalToSuperview().inset(16) + make.trailing.equalToSuperview().inset(Constant.horizontalInset) make.centerY.equalTo(titleLabel) make.size.equalTo(24) } segmentedControl.snp.makeConstraints { make in - make.top.equalTo(titleLabel.snp.bottom).offset(16) + make.top.equalTo(titleLabel.snp.bottom).offset(Constant.segmentedTopOffset) make.leading.trailing.equalToSuperview() } locationScrollView.snp.makeConstraints { make in make.top.equalTo(segmentedControl.snp.bottom).offset(20) make.leading.trailing.equalToSuperview() - make.height.equalTo(36) + make.height.equalTo(Constant.scrollViewHeight) } locationContentView.snp.makeConstraints { make in @@ -169,28 +163,28 @@ final class FilterBottomSheetView: UIView { } categoryCollectionView.snp.makeConstraints { make in - make.top.equalTo(segmentedControl.snp.bottom).offset(16) + make.top.equalTo(segmentedControl.snp.bottom).offset(Constant.segmentedTopOffset) make.leading.trailing.equalToSuperview() - categoryHeightConstraint = make.height.equalTo(160).constraint + categoryHeightConstraint = make.height.equalTo(Constant.categoryHeight).constraint } balloonBackgroundView.snp.makeConstraints { make in - make.top.equalTo(locationScrollView.snp.bottom).offset(16) - make.leading.trailing.equalToSuperview().inset(16) + make.top.equalTo(locationScrollView.snp.bottom).offset(Constant.balloonTopOffset) + make.leading.trailing.equalToSuperview().inset(Constant.horizontalInset) balloonHeightConstraint = make.height.equalTo(0).constraint } filterChipsView.snp.makeConstraints { make in make.top.equalTo(balloonBackgroundView.snp.bottom).offset(24) - make.leading.trailing.equalToSuperview().inset(16) - make.height.equalTo(80) + make.leading.trailing.equalToSuperview().inset(Constant.horizontalInset) + make.height.equalTo(Constant.filterChipsHeight) } buttonStack.snp.makeConstraints { make in make.top.equalTo(filterChipsView.snp.bottom).offset(24) - make.leading.trailing.equalToSuperview().inset(16) + make.leading.trailing.equalToSuperview().inset(Constant.horizontalInset) make.bottom.equalToSuperview().inset(40) - make.height.equalTo(52) + make.height.equalTo(Constant.buttonStackHeight) } } @@ -305,41 +299,43 @@ final class FilterBottomSheetView: UIView { let button = PPButton( style: .secondary, text: title, - font: .korFont(style: .medium, size: 13), + font: .korFont(style: isSelected ? .bold : .medium, size: 13), cornerRadius: 18 ) - button.setBackgroundColor(.w100, for: .normal) - button.setTitleColor(.g400, for: .normal) + button.setBackgroundColor(isSelected ? .blu500 : .w100, for: .normal) + button.setTitleColor(isSelected ? .w100 : .g400, for: .normal) + button.layer.borderWidth = isSelected ? 0 : 1 button.layer.borderColor = UIColor.g200.cgColor - button.layer.borderWidth = 1 - - if isSelected { - button.setBackgroundColor(.blu500, for: .normal) - button.setTitleColor(.w100, for: .normal) - button.layer.borderWidth = 0 - } - button.contentEdgeInsets = UIEdgeInsets(top: 9, left: 16, bottom: 9, right: 16) + button.titleLabel?.setLineHeightText( + text: title, + font: .korFont(style: isSelected ? .bold : .medium, size: 13), + lineHeight: 1.2 + ) + return button } func updateMainLocationSelection(_ index: Int) { locationContentView.subviews.enumerated().forEach { (idx, view) in guard let button = view as? PPButton else { return } - if idx == index { - button.setBackgroundColor(.blu500, for: .normal) - button.setTitleColor(.w100, for: .normal) - button.layer.borderWidth = 0 - button.titleLabel?.font = .korFont(style: .bold, size: 13) - } else { - button.setBackgroundColor(.w100, for: .normal) - button.setTitleColor(.g400, for: .normal) - button.layer.borderColor = UIColor.g200.cgColor - button.titleLabel?.font = .korFont(style: .medium, size: 13) - button.layer.borderWidth = 1 - } + + let isSelected = idx == index + button.setBackgroundColor(isSelected ? .blu500 : .w100, for: .normal) + button.setTitleColor(isSelected ? .w100 : .g400, for: .normal) + button.layer.borderWidth = isSelected ? 0 : 1 + button.layer.borderColor = UIColor.g200.cgColor + + // 버튼의 타이틀 레이블에 setLineHeightText 적용 + let title = button.currentTitle ?? "" + button.titleLabel?.setLineHeightText( + text: title, + font: .korFont(style: isSelected ? .bold : .medium, size: 13), + lineHeight: 1.2 + ) } + } func updateBalloonHeight(isHidden: Bool, dynamicHeight: CGFloat = 160) { diff --git a/Poppool/PresentationLayer/Presentation/Presentation/Scene/Map/MapView/MapViewController.swift b/Poppool/PresentationLayer/Presentation/Presentation/Scene/Map/MapView/MapViewController.swift index 6e6923b8..88ce0efa 100644 --- a/Poppool/PresentationLayer/Presentation/Presentation/Scene/Map/MapView/MapViewController.swift +++ b/Poppool/PresentationLayer/Presentation/Presentation/Scene/Map/MapView/MapViewController.swift @@ -161,12 +161,10 @@ class MapViewController: BaseViewController, View, CLLocationManagerDelegate, NM self.configureTooltip(for: markerToFocus, stores: storeArray) } - // 툴팁에서 선택된 스토어 업데이트 if let tooltipIndex = storeArray.firstIndex(where: { $0.id == store.id }) { (self.currentTooltipView as? MarkerTooltipView)?.selectStore(at: tooltipIndex) } } else { - // 단일 마커면 기존 툴팁 제거 self.currentTooltipView?.removeFromSuperview() self.currentTooltipView = nil }