From aa8103ecda2b2489cc819b4778952bd6e5889e7c Mon Sep 17 00:00:00 2001
From: Hwang Sang Hwan <137605270+Hrepay@users.noreply.github.com>
Date: Sat, 18 Jul 2026 17:50:58 +0900
Subject: [PATCH 1/8] =?UTF-8?q?[#432]=20=EC=B9=B4=EC=B9=B4=EC=98=A4?=
=?UTF-8?q?=EB=A7=B5/=EB=84=A4=EC=9D=B4=EB=B2=84=EC=A7=80=EB=8F=84=20?=
=?UTF-8?q?=EB=A1=9C=EA=B3=A0=20=EC=97=90=EC=85=8B=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../KakaoMapLogo.imageset/Contents.json | 12 ++++++++++++
.../KakaoMapLogo.imageset/KakaoMapLogo.svg | 9 +++++++++
.../NaverMapLogo.imageset/Contents.json | 12 ++++++++++++
.../NaverMapLogo.imageset/NaverMapLogo.svg | 9 +++++++++
4 files changed, 42 insertions(+)
create mode 100644 EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/KakaoMapLogo.imageset/Contents.json
create mode 100644 EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/KakaoMapLogo.imageset/KakaoMapLogo.svg
create mode 100644 EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/NaverMapLogo.imageset/Contents.json
create mode 100644 EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/NaverMapLogo.imageset/NaverMapLogo.svg
diff --git a/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/KakaoMapLogo.imageset/Contents.json b/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/KakaoMapLogo.imageset/Contents.json
new file mode 100644
index 00000000..f51699e6
--- /dev/null
+++ b/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/KakaoMapLogo.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "KakaoMapLogo.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/KakaoMapLogo.imageset/KakaoMapLogo.svg b/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/KakaoMapLogo.imageset/KakaoMapLogo.svg
new file mode 100644
index 00000000..2e71c881
--- /dev/null
+++ b/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/KakaoMapLogo.imageset/KakaoMapLogo.svg
@@ -0,0 +1,9 @@
+
diff --git a/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/NaverMapLogo.imageset/Contents.json b/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/NaverMapLogo.imageset/Contents.json
new file mode 100644
index 00000000..1a28006b
--- /dev/null
+++ b/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/NaverMapLogo.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "NaverMapLogo.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/NaverMapLogo.imageset/NaverMapLogo.svg b/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/NaverMapLogo.imageset/NaverMapLogo.svg
new file mode 100644
index 00000000..e3747a9c
--- /dev/null
+++ b/EATSSUDesign/EATSSUDesign/Resources/Images.xcassets/NaverMapLogo.imageset/NaverMapLogo.svg
@@ -0,0 +1,9 @@
+
From ebd09a9bc89f8549ecb5771767719a9fb301b9be Mon Sep 17 00:00:00 2001
From: Hwang Sang Hwan <137605270+Hrepay@users.noreply.github.com>
Date: Sat, 18 Jul 2026 17:51:06 +0900
Subject: [PATCH 2/8] =?UTF-8?q?[#432]=20=EC=A0=9C=ED=9C=B4=20=EC=83=81?=
=?UTF-8?q?=EC=84=B8=20=EC=8B=9C=ED=8A=B8=20=EB=A0=88=EC=9D=B4=EC=95=84?=
=?UTF-8?q?=EC=9B=83=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EC=B9=B4=EC=B9=B4?=
=?UTF-8?q?=EC=98=A4=EB=A7=B5/=EB=84=A4=EC=9D=B4=EB=B2=84=EC=A7=80?=
=?UTF-8?q?=EB=8F=84=20=EC=97=B0=EA=B2=B0=20=EB=B2=84=ED=8A=BC=20=EC=B6=94?=
=?UTF-8?q?=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Resources/en.lproj/Localizable.strings | 4 +
.../Resources/ja.lproj/Localizable.strings | 4 +
.../Resources/ko.lproj/Localizable.strings | 4 +
.../Resources/vi.lproj/Localizable.strings | 4 +
.../MainMapViewController+Marker.swift | 6 +-
...PartnershipDetailSheetViewController.swift | 133 +++++++++++++++---
.../Sources/Utility/Literal/TextLiteral.swift | 10 ++
EATSSU/Project.swift | 2 +
8 files changed, 140 insertions(+), 27 deletions(-)
diff --git a/EATSSU/App/Resources/en.lproj/Localizable.strings b/EATSSU/App/Resources/en.lproj/Localizable.strings
index 6f0aefc5..10db5443 100644
--- a/EATSSU/App/Resources/en.lproj/Localizable.strings
+++ b/EATSSU/App/Resources/en.lproj/Localizable.strings
@@ -192,6 +192,10 @@
"map.pub" = "Pub";
/// PartnershipDetailSheetVC - "학과 정보 없음"
"map.noDepartmentInfo" = "No Department Info";
+
+"map.kakaoMap" = "KakaoMap";
+
+"map.naverMap" = "Naver Map";
/// MainMapVC+Location - "위치 권한 필요"
"map.needLocationAuth" = "Location Permission Required";
/// MainMapVC+Location - "지도에서 내 위치를 바로 확인하고, 현재 위치 주변의 제휴점들을 손쉽게 찾아볼 수 있도록 위치 권한을 허용해 주세요."
diff --git a/EATSSU/App/Resources/ja.lproj/Localizable.strings b/EATSSU/App/Resources/ja.lproj/Localizable.strings
index 373573ec..f09b2801 100644
--- a/EATSSU/App/Resources/ja.lproj/Localizable.strings
+++ b/EATSSU/App/Resources/ja.lproj/Localizable.strings
@@ -193,6 +193,10 @@
"map.pub" = "居酒屋";
/// PartnershipDetailSheetVC - "학과 정보 없음"
"map.noDepartmentInfo" = "学科情報なし";
+
+"map.kakaoMap" = "カカオマップ";
+
+"map.naverMap" = "NAVERマップ";
/// MainMapVC+Location - "위치 권한 필요"
"map.needLocationAuth" = "位置情報権限が必要です";
/// MainMapVC+Location - "지도에서 내 위치를 바로 확인하고, 현재 위치 주변의 제휴점들을 손쉽게 찾아볼 수 있도록 위치 권한을 허용해 주세요."
diff --git a/EATSSU/App/Resources/ko.lproj/Localizable.strings b/EATSSU/App/Resources/ko.lproj/Localizable.strings
index d95c634a..574bcced 100644
--- a/EATSSU/App/Resources/ko.lproj/Localizable.strings
+++ b/EATSSU/App/Resources/ko.lproj/Localizable.strings
@@ -193,6 +193,10 @@
"map.pub" = "주점";
/// PartnershipDetailSheetVC - "학과 정보 없음"
"map.noDepartmentInfo" = "학과 정보 없음";
+
+"map.kakaoMap" = "카카오맵";
+
+"map.naverMap" = "네이버지도";
/// MainMapVC+Location - "위치 권한 필요"
"map.needLocationAuth" = "위치 권한 필요";
/// MainMapVC+Location - "지도에서 내 위치를 바로 확인하고, 현재 위치 주변의 제휴점들을 손쉽게 찾아볼 수 있도록 위치 권한을 허용해 주세요."
diff --git a/EATSSU/App/Resources/vi.lproj/Localizable.strings b/EATSSU/App/Resources/vi.lproj/Localizable.strings
index 5edb302b..db539310 100644
--- a/EATSSU/App/Resources/vi.lproj/Localizable.strings
+++ b/EATSSU/App/Resources/vi.lproj/Localizable.strings
@@ -193,6 +193,10 @@
"map.pub" = "Quán nhậu";
/// PartnershipDetailSheetVC - "학과 정보 없음"
"map.noDepartmentInfo" = "Không có thông tin ngành";
+
+"map.kakaoMap" = "KakaoMap";
+
+"map.naverMap" = "Naver Map";
/// MainMapVC+Location - "위치 권한 필요"
"map.needLocationAuth" = "Cần quyền truy cập vị trí";
/// MainMapVC+Location - "지도에서 내 위치를 바로 확인하고, 현재 위치 주변의 제휴점들을 손쉽게 찾아볼 수 있도록 위치 권한을 허용해 주세요."
diff --git a/EATSSU/App/Sources/Presentation/Map/ViewController/MainMapViewController+Marker.swift b/EATSSU/App/Sources/Presentation/Map/ViewController/MainMapViewController+Marker.swift
index b2bf9b31..cc490f8d 100644
--- a/EATSSU/App/Sources/Presentation/Map/ViewController/MainMapViewController+Marker.swift
+++ b/EATSSU/App/Sources/Presentation/Map/ViewController/MainMapViewController+Marker.swift
@@ -155,11 +155,7 @@ extension MainMapViewController {
partnerId: partnership.partnershipInfos.first?.id ?? -1
)
- let detailVC = PartnershipDetailSheetViewController(
- storeName: partnership.storeName,
- restaurantType: partnership.restaurantType,
- partnershipInfos: partnership.partnershipInfos
- )
+ let detailVC = PartnershipDetailSheetViewController(partnership: partnership)
// 뷰가 로드된 후 높이 계산
detailVC.loadViewIfNeeded()
diff --git a/EATSSU/App/Sources/Presentation/Map/ViewController/PartnershipDetailSheetViewController.swift b/EATSSU/App/Sources/Presentation/Map/ViewController/PartnershipDetailSheetViewController.swift
index 1ecf307c..0cc0cb53 100644
--- a/EATSSU/App/Sources/Presentation/Map/ViewController/PartnershipDetailSheetViewController.swift
+++ b/EATSSU/App/Sources/Presentation/Map/ViewController/PartnershipDetailSheetViewController.swift
@@ -15,9 +15,7 @@ final class PartnershipDetailSheetViewController: BaseViewController {
// MARK: - Properties
- private let storeName: String
- private let restaurantType: String
- private let partnershipInfos: [PartnershipInfoDTO]
+ private let partnership: PartnershipDTO
// MARK: - UI Components
@@ -26,13 +24,15 @@ final class PartnershipDetailSheetViewController: BaseViewController {
private let typeIconImageView = UIImageView()
private let typeTextLabel = UILabel()
private let infoListStackView = UIStackView()
+ private let mapButtonBarView = UIView()
+ private let kakaoMapButton = UIButton()
+ private let naverMapButton = UIButton()
+ private let mapButtonDivider = UIView()
// MARK: - Init
- init(storeName: String, restaurantType: String, partnershipInfos: [PartnershipInfoDTO]) {
- self.storeName = storeName
- self.restaurantType = restaurantType
- self.partnershipInfos = partnershipInfos
+ init(partnership: PartnershipDTO) {
+ self.partnership = partnership
super.init(nibName: nil, bundle: nil)
modalPresentationStyle = .pageSheet
@@ -47,13 +47,13 @@ final class PartnershipDetailSheetViewController: BaseViewController {
override func configureUI() {
view.backgroundColor = .white
- storeNameLabel.font = EATSSUDesignFontFamily.Pretendard.bold.font(size: 16)
+ storeNameLabel.font = .header2
storeNameLabel.textColor = .label
typeIconImageView.contentMode = .scaleAspectFit
typeIconImageView.snp.makeConstraints { $0.width.height.equalTo(18) }
- typeTextLabel.font = EATSSUDesignFontFamily.Pretendard.regular.font(size: 10)
+ typeTextLabel.font = .body2
typeTextLabel.textColor = .gray
typeStackView.axis = .horizontal
@@ -69,7 +69,25 @@ final class PartnershipDetailSheetViewController: BaseViewController {
infoListStackView.isLayoutMarginsRelativeArrangement = true
infoListStackView.layoutMargins = .init(top: 10, left: 0, bottom: 10, right: 0)
- [storeNameLabel, typeStackView, infoListStackView].forEach {
+ kakaoMapButton.configuration = makeMapButtonConfiguration(
+ image: EATSSUDesignAsset.Images.kakaoMapLogo.image,
+ title: TextLiteral.Map.kakaoMap
+ )
+ kakaoMapButton.addTarget(self, action: #selector(kakaoMapButtonTapped), for: .touchUpInside)
+
+ naverMapButton.configuration = makeMapButtonConfiguration(
+ image: EATSSUDesignAsset.Images.naverMapLogo.image,
+ title: TextLiteral.Map.naverMap
+ )
+ naverMapButton.addTarget(self, action: #selector(naverMapButtonTapped), for: .touchUpInside)
+
+ mapButtonDivider.backgroundColor = EATSSUDesignColors.Color.gray200
+
+ [kakaoMapButton, naverMapButton, mapButtonDivider].forEach {
+ mapButtonBarView.addSubview($0)
+ }
+
+ [storeNameLabel, typeStackView, infoListStackView, mapButtonBarView].forEach {
view.addSubview($0)
}
}
@@ -89,6 +107,28 @@ final class PartnershipDetailSheetViewController: BaseViewController {
$0.top.equalTo(typeStackView.snp.bottom).offset(10)
$0.leading.trailing.equalToSuperview().inset(24)
}
+
+ mapButtonBarView.snp.makeConstraints {
+ $0.top.equalTo(infoListStackView.snp.bottom).offset(6)
+ $0.leading.trailing.equalToSuperview()
+ $0.height.equalTo(56)
+ }
+
+ kakaoMapButton.snp.makeConstraints {
+ $0.leading.top.bottom.equalToSuperview()
+ $0.trailing.equalTo(mapButtonBarView.snp.centerX)
+ }
+
+ naverMapButton.snp.makeConstraints {
+ $0.trailing.top.bottom.equalToSuperview()
+ $0.leading.equalTo(mapButtonBarView.snp.centerX)
+ }
+
+ mapButtonDivider.snp.makeConstraints {
+ $0.center.equalToSuperview()
+ $0.width.equalTo(1)
+ $0.height.equalTo(16)
+ }
}
// MARK: - Data Config
@@ -106,9 +146,9 @@ final class PartnershipDetailSheetViewController: BaseViewController {
/// 매장 정보와 제휴 내용을 화면에 반영
private func configureData() {
- storeNameLabel.text = storeName
+ storeNameLabel.text = partnership.storeName
- switch restaurantType {
+ switch partnership.restaurantType {
case "RESTAURANT":
typeIconImageView.image = EATSSUDesignAsset.Images.restaurantPin.image
typeTextLabel.text = TextLiteral.Map.restaurant
@@ -120,11 +160,11 @@ final class PartnershipDetailSheetViewController: BaseViewController {
typeTextLabel.text = TextLiteral.Map.pub
default:
typeIconImageView.image = EATSSUDesignAsset.Images.restaurantPin.image
- typeTextLabel.text = restaurantType
+ typeTextLabel.text = partnership.restaurantType
}
- for (index, info) in partnershipInfos.enumerated() {
- let isLast = index == partnershipInfos.count - 1
+ for (index, info) in partnership.partnershipInfos.enumerated() {
+ let isLast = index == partnership.partnershipInfos.count - 1
let card = makeInfoCard(info: info, isLast: isLast)
infoListStackView.addArrangedSubview(card)
}
@@ -135,11 +175,24 @@ final class PartnershipDetailSheetViewController: BaseViewController {
/// 제휴 content 갯수에 따라 유동적으로 Height 측정
func calculatePreferredHeight() -> CGFloat {
view.layoutIfNeeded()
- let contentHeight = infoListStackView.frame.maxY
- let bottomPadding: CGFloat = view.safeAreaInsets.bottom + 20
-
+ let contentHeight = mapButtonBarView.frame.maxY
+ let bottomPadding: CGFloat = view.safeAreaInsets.bottom + 10
+
return contentHeight + bottomPadding
}
+
+ /// 지도 앱 이동 버튼 공통 Configuration 생성
+ private func makeMapButtonConfiguration(image: UIImage, title: String) -> UIButton.Configuration {
+ var config = UIButton.Configuration.plain()
+ config.image = image
+ config.imagePadding = 6
+ config.baseForegroundColor = .label
+ config.attributedTitle = AttributedString(
+ title,
+ attributes: AttributeContainer([.font: UIFont.body2])
+ )
+ return config
+ }
/// 제휴 정보 카드 뷰 생성
private func makeInfoCard(info: PartnershipInfoDTO, isLast: Bool) -> UIView {
@@ -155,21 +208,21 @@ final class PartnershipDetailSheetViewController: BaseViewController {
let dateRange = (fullText as NSString).range(of: "\(start) ~ \(end)")
attrText.addAttributes([
- .font: EATSSUDesignFontFamily.Pretendard.medium.font(size: 14),
+ .font: UIFont.body2,
.foregroundColor: UIColor.label
], range: collegeRange)
attrText.addAttributes([
- .font: EATSSUDesignFontFamily.Pretendard.regular.font(size: 10),
+ .font: UIFont.caption2,
.foregroundColor: EATSSUDesignColors.Color.gray700,
- .baselineOffset: +2
+ .baselineOffset: +1
], range: dateRange)
let titleDateLabel = UILabel()
titleDateLabel.attributedText = attrText
let descriptionLabel = UILabel()
- descriptionLabel.font = EATSSUDesignFontFamily.Pretendard.regular.font(size: 12)
+ descriptionLabel.font = .body3
descriptionLabel.textColor = EATSSUDesignColors.Color.gray700
descriptionLabel.numberOfLines = 0
descriptionLabel.text = info.description
@@ -198,4 +251,40 @@ final class PartnershipDetailSheetViewController: BaseViewController {
return paddedContainer
}
+ // MARK: - Actions
+
+ /// 카카오맵 앱으로 이동 (미설치 시 카카오맵 웹)
+ @objc private func kakaoMapButtonTapped() {
+ let appURL = URL(string: "kakaomap://look?p=\(partnership.latitude),\(partnership.longitude)")
+ let encodedName = partnership.storeName
+ .addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
+ let webURL = URL(
+ string: "https://map.kakao.com/link/map/\(encodedName),\(partnership.latitude),\(partnership.longitude)"
+ )
+ openMapApp(appURL: appURL, fallbackURL: webURL)
+ }
+
+ /// 네이버지도 앱으로 이동 (미설치 시 네이버지도 웹 검색)
+ @objc private func naverMapButtonTapped() {
+ var components = URLComponents(string: "nmap://place")
+ components?.queryItems = [
+ URLQueryItem(name: "lat", value: "\(partnership.latitude)"),
+ URLQueryItem(name: "lng", value: "\(partnership.longitude)"),
+ URLQueryItem(name: "name", value: partnership.storeName),
+ URLQueryItem(name: "appname", value: Bundle.main.bundleIdentifier ?? "")
+ ]
+ let encodedName = partnership.storeName
+ .addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? ""
+ let webURL = URL(string: "https://map.naver.com/p/search/\(encodedName)")
+ openMapApp(appURL: components?.url, fallbackURL: webURL)
+ }
+
+ private func openMapApp(appURL: URL?, fallbackURL: URL?) {
+ if let appURL, UIApplication.shared.canOpenURL(appURL) {
+ UIApplication.shared.open(appURL)
+ } else if let fallbackURL {
+ UIApplication.shared.open(fallbackURL)
+ }
+ }
+
}
diff --git a/EATSSU/App/Sources/Utility/Literal/TextLiteral.swift b/EATSSU/App/Sources/Utility/Literal/TextLiteral.swift
index 3e95247b..c3a69de5 100644
--- a/EATSSU/App/Sources/Utility/Literal/TextLiteral.swift
+++ b/EATSSU/App/Sources/Utility/Literal/TextLiteral.swift
@@ -493,6 +493,16 @@ enum TextLiteral {
Localization.localized("map.noDepartmentInfo", fallback: "학과 정보 없음")
}
+ /// PartnershipDetailSheetVC - "카카오맵"
+ static var kakaoMap: String {
+ Localization.localized("map.kakaoMap", fallback: "카카오맵")
+ }
+
+ /// PartnershipDetailSheetVC - "네이버지도"
+ static var naverMap: String {
+ Localization.localized("map.naverMap", fallback: "네이버지도")
+ }
+
/// MainMapVC+Location - "위치 권한 필요"
static var needLocationAuth: String {
Localization.localized("map.needLocationAuth", fallback: "위치 권한 필요")
diff --git a/EATSSU/Project.swift b/EATSSU/Project.swift
index 7f55a697..a650e84d 100644
--- a/EATSSU/Project.swift
+++ b/EATSSU/Project.swift
@@ -20,8 +20,10 @@ let appInfoPlist: InfoPlist = .extendingDefault(with: [
"LSApplicationQueriesSchemes": [
"kakaokompassauth",
"kakaolink",
+ "kakaomap",
"kakaoplus",
"kakaotalk",
+ "nmap",
],
"NSLocationWhenInUseUsageDescription": "지도에서 내 위치를 바로 확인하고, 현재 위치 주변의 제휴점들을 손쉽게 찾아볼 수 있도록 위치 권한을 허용해 주세요.",
"UIApplicationSceneManifest": [
From 50e738b6fc11dbdb319b897d86bc102cb0dc719f Mon Sep 17 00:00:00 2001
From: Hwang Sang Hwan <137605270+Hrepay@users.noreply.github.com>
Date: Sat, 18 Jul 2026 17:51:11 +0900
Subject: [PATCH 3/8] =?UTF-8?q?[#432]=20=EC=8B=A4=EA=B8=B0=EA=B8=B0=20?=
=?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=9A=A9=20=EC=A0=9C=ED=9C=B4=20Moc?=
=?UTF-8?q?k=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Map/PartnershipMockData.swift | 97 +++++++++++++++++++
.../MainMapViewController+Network.swift | 8 ++
2 files changed, 105 insertions(+)
create mode 100644 EATSSU/App/Sources/Presentation/Map/PartnershipMockData.swift
diff --git a/EATSSU/App/Sources/Presentation/Map/PartnershipMockData.swift b/EATSSU/App/Sources/Presentation/Map/PartnershipMockData.swift
new file mode 100644
index 00000000..9c718b5d
--- /dev/null
+++ b/EATSSU/App/Sources/Presentation/Map/PartnershipMockData.swift
@@ -0,0 +1,97 @@
+//
+// PartnershipMockData.swift
+// EATSSU
+//
+// Created by 황상환 on 7/18/26.
+//
+
+#if DEBUG
+import Foundation
+
+/// 서버에 제휴 데이터가 없을 때 실기기 테스트용 Mock 데이터 (DEBUG 빌드 전용)
+enum PartnershipMockData {
+
+ /// 숭실대 주변 가게 좌표 기반 샘플
+ static let samples: [PartnershipDTO] = [
+ PartnershipDTO(
+ storeName: "맘스터치 숭실대점",
+ longitude: 126.9550,
+ latitude: 37.4959,
+ restaurantType: "RESTAURANT",
+ partnershipInfos: [
+ PartnershipInfoDTO(
+ id: -1,
+ collegeName: "IT대학",
+ departmentName: nil,
+ likeCount: 0,
+ isLiked: false,
+ description: "학생증 인증하면 음료수 1개 증정",
+ startDate: "2025.09.03",
+ endDate: "2025.12.18",
+ periodType: .normal
+ ),
+ PartnershipInfoDTO(
+ id: -2,
+ collegeName: nil,
+ departmentName: "컴퓨터학부",
+ likeCount: 0,
+ isLiked: false,
+ description: "학생증 인증하고 카카오페이 결제 시 10% 할인",
+ startDate: "2025.09.01",
+ endDate: "2025.12.31",
+ periodType: .normal
+ )
+ ]
+ ),
+ PartnershipDTO(
+ storeName: "스타벅스 숭실대입구역점",
+ longitude: 126.9538,
+ latitude: 37.4964,
+ restaurantType: "CAFE",
+ partnershipInfos: [
+ PartnershipInfoDTO(
+ id: -3,
+ collegeName: "경영대학",
+ departmentName: nil,
+ likeCount: 0,
+ isLiked: false,
+ description: "아메리카노 사이즈업 무료",
+ startDate: "2025.09.03",
+ endDate: "2025.12.18",
+ periodType: .normal
+ )
+ ]
+ ),
+ PartnershipDTO(
+ storeName: "역전할머니맥주 숭실대점",
+ longitude: 126.9532,
+ latitude: 37.4949,
+ restaurantType: "PUB",
+ partnershipInfos: [
+ PartnershipInfoDTO(
+ id: -4,
+ collegeName: "벤처중소기업센터",
+ departmentName: nil,
+ likeCount: 0,
+ isLiked: false,
+ description: "4인 이상 방문 시 안주 1개 서비스",
+ startDate: "2025.09.03",
+ endDate: "2025.12.18",
+ periodType: .normal
+ ),
+ PartnershipInfoDTO(
+ id: -5,
+ collegeName: "총학생회",
+ departmentName: nil,
+ likeCount: 0,
+ isLiked: false,
+ description: "축제 기간 병맥주 1+1",
+ startDate: "2025.10.06",
+ endDate: "2025.10.08",
+ periodType: .festival
+ )
+ ]
+ )
+ ]
+}
+#endif
diff --git a/EATSSU/App/Sources/Presentation/Map/ViewController/MainMapViewController+Network.swift b/EATSSU/App/Sources/Presentation/Map/ViewController/MainMapViewController+Network.swift
index 0f0c0fcb..ea784210 100644
--- a/EATSSU/App/Sources/Presentation/Map/ViewController/MainMapViewController+Network.swift
+++ b/EATSSU/App/Sources/Presentation/Map/ViewController/MainMapViewController+Network.swift
@@ -29,6 +29,14 @@ extension MainMapViewController {
self.cachedAllPartnerships = []
self.displayMarkers([])
}
+
+ #if DEBUG
+ // 서버에 제휴 데이터가 없는 동안 Mock으로 대체 (DEBUG 빌드 전용)
+ if self.cachedAllPartnerships.isEmpty {
+ self.cachedAllPartnerships = PartnershipMockData.samples
+ self.applyCachedMarkers()
+ }
+ #endif
}
}
From be2c0326c803dc26f373a70fda4b90ff536b17be Mon Sep 17 00:00:00 2001
From: Hwang Sang Hwan <137605270+Hrepay@users.noreply.github.com>
Date: Sat, 18 Jul 2026 19:17:07 +0900
Subject: [PATCH 4/8] =?UTF-8?q?[#432]=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20?=
=?UTF-8?q?=EB=A1=9C=EC=BB=AC=20API=20=EA=B8=B0=EB=B0=98=20=EC=B9=B4?=
=?UTF-8?q?=EC=B9=B4=EC=98=A4=EB=A7=B5=20=EC=9E=A5=EC=86=8C=20=EC=83=81?=
=?UTF-8?q?=EC=84=B8=20=EC=97=B0=EA=B2=B0=20=EB=B0=8F=20=EB=84=A4=EC=9D=B4?=
=?UTF-8?q?=EB=B2=84=EC=A7=80=EB=8F=84=20=EA=B2=80=EC=83=89=20=EC=A0=84?=
=?UTF-8?q?=ED=99=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Network/Service/KakaoLocalService.swift | 86 +++++++++++++++++++
...PartnershipDetailSheetViewController.swift | 44 ++++++++--
EATSSU/Project.swift | 1 +
3 files changed, 123 insertions(+), 8 deletions(-)
create mode 100644 EATSSU/App/Sources/Data/Network/Service/KakaoLocalService.swift
diff --git a/EATSSU/App/Sources/Data/Network/Service/KakaoLocalService.swift b/EATSSU/App/Sources/Data/Network/Service/KakaoLocalService.swift
new file mode 100644
index 00000000..97be62d9
--- /dev/null
+++ b/EATSSU/App/Sources/Data/Network/Service/KakaoLocalService.swift
@@ -0,0 +1,86 @@
+//
+// KakaoLocalService.swift
+// EATSSU
+//
+// Created by 황상환 on 7/18/26.
+//
+
+import Foundation
+
+/// 카카오 로컬 API - 키워드 장소 검색 (카카오맵 place id 조회용)
+final class KakaoLocalService {
+
+ // MARK: - Singleton
+
+ static let shared = KakaoLocalService()
+
+ private init() {}
+
+ // MARK: - Model
+
+ struct Place: Decodable {
+ let id: String
+ let placeName: String
+ let placeURL: String
+
+ enum CodingKeys: String, CodingKey {
+ case id
+ case placeName = "place_name"
+ case placeURL = "place_url"
+ }
+ }
+
+ private struct SearchResponse: Decodable {
+ let documents: [Place]
+ }
+
+ // MARK: - Properties
+
+ private var restAPIKey: String? {
+ guard let key = Bundle.main.object(forInfoDictionaryKey: "KAKAO REST API KEY") as? String,
+ !key.isEmpty else { return nil }
+ return key
+ }
+
+ // MARK: - Request
+
+ /// 가게명 + 좌표 기준으로 가장 가까운 장소 1건 검색 (키 미설정/실패 시 nil 반환)
+ func searchNearestPlace(
+ keyword: String,
+ latitude: Double,
+ longitude: Double,
+ completion: @escaping (Place?) -> Void
+ ) {
+ guard let key = restAPIKey else {
+ completion(nil)
+ return
+ }
+
+ var components = URLComponents(string: "https://dapi.kakao.com/v2/local/search/keyword.json")
+ components?.queryItems = [
+ URLQueryItem(name: "query", value: keyword),
+ URLQueryItem(name: "x", value: "\(longitude)"),
+ URLQueryItem(name: "y", value: "\(latitude)"),
+ URLQueryItem(name: "radius", value: "300"),
+ URLQueryItem(name: "sort", value: "distance"),
+ URLQueryItem(name: "size", value: "1")
+ ]
+
+ guard let url = components?.url else {
+ completion(nil)
+ return
+ }
+
+ var request = URLRequest(url: url)
+ request.setValue("KakaoAK \(key)", forHTTPHeaderField: "Authorization")
+
+ URLSession.shared.dataTask(with: request) { data, _, _ in
+ let place = data.flatMap {
+ try? JSONDecoder().decode(SearchResponse.self, from: $0).documents.first
+ }
+ DispatchQueue.main.async {
+ completion(place)
+ }
+ }.resume()
+ }
+}
diff --git a/EATSSU/App/Sources/Presentation/Map/ViewController/PartnershipDetailSheetViewController.swift b/EATSSU/App/Sources/Presentation/Map/ViewController/PartnershipDetailSheetViewController.swift
index 0cc0cb53..911fe10c 100644
--- a/EATSSU/App/Sources/Presentation/Map/ViewController/PartnershipDetailSheetViewController.swift
+++ b/EATSSU/App/Sources/Presentation/Map/ViewController/PartnershipDetailSheetViewController.swift
@@ -253,8 +253,26 @@ final class PartnershipDetailSheetViewController: BaseViewController {
// MARK: - Actions
- /// 카카오맵 앱으로 이동 (미설치 시 카카오맵 웹)
+ /// 카카오맵 장소 상세 페이지로 이동 (place id 조회 실패 시 좌표 핀으로 폴백)
@objc private func kakaoMapButtonTapped() {
+ KakaoLocalService.shared.searchNearestPlace(
+ keyword: partnership.storeName,
+ latitude: partnership.latitude,
+ longitude: partnership.longitude
+ ) { [weak self] place in
+ guard let self else { return }
+
+ if let place {
+ let appURL = URL(string: "kakaomap://place?id=\(place.id)")
+ self.openMapApp(appURL: appURL, fallbackURL: URL(string: place.placeURL))
+ } else {
+ self.openKakaoMapByCoordinate()
+ }
+ }
+ }
+
+ /// 카카오맵 좌표 핀으로 이동 (미설치 시 카카오맵 웹)
+ private func openKakaoMapByCoordinate() {
let appURL = URL(string: "kakaomap://look?p=\(partnership.latitude),\(partnership.longitude)")
let encodedName = partnership.storeName
.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
@@ -264,18 +282,28 @@ final class PartnershipDetailSheetViewController: BaseViewController {
openMapApp(appURL: appURL, fallbackURL: webURL)
}
- /// 네이버지도 앱으로 이동 (미설치 시 네이버지도 웹 검색)
+ /// 네이버지도 검색으로 이동 (미설치 시 네이버지도 웹 검색)
+ /// 카카오 로컬 API의 정제된 상호명(지점명 포함)으로 검색해 명중률을 높임
@objc private func naverMapButtonTapped() {
- var components = URLComponents(string: "nmap://place")
+ KakaoLocalService.shared.searchNearestPlace(
+ keyword: partnership.storeName,
+ latitude: partnership.latitude,
+ longitude: partnership.longitude
+ ) { [weak self] place in
+ guard let self else { return }
+ self.openNaverMapSearch(query: place?.placeName ?? self.partnership.storeName)
+ }
+ }
+
+ private func openNaverMapSearch(query: String) {
+ var components = URLComponents(string: "nmap://search")
components?.queryItems = [
- URLQueryItem(name: "lat", value: "\(partnership.latitude)"),
- URLQueryItem(name: "lng", value: "\(partnership.longitude)"),
- URLQueryItem(name: "name", value: partnership.storeName),
+ URLQueryItem(name: "query", value: query),
URLQueryItem(name: "appname", value: Bundle.main.bundleIdentifier ?? "")
]
- let encodedName = partnership.storeName
+ let encodedQuery = query
.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? ""
- let webURL = URL(string: "https://map.naver.com/p/search/\(encodedName)")
+ let webURL = URL(string: "https://map.naver.com/p/search/\(encodedQuery)")
openMapApp(appURL: components?.url, fallbackURL: webURL)
}
diff --git a/EATSSU/Project.swift b/EATSSU/Project.swift
index a650e84d..ba37b330 100644
--- a/EATSSU/Project.swift
+++ b/EATSSU/Project.swift
@@ -6,6 +6,7 @@ let appInfoPlist: InfoPlist = .extendingDefault(with: [
"UILaunchStoryboardName": "LaunchScreen",
"BASE_URL": "https://$(BASE_URL)",
"KAKAO API KEY": "$(KAKAO_API_KEY)",
+ "KAKAO REST API KEY": "$(KAKAO_REST_API_KEY)",
"AppGroupID": "$(APP_GROUP_ID)",
"FirebaseAutomaticScreenReportingEnabled": false,
"UIBackgroundModes": [
From 0e4c510ef25705dc54e52fdbc18298710d67ea74 Mon Sep 17 00:00:00 2001
From: Hwang Sang Hwan <137605270+Hrepay@users.noreply.github.com>
Date: Sat, 18 Jul 2026 22:49:31 +0900
Subject: [PATCH 5/8] =?UTF-8?q?[#432]=20=EC=84=9C=EB=B2=84=20=EC=A0=9C?=
=?UTF-8?q?=EA=B3=B5=20=EC=B9=B4=EC=B9=B4=EC=98=A4=EB=A7=B5/=EB=84=A4?=
=?UTF-8?q?=EC=9D=B4=EB=B2=84=EC=A7=80=EB=8F=84=20URL=20=EC=9A=B0=EC=84=A0?=
=?UTF-8?q?=20=EC=97=B0=EA=B2=B0=20=EB=B0=8F=20DTO=20=ED=95=84=EB=93=9C=20?=
=?UTF-8?q?=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../DTO/Partnership/PartnershipDTO.swift | 2 ++
.../Map/PartnershipMockData.swift | 6 +++++
.../MainMapViewController+Network.swift | 2 ++
...PartnershipDetailSheetViewController.swift | 22 ++++++++++++++++---
4 files changed, 29 insertions(+), 3 deletions(-)
diff --git a/EATSSU/App/Sources/Data/Network/DTO/Partnership/PartnershipDTO.swift b/EATSSU/App/Sources/Data/Network/DTO/Partnership/PartnershipDTO.swift
index 0a74e0de..abd7f6df 100644
--- a/EATSSU/App/Sources/Data/Network/DTO/Partnership/PartnershipDTO.swift
+++ b/EATSSU/App/Sources/Data/Network/DTO/Partnership/PartnershipDTO.swift
@@ -12,6 +12,8 @@ struct PartnershipDTO: Codable {
let longitude: Double
let latitude: Double
let restaurantType: String
+ let naverMapUrl: String?
+ let kakaoMapUrl: String?
let partnershipInfos: [PartnershipInfoDTO]
}
diff --git a/EATSSU/App/Sources/Presentation/Map/PartnershipMockData.swift b/EATSSU/App/Sources/Presentation/Map/PartnershipMockData.swift
index 9c718b5d..49de0d0e 100644
--- a/EATSSU/App/Sources/Presentation/Map/PartnershipMockData.swift
+++ b/EATSSU/App/Sources/Presentation/Map/PartnershipMockData.swift
@@ -18,6 +18,8 @@ enum PartnershipMockData {
longitude: 126.9550,
latitude: 37.4959,
restaurantType: "RESTAURANT",
+ naverMapUrl: nil,
+ kakaoMapUrl: nil,
partnershipInfos: [
PartnershipInfoDTO(
id: -1,
@@ -48,6 +50,8 @@ enum PartnershipMockData {
longitude: 126.9538,
latitude: 37.4964,
restaurantType: "CAFE",
+ naverMapUrl: nil,
+ kakaoMapUrl: nil,
partnershipInfos: [
PartnershipInfoDTO(
id: -3,
@@ -67,6 +71,8 @@ enum PartnershipMockData {
longitude: 126.9532,
latitude: 37.4949,
restaurantType: "PUB",
+ naverMapUrl: nil,
+ kakaoMapUrl: nil,
partnershipInfos: [
PartnershipInfoDTO(
id: -4,
diff --git a/EATSSU/App/Sources/Presentation/Map/ViewController/MainMapViewController+Network.swift b/EATSSU/App/Sources/Presentation/Map/ViewController/MainMapViewController+Network.swift
index ea784210..d877f66b 100644
--- a/EATSSU/App/Sources/Presentation/Map/ViewController/MainMapViewController+Network.swift
+++ b/EATSSU/App/Sources/Presentation/Map/ViewController/MainMapViewController+Network.swift
@@ -65,6 +65,8 @@ extension MainMapViewController {
longitude: partnership.longitude,
latitude: partnership.latitude,
restaurantType: partnership.restaurantType,
+ naverMapUrl: partnership.naverMapUrl,
+ kakaoMapUrl: partnership.kakaoMapUrl,
partnershipInfos: matchingInfos
)
}
diff --git a/EATSSU/App/Sources/Presentation/Map/ViewController/PartnershipDetailSheetViewController.swift b/EATSSU/App/Sources/Presentation/Map/ViewController/PartnershipDetailSheetViewController.swift
index 911fe10c..2cdbfaa5 100644
--- a/EATSSU/App/Sources/Presentation/Map/ViewController/PartnershipDetailSheetViewController.swift
+++ b/EATSSU/App/Sources/Presentation/Map/ViewController/PartnershipDetailSheetViewController.swift
@@ -253,8 +253,18 @@ final class PartnershipDetailSheetViewController: BaseViewController {
// MARK: - Actions
- /// 카카오맵 장소 상세 페이지로 이동 (place id 조회 실패 시 좌표 핀으로 폴백)
+ /// 카카오맵 장소 상세 페이지로 이동
+ /// 서버 제공 URL 우선 → 없으면 로컬 API 매칭 → 실패 시 좌표 핀 폴백
@objc private func kakaoMapButtonTapped() {
+ // 서버가 준 place URL(place.map.kakao.com/{id})에서 id를 추출해 앱 스킴으로 연결
+ if let urlString = partnership.kakaoMapUrl,
+ let webURL = URL(string: urlString),
+ Int(webURL.lastPathComponent) != nil {
+ let appURL = URL(string: "kakaomap://place?id=\(webURL.lastPathComponent)")
+ openMapApp(appURL: appURL, fallbackURL: webURL)
+ return
+ }
+
KakaoLocalService.shared.searchNearestPlace(
keyword: partnership.storeName,
latitude: partnership.latitude,
@@ -282,9 +292,15 @@ final class PartnershipDetailSheetViewController: BaseViewController {
openMapApp(appURL: appURL, fallbackURL: webURL)
}
- /// 네이버지도 검색으로 이동 (미설치 시 네이버지도 웹 검색)
- /// 카카오 로컬 API의 정제된 상호명(지점명 포함)으로 검색해 명중률을 높임
+ /// 네이버지도 플레이스로 이동
+ /// 서버 제공 URL 우선(유니버설 링크로 앱/웹 자동 분기) → 없으면 정제된 상호명 검색 폴백
@objc private func naverMapButtonTapped() {
+ if let urlString = partnership.naverMapUrl,
+ let url = URL(string: urlString) {
+ UIApplication.shared.open(url)
+ return
+ }
+
KakaoLocalService.shared.searchNearestPlace(
keyword: partnership.storeName,
latitude: partnership.latitude,
From 0164c6d65c16ecf3b00bbf9b31087e1b2fda1d28 Mon Sep 17 00:00:00 2001
From: Hwang Sang Hwan <137605270+Hrepay@users.noreply.github.com>
Date: Sat, 18 Jul 2026 22:49:31 +0900
Subject: [PATCH 6/8] =?UTF-8?q?[#432]=20=EB=82=B4=20=EC=A0=9C=ED=9C=B4=20?=
=?UTF-8?q?=EB=AA=A8=EB=93=9C=EB=A1=9C=20=EC=8B=9C=EC=9E=91=20=EC=8B=9C=20?=
=?UTF-8?q?=EC=A0=84=EC=B2=B4=20=ED=83=AD=20=EB=A7=88=EC=BB=A4=20=EB=AF=B8?=
=?UTF-8?q?=ED=91=9C=EC=8B=9C=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Map/ViewController/MainMapViewController.swift | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/EATSSU/App/Sources/Presentation/Map/ViewController/MainMapViewController.swift b/EATSSU/App/Sources/Presentation/Map/ViewController/MainMapViewController.swift
index d147da6d..066d5a0a 100644
--- a/EATSSU/App/Sources/Presentation/Map/ViewController/MainMapViewController.swift
+++ b/EATSSU/App/Sources/Presentation/Map/ViewController/MainMapViewController.swift
@@ -133,7 +133,7 @@ final class MainMapViewController: BaseViewController {
collegeId: currentCollegeId,
majorId: currentDepartmentId
)
- applyCachedMarkers()
+ refreshOrApplyCachedMarkers()
}
@objc private func didTapWhole() {
@@ -146,7 +146,16 @@ final class MainMapViewController: BaseViewController {
collegeId: currentCollegeId,
majorId: currentDepartmentId
)
- applyCachedMarkers()
+ refreshOrApplyCachedMarkers()
+ }
+
+ /// 캐시가 비어있으면(내 제휴 모드로 시작해 전체 데이터를 아직 안 받은 경우) 서버에서 받아오고, 있으면 캐시로 필터링
+ private func refreshOrApplyCachedMarkers() {
+ if cachedAllPartnerships.isEmpty {
+ refreshAllPartnerships()
+ } else {
+ applyCachedMarkers()
+ }
}
@objc private func didTapMyOnly() {
From 1c522bfd75443d5ed06a5bb7f00a8502a1cd2ada Mon Sep 17 00:00:00 2001
From: Hwang Sang Hwan <137605270+Hrepay@users.noreply.github.com>
Date: Sat, 18 Jul 2026 22:59:16 +0900
Subject: [PATCH 7/8] =?UTF-8?q?[#432]=20=EC=A0=9C=ED=9C=B4=20=EC=8B=9C?=
=?UTF-8?q?=ED=8A=B8=20=EA=B5=AC=EB=B6=84=EC=84=A0=20=EB=8C=80=EB=B9=84=20?=
=?UTF-8?q?=EB=B3=B4=EC=A0=95=20=EB=B0=8F=20large=20=ED=99=95=EC=9E=A5=20?=
=?UTF-8?q?=EC=A7=80=EC=9B=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../MainMapViewController+Marker.swift | 4 ++--
.../PartnershipDetailSheetViewController.swift | 15 +++++++++------
2 files changed, 11 insertions(+), 8 deletions(-)
diff --git a/EATSSU/App/Sources/Presentation/Map/ViewController/MainMapViewController+Marker.swift b/EATSSU/App/Sources/Presentation/Map/ViewController/MainMapViewController+Marker.swift
index cc490f8d..06ba34a6 100644
--- a/EATSSU/App/Sources/Presentation/Map/ViewController/MainMapViewController+Marker.swift
+++ b/EATSSU/App/Sources/Presentation/Map/ViewController/MainMapViewController+Marker.swift
@@ -168,10 +168,10 @@ extension MainMapViewController {
let customDetent = UISheetPresentationController.Detent.custom { _ in
return contentHeight
}
- sheet.detents = [customDetent]
+ sheet.detents = [customDetent, .large()]
} else {
// iOS 16.0 미만에서는 medium detent 사용
- sheet.detents = [.medium()]
+ sheet.detents = [.medium(), .large()]
}
sheet.prefersGrabberVisible = true
diff --git a/EATSSU/App/Sources/Presentation/Map/ViewController/PartnershipDetailSheetViewController.swift b/EATSSU/App/Sources/Presentation/Map/ViewController/PartnershipDetailSheetViewController.swift
index 2cdbfaa5..201cddc3 100644
--- a/EATSSU/App/Sources/Presentation/Map/ViewController/PartnershipDetailSheetViewController.swift
+++ b/EATSSU/App/Sources/Presentation/Map/ViewController/PartnershipDetailSheetViewController.swift
@@ -81,7 +81,7 @@ final class PartnershipDetailSheetViewController: BaseViewController {
)
naverMapButton.addTarget(self, action: #selector(naverMapButtonTapped), for: .touchUpInside)
- mapButtonDivider.backgroundColor = EATSSUDesignColors.Color.gray200
+ mapButtonDivider.backgroundColor = EATSSUDesignColors.Color.gray300
[kakaoMapButton, naverMapButton, mapButtonDivider].forEach {
mapButtonBarView.addSubview($0)
@@ -108,10 +108,12 @@ final class PartnershipDetailSheetViewController: BaseViewController {
$0.leading.trailing.equalToSuperview().inset(24)
}
+ // large로 확장 시에도 버튼 바가 시트 하단에 붙어있도록 bottom 고정
mapButtonBarView.snp.makeConstraints {
- $0.top.equalTo(infoListStackView.snp.bottom).offset(6)
+ $0.top.greaterThanOrEqualTo(infoListStackView.snp.bottom).offset(6)
$0.leading.trailing.equalToSuperview()
$0.height.equalTo(56)
+ $0.bottom.equalTo(view.safeAreaLayoutGuide).inset(10)
}
kakaoMapButton.snp.makeConstraints {
@@ -173,12 +175,13 @@ final class PartnershipDetailSheetViewController: BaseViewController {
// MARK: - UI Helpers
/// 제휴 content 갯수에 따라 유동적으로 Height 측정
+ /// 버튼 바는 시트 하단에 고정이므로 리스트 높이 기준으로 계산
func calculatePreferredHeight() -> CGFloat {
view.layoutIfNeeded()
- let contentHeight = mapButtonBarView.frame.maxY
- let bottomPadding: CGFloat = view.safeAreaInsets.bottom + 10
+ let buttonBarArea: CGFloat = 6 + 56 + 10
+ let bottomPadding: CGFloat = view.safeAreaInsets.bottom
- return contentHeight + bottomPadding
+ return infoListStackView.frame.maxY + buttonBarArea + bottomPadding
}
/// 지도 앱 이동 버튼 공통 Configuration 생성
@@ -232,7 +235,7 @@ final class PartnershipDetailSheetViewController: BaseViewController {
contentStack.spacing = 4
let separator = UIView()
- separator.backgroundColor = EATSSUDesignColors.Color.gray200
+ separator.backgroundColor = EATSSUDesignColors.Color.gray300
separator.isHidden = isLast
separator.snp.makeConstraints {
$0.height.equalTo(1)
From 33947880e09882da90fe40664775c737d63e52e5 Mon Sep 17 00:00:00 2001
From: Hwang Sang Hwan <137605270+Hrepay@users.noreply.github.com>
Date: Sun, 19 Jul 2026 19:57:28 +0900
Subject: [PATCH 8/8] =?UTF-8?q?[#432]=20=EC=BD=94=EB=93=9C=EB=A6=AC?=
=?UTF-8?q?=EB=B7=B0=20=EB=B0=98=EC=98=81:=20=EC=8B=9C=ED=8A=B8=20?=
=?UTF-8?q?=EB=86=92=EC=9D=B4=20safe=20area=20=EC=8B=A4=EC=B8=A1=20?=
=?UTF-8?q?=EC=9E=AC=EA=B3=84=EC=82=B0=20=EB=B0=8F=20=EC=A7=80=EB=8F=84=20?=
=?UTF-8?q?URL=20=EC=9D=B8=EC=BD=94=EB=94=A9=20=EB=B3=B4=EA=B0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../MainMapViewController+Marker.swift | 7 +++---
...PartnershipDetailSheetViewController.swift | 24 +++++++++++++++----
2 files changed, 22 insertions(+), 9 deletions(-)
diff --git a/EATSSU/App/Sources/Presentation/Map/ViewController/MainMapViewController+Marker.swift b/EATSSU/App/Sources/Presentation/Map/ViewController/MainMapViewController+Marker.swift
index 06ba34a6..23543c18 100644
--- a/EATSSU/App/Sources/Presentation/Map/ViewController/MainMapViewController+Marker.swift
+++ b/EATSSU/App/Sources/Presentation/Map/ViewController/MainMapViewController+Marker.swift
@@ -161,12 +161,11 @@ extension MainMapViewController {
detailVC.loadViewIfNeeded()
if let sheet = detailVC.sheetPresentationController {
- let contentHeight = detailVC.calculatePreferredHeight()
-
// iOS 16.0 이상에서만 custom detent 사용
+ // 표시 후 safe area 확정 시 invalidateDetents()로 클로저가 재실행되어 높이가 보정됨
if #available(iOS 16.0, *) {
- let customDetent = UISheetPresentationController.Detent.custom { _ in
- return contentHeight
+ let customDetent = UISheetPresentationController.Detent.custom { [weak detailVC] _ in
+ detailVC?.calculatePreferredHeight()
}
sheet.detents = [customDetent, .large()]
} else {
diff --git a/EATSSU/App/Sources/Presentation/Map/ViewController/PartnershipDetailSheetViewController.swift b/EATSSU/App/Sources/Presentation/Map/ViewController/PartnershipDetailSheetViewController.swift
index 201cddc3..a19be2f8 100644
--- a/EATSSU/App/Sources/Presentation/Map/ViewController/PartnershipDetailSheetViewController.swift
+++ b/EATSSU/App/Sources/Presentation/Map/ViewController/PartnershipDetailSheetViewController.swift
@@ -142,10 +142,19 @@ final class PartnershipDetailSheetViewController: BaseViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
-
+
logScreenView(screenID: FirebaseScreenID.Map.map2)
}
+ /// 시트가 화면에 붙어 실제 safe area가 확정되면 detent 높이 재계산
+ override func viewSafeAreaInsetsDidChange() {
+ super.viewSafeAreaInsetsDidChange()
+
+ if #available(iOS 16.0, *) {
+ sheetPresentationController?.invalidateDetents()
+ }
+ }
+
/// 매장 정보와 제휴 내용을 화면에 반영
private func configureData() {
storeNameLabel.text = partnership.storeName
@@ -287,14 +296,20 @@ final class PartnershipDetailSheetViewController: BaseViewController {
/// 카카오맵 좌표 핀으로 이동 (미설치 시 카카오맵 웹)
private func openKakaoMapByCoordinate() {
let appURL = URL(string: "kakaomap://look?p=\(partnership.latitude),\(partnership.longitude)")
- let encodedName = partnership.storeName
- .addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
+ let encodedName = percentEncodedForMapURL(partnership.storeName)
let webURL = URL(
string: "https://map.kakao.com/link/map/\(encodedName),\(partnership.latitude),\(partnership.longitude)"
)
openMapApp(appURL: appURL, fallbackURL: webURL)
}
+ /// 지도 웹 URL 경로에 안전하게 넣을 수 있도록 경로/구분자 문자까지 인코딩
+ private func percentEncodedForMapURL(_ text: String) -> String {
+ var allowed = CharacterSet.urlPathAllowed
+ allowed.remove(charactersIn: "/?&,")
+ return text.addingPercentEncoding(withAllowedCharacters: allowed) ?? ""
+ }
+
/// 네이버지도 플레이스로 이동
/// 서버 제공 URL 우선(유니버설 링크로 앱/웹 자동 분기) → 없으면 정제된 상호명 검색 폴백
@objc private func naverMapButtonTapped() {
@@ -320,8 +335,7 @@ final class PartnershipDetailSheetViewController: BaseViewController {
URLQueryItem(name: "query", value: query),
URLQueryItem(name: "appname", value: Bundle.main.bundleIdentifier ?? "")
]
- let encodedQuery = query
- .addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? ""
+ let encodedQuery = percentEncodedForMapURL(query)
let webURL = URL(string: "https://map.naver.com/p/search/\(encodedQuery)")
openMapApp(appURL: components?.url, fallbackURL: webURL)
}