From 662b652098835aa2a2044cf13fdf27426f944c3d Mon Sep 17 00:00:00 2001 From: logicHoon Date: Tue, 30 Dec 2025 17:17:09 +0900 Subject: [PATCH 1/9] =?UTF-8?q?style:=20map=20=ED=8C=8C=EC=9D=BC=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EB=B3=80=EC=88=98=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Coordinator/NaverMapViewCoordinator.swift | 28 +++++++++---------- .../Features/PlaceMap/NaverMapView.swift | 4 +-- .../PlaceMap/ViewModels/MapViewModel.swift | 20 ++++++------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/LeaveGo/LeaveGo/Features/PlaceMap/Coordinator/NaverMapViewCoordinator.swift b/LeaveGo/LeaveGo/Features/PlaceMap/Coordinator/NaverMapViewCoordinator.swift index 7006898..826318e 100644 --- a/LeaveGo/LeaveGo/Features/PlaceMap/Coordinator/NaverMapViewCoordinator.swift +++ b/LeaveGo/LeaveGo/Features/PlaceMap/Coordinator/NaverMapViewCoordinator.swift @@ -34,7 +34,7 @@ class NaverMapViewCoordinator: NSObject { // 마커 캐시 private var currentMarkers: [String: NMFMarker] = [:] - private var cachedPlaceIds: Set = [] + private var cachedPlaceIDs: Set = [] // 상태 플래그 var hasMovedToUserLocation = false @@ -67,26 +67,26 @@ class NaverMapViewCoordinator: NSObject { /// 6. 캐시 업데이트 /// ``` public func updateMarkers(on mapView: NMFMapView, with placeList: [PlaceDTO]) { - let newIds = Set(placeList.map { $0.id }) + let newIDs = Set(placeList.map { $0.id }) // 변경 없으면 스킵 - guard cachedPlaceIds != newIds else { return } + guard cachedPlaceIDs != newIDs else { return } // 1. 삭제: 새 데이터에 없는 기존 마커 제거 - let toRemove = cachedPlaceIds.subtracting(newIds) + let toRemove = cachedPlaceIDs.subtracting(newIDs) for id in toRemove { removeMarker(id: id) } // 2. 추가: 기존에 없는 새 마커 생성 - let toAdd = newIds.subtracting(cachedPlaceIds) + let toAdd = newIDs.subtracting(cachedPlaceIDs) for place in placeList where toAdd.contains(place.id) { let marker = createMarker(from: place) marker.mapView = mapView currentMarkers[place.id] = marker } - cachedPlaceIds = newIds + cachedPlaceIDs = newIDs } // MARK: createMarker @@ -122,12 +122,12 @@ class NaverMapViewCoordinator: NSObject { // 탭 핸들러: 오버레이가 터치될 경우 호출되는 콜백 블록 marker.touchHandler = { [weak self] overlay -> Bool in - guard let placeId = overlay.userInfo["placeId"] as? String else { + guard let placeID = overlay.userInfo["placeId"] as? String else { return true } Task { @MainActor in - await self?.naverMapViewDelegate?.setSelectedPlaceId(id: placeId) + await self?.naverMapViewDelegate?.setSelectedPlaceID(id: placeID) } return true } @@ -152,10 +152,10 @@ class NaverMapViewCoordinator: NSObject { /// 이전 선택과 현재 선택 마커 **최대 2개만** 업데이트하여 성능을 극대화합니다. /// 모든 마커를 순회하는 기존 방식(O(n))과 달리, 변경된 마커만 직접 접근하여 /// 업데이트하므로 O(1) 복잡도를 달성합니다. - public func updateSelectedMarkerOptimized(selectedId: String?, previousSelectedId: String?) { + public func updateSelectedMarkerOptimized(selectedID: String?, previousSelectedID: String?) { // 1. 이전 선택 마커를 기본 스타일로 복원 - if let prevId = previousSelectedId, - let prevMarker = currentMarkers[prevId] { + if let prevID = previousSelectedID, + let prevMarker = currentMarkers[prevID] { prevMarker.iconTintColor = .systemBlue prevMarker.width = 24 prevMarker.height = 32 @@ -163,8 +163,8 @@ class NaverMapViewCoordinator: NSObject { } // 2. 새로운 선택 마커를 강조 스타일로 변경 - if let newId = selectedId, - let newMarker = currentMarkers[newId] { + if let newID = selectedID, + let newMarker = currentMarkers[newID] { newMarker.iconTintColor = .systemOrange newMarker.width = 32 newMarker.height = 42 @@ -190,7 +190,7 @@ extension NaverMapViewCoordinator: NMFMapViewTouchDelegate { func mapView(_ mapView: NMFMapView, didTapMap latlng: NMGLatLng, point: CGPoint) { // 지도 빈 영역 탭 시 선택 해제 Task { @MainActor in - await naverMapViewDelegate?.setSelectedPlaceId(id: nil) + await naverMapViewDelegate?.setSelectedPlaceID(id: nil) } } diff --git a/LeaveGo/LeaveGo/Features/PlaceMap/NaverMapView.swift b/LeaveGo/LeaveGo/Features/PlaceMap/NaverMapView.swift index 2df78ac..5c46215 100644 --- a/LeaveGo/LeaveGo/Features/PlaceMap/NaverMapView.swift +++ b/LeaveGo/LeaveGo/Features/PlaceMap/NaverMapView.swift @@ -95,8 +95,8 @@ struct NaverMapView: UIViewRepresentable { coordinator.updateMarkers(on: uiView.mapView, with: viewModel.placeList) coordinator.updateSelectedMarkerOptimized( - selectedId: viewModel.selectedPlaceId, - previousSelectedId: viewModel.getPreviousSelectedPlaceId()) + selectedID: viewModel.selectedPlaceID, + previousSelectedID: viewModel.getPreviousSelectedPlaceID()) } /// Coordinator 생성 diff --git a/LeaveGo/LeaveGo/Features/PlaceMap/ViewModels/MapViewModel.swift b/LeaveGo/LeaveGo/Features/PlaceMap/ViewModels/MapViewModel.swift index d677df9..44de609 100644 --- a/LeaveGo/LeaveGo/Features/PlaceMap/ViewModels/MapViewModel.swift +++ b/LeaveGo/LeaveGo/Features/PlaceMap/ViewModels/MapViewModel.swift @@ -11,7 +11,7 @@ import NMapsMap // MARK: NaverMapViewDelegate Protocol protocol NaverMapViewDelegate: AnyObject { - func setSelectedPlaceId(id : String?) async + func setSelectedPlaceID(id: String?) async } @MainActor @@ -21,11 +21,11 @@ final class MapViewModel { // MARK: - Properties public var userLocation: CLLocationCoordinate2D? public var placeList: [PlaceDTO] = [] - public var selectedPlaceId: String? - private var previousSelectedPlaceId: String? + public var selectedPlaceID: String? + private var previousSelectedPlaceID: String? public var selectedPlace: PlaceDTO? { - placeList.first { $0.id == selectedPlaceId } + placeList.first { $0.id == selectedPlaceID } } /// 여행지 API 요청을 처리하는 리포지토리 @@ -44,8 +44,8 @@ final class MapViewModel { // MARK: Method - func getPreviousSelectedPlaceId() -> String? { - return previousSelectedPlaceId + func getPreviousSelectedPlaceID() -> String? { + return previousSelectedPlaceID } // MARK: - LocationManager @@ -116,13 +116,13 @@ final class MapViewModel { // MARK: - NaverMapViewDelegate extension MapViewModel: NaverMapViewDelegate { - func setSelectedPlaceId(id: String?) async { - guard selectedPlaceId != id else { + func setSelectedPlaceID(id: String?) async { + guard selectedPlaceID != id else { return } - previousSelectedPlaceId = selectedPlaceId + previousSelectedPlaceID = selectedPlaceID - selectedPlaceId = id + selectedPlaceID = id } } From a59738650ac7d154fafb552e40bb5a914070e276 Mon Sep 17 00:00:00 2001 From: logicHoon Date: Tue, 30 Dec 2025 18:38:28 +0900 Subject: [PATCH 2/9] =?UTF-8?q?refactor:=20map=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=EC=9D=98=20viewmodel=EC=A3=BC=EC=9E=85=20=EC=8B=9C=EC=A0=90?= =?UTF-8?q?=EC=9D=84=20=EC=A0=84=EC=97=AD=EC=97=90=EC=84=9C=20map=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EB=82=B4=EB=B6=80=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=ED=96=88=EC=8A=B5=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LeaveGo/LeaveGo/ContentView.swift | 3 --- LeaveGo/LeaveGo/Features/PlaceMap/MapView.swift | 3 +-- LeaveGo/LeaveGo/Shared/APIKeys.swift | 6 +++--- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/LeaveGo/LeaveGo/ContentView.swift b/LeaveGo/LeaveGo/ContentView.swift index 7acc765..29191c0 100644 --- a/LeaveGo/LeaveGo/ContentView.swift +++ b/LeaveGo/LeaveGo/ContentView.swift @@ -10,8 +10,6 @@ import CoreData struct ContentView: View { - @State private var mapViewModel = MapViewModel() - var body: some View { TabView { DummyView() @@ -21,7 +19,6 @@ struct ContentView: View { } MapView() - .environment(mapViewModel) .tabItem { Image(systemName: "map.fill") Text("장소") diff --git a/LeaveGo/LeaveGo/Features/PlaceMap/MapView.swift b/LeaveGo/LeaveGo/Features/PlaceMap/MapView.swift index 036e2af..c459106 100644 --- a/LeaveGo/LeaveGo/Features/PlaceMap/MapView.swift +++ b/LeaveGo/LeaveGo/Features/PlaceMap/MapView.swift @@ -9,10 +9,9 @@ import SwiftUI struct MapView: View { - @Environment(MapViewModel.self) private var viewModel + @State private var viewModel = MapViewModel() var body: some View { - @Bindable var viewModel = viewModel ZStack { NaverMapView() diff --git a/LeaveGo/LeaveGo/Shared/APIKeys.swift b/LeaveGo/LeaveGo/Shared/APIKeys.swift index cebae41..e9b9032 100644 --- a/LeaveGo/LeaveGo/Shared/APIKeys.swift +++ b/LeaveGo/LeaveGo/Shared/APIKeys.swift @@ -9,9 +9,9 @@ import Foundation struct APIKeys { static var naverMapClientId: String { - if let clientId = Bundle.main.object(forInfoDictionaryKey: "NMFClientId") as? String, !clientId.isEmpty, - clientId != "NAVER_MAP_CLIENT_ID" { - return clientId + if let clientID = Bundle.main.object(forInfoDictionaryKey: "NMFClientId") as? String, !clientID.isEmpty, + clientID != "NAVER_MAP_CLIENT_ID" { + return clientID } #if DEBUG From 6a98ec0688b7a98d5de43e30f6c1191f579bf772 Mon Sep 17 00:00:00 2001 From: logicHoon Date: Wed, 31 Dec 2025 19:22:48 +0900 Subject: [PATCH 3/9] =?UTF-8?q?design:=20=EC=A7=80=EB=8F=84=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EB=A7=88=EC=BB=A4=EB=A5=BC=20custom=20=EB=A7=88?= =?UTF-8?q?=EC=BB=A4=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Design/Extensions/View+asUIImage.swift | 22 ++++++++ .../PlaceMap/Components/PlaceMarkerView.swift | 32 ++++++++++++ .../Coordinator/NaverMapViewCoordinator.swift | 52 +++++++++++++------ .../Features/PlaceMap/NaverMapView.swift | 2 +- 4 files changed, 92 insertions(+), 16 deletions(-) create mode 100644 LeaveGo/LeaveGo/Design/Extensions/View+asUIImage.swift create mode 100644 LeaveGo/LeaveGo/Features/PlaceMap/Components/PlaceMarkerView.swift diff --git a/LeaveGo/LeaveGo/Design/Extensions/View+asUIImage.swift b/LeaveGo/LeaveGo/Design/Extensions/View+asUIImage.swift new file mode 100644 index 0000000..836d1d4 --- /dev/null +++ b/LeaveGo/LeaveGo/Design/Extensions/View+asUIImage.swift @@ -0,0 +1,22 @@ +// +// View+asUIImage.swift +// LeaveGo +// +// Created by 이치훈 on 12/30/25. +// + +import SwiftUI + +extension View { + func asMarkerImage(size: CGSize) -> UIImage? { + let controller = UIHostingController(rootView: self) + controller.view.bounds = CGRect(origin: .zero, size: size) + controller.view.backgroundColor = .clear + controller.view.layoutIfNeeded() + + let renderer = UIGraphicsImageRenderer(size: size) + return renderer.image { _ in + controller.view.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true) + } + } +} diff --git a/LeaveGo/LeaveGo/Features/PlaceMap/Components/PlaceMarkerView.swift b/LeaveGo/LeaveGo/Features/PlaceMap/Components/PlaceMarkerView.swift new file mode 100644 index 0000000..856191e --- /dev/null +++ b/LeaveGo/LeaveGo/Features/PlaceMap/Components/PlaceMarkerView.swift @@ -0,0 +1,32 @@ +// +// PlaceMarker.swift +// LeaveGo +// +// Created by 이치훈 on 12/30/25. +// + +import SwiftUI + +struct PlaceMarkerView: View { + + let isSelected: Bool + + init(isSelected: Bool = false) { + self.isSelected = isSelected + } + + var body: some View { + ZStack { + Rectangle() + .fill(.red.opacity(0.3)) + + Circle() + .fill(isSelected ? .lgAccent : .lgBackgroundAccent) + .frame(width: isSelected ? 48 : 40, + height: isSelected ? 48 : 40) + .shadow(.medium) + + Image(systemName: "house.fill") + } + } +} diff --git a/LeaveGo/LeaveGo/Features/PlaceMap/Coordinator/NaverMapViewCoordinator.swift b/LeaveGo/LeaveGo/Features/PlaceMap/Coordinator/NaverMapViewCoordinator.swift index 826318e..cb37a27 100644 --- a/LeaveGo/LeaveGo/Features/PlaceMap/Coordinator/NaverMapViewCoordinator.swift +++ b/LeaveGo/LeaveGo/Features/PlaceMap/Coordinator/NaverMapViewCoordinator.swift @@ -39,9 +39,25 @@ class NaverMapViewCoordinator: NSObject { // 상태 플래그 var hasMovedToUserLocation = false - // 공유 아이콘 (메모리 최적화) - private let defaultIcon = "img_logoWithNoBg" - private let selectedIcon = "img_userAnnotationPlaceholder" + // MARK: - 마커 이미지 캐시 + + /// 선택되지 않은 마커의 이미지 + private lazy var defaultMarkerImage: NMFOverlayImage? = { + let markerView = PlaceMarkerView(isSelected: false) + guard let uiImage = markerView.asMarkerImage(size: CGSize(width: 50, height: 50)) else { + return nil + } + return NMFOverlayImage(image: uiImage) + }() + + /// 선택된 마커의 이미지 + private lazy var selectedMarkerImage: NMFOverlayImage? = { + let markerView = PlaceMarkerView(isSelected: true) + guard let uiImage = markerView.asMarkerImage(size: CGSize(width: 60, height: 60)) else { + return nil + } + return NMFOverlayImage(image: uiImage) + }() init(viewModel: MapViewModel) { naverMapViewDelegate = viewModel @@ -104,11 +120,10 @@ class NaverMapViewCoordinator: NSObject { marker.position = NMGLatLng(lat: lat, lng: lng) } - // 스타일 설정 - marker.iconImage = NMFOverlayImage(name: defaultIcon) - marker.iconTintColor = .systemBlue - marker.width = 24 - marker.height = 32 + // 스타일 설정 - 캐시된 기본 이미지 사용 + marker.iconImage = defaultMarkerImage ?? NMFOverlayImage() + marker.width = CGFloat(NMF_MARKER_SIZE_AUTO) + marker.height = CGFloat(NMF_MARKER_SIZE_AUTO) // 캡션 설정 marker.captionText = place.title @@ -147,27 +162,34 @@ class NaverMapViewCoordinator: NSObject { } // MARK: updateSelectedMarker + /// 선택된 마커를 최적화된 방식으로 업데이트합니다. /// /// 이전 선택과 현재 선택 마커 **최대 2개만** 업데이트하여 성능을 극대화합니다. /// 모든 마커를 순회하는 기존 방식(O(n))과 달리, 변경된 마커만 직접 접근하여 /// 업데이트하므로 O(1) 복잡도를 달성합니다. - public func updateSelectedMarkerOptimized(selectedID: String?, previousSelectedID: String?) { + /// + /// - Parameters: + /// - selectedID: 현재 선택된 마커의 ID + /// - previousSelectedID: 이전에 선택되었던 마커의 ID + /// + /// - Note: 캐시된 이미지를 재사용하므로 이미지 재생성 비용이 발생하지 않습니다. + public func updateSelectedMarker(selectedID: String?, previousSelectedID: String?) { // 1. 이전 선택 마커를 기본 스타일로 복원 if let prevID = previousSelectedID, let prevMarker = currentMarkers[prevID] { - prevMarker.iconTintColor = .systemBlue - prevMarker.width = 24 - prevMarker.height = 32 + prevMarker.iconImage = defaultMarkerImage ?? NMFOverlayImage() + prevMarker.width = CGFloat(NMF_MARKER_SIZE_AUTO) + prevMarker.height = CGFloat(NMF_MARKER_SIZE_AUTO) prevMarker.zIndex = 0 } // 2. 새로운 선택 마커를 강조 스타일로 변경 if let newID = selectedID, let newMarker = currentMarkers[newID] { - newMarker.iconTintColor = .systemOrange - newMarker.width = 32 - newMarker.height = 42 + newMarker.iconImage = selectedMarkerImage ?? NMFOverlayImage() + newMarker.width = CGFloat(NMF_MARKER_SIZE_AUTO) + newMarker.height = CGFloat(NMF_MARKER_SIZE_AUTO) newMarker.zIndex = 1 } } diff --git a/LeaveGo/LeaveGo/Features/PlaceMap/NaverMapView.swift b/LeaveGo/LeaveGo/Features/PlaceMap/NaverMapView.swift index 5c46215..cda0c34 100644 --- a/LeaveGo/LeaveGo/Features/PlaceMap/NaverMapView.swift +++ b/LeaveGo/LeaveGo/Features/PlaceMap/NaverMapView.swift @@ -94,7 +94,7 @@ struct NaverMapView: UIViewRepresentable { coordinator.updateMarkers(on: uiView.mapView, with: viewModel.placeList) - coordinator.updateSelectedMarkerOptimized( + coordinator.updateSelectedMarker( selectedID: viewModel.selectedPlaceID, previousSelectedID: viewModel.getPreviousSelectedPlaceID()) } From 8b66dbc79370cee7930af22eaa31cba079ebd178 Mon Sep 17 00:00:00 2001 From: logicHoon Date: Wed, 31 Dec 2025 20:58:51 +0900 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20=EB=A7=88=EC=BB=A4=20layout?= =?UTF-8?q?=EC=97=90=20=EC=A7=80=EC=97=AD=20=EC=8D=B8=EB=84=A4=EC=9D=BC?= =?UTF-8?q?=EC=9D=B4=20=ED=91=9C=EC=8B=9C=EB=90=98=EB=8A=94=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PlaceMap/Components/PlaceMarkerView.swift | 22 ++++++- .../Coordinator/NaverMapViewCoordinator.swift | 66 +++++++++++++++++-- 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/LeaveGo/LeaveGo/Features/PlaceMap/Components/PlaceMarkerView.swift b/LeaveGo/LeaveGo/Features/PlaceMap/Components/PlaceMarkerView.swift index 856191e..2c513c7 100644 --- a/LeaveGo/LeaveGo/Features/PlaceMap/Components/PlaceMarkerView.swift +++ b/LeaveGo/LeaveGo/Features/PlaceMap/Components/PlaceMarkerView.swift @@ -10,15 +10,19 @@ import SwiftUI struct PlaceMarkerView: View { let isSelected: Bool + let thumbnail: UIImage? - init(isSelected: Bool = false) { + init(isSelected: Bool = false, thumbnail: UIImage? = nil) { self.isSelected = isSelected + self.thumbnail = thumbnail } var body: some View { ZStack { +#if DEBUG Rectangle() .fill(.red.opacity(0.3)) +#endif Circle() .fill(isSelected ? .lgAccent : .lgBackgroundAccent) @@ -26,7 +30,21 @@ struct PlaceMarkerView: View { height: isSelected ? 48 : 40) .shadow(.medium) - Image(systemName: "house.fill") + if let thumbnail { + thumbnailImage(Image(uiImage: thumbnail)) + } else { + thumbnailImage(Image("img_logoWithNoBg")) + } } } + + @ViewBuilder + private func thumbnailImage(_ image: Image) -> some View { + image + .resizable() + .scaledToFill() + .frame(width: isSelected ? 40 : 32, + height: isSelected ? 40 : 32) + .clipShape(Circle()) + } } diff --git a/LeaveGo/LeaveGo/Features/PlaceMap/Coordinator/NaverMapViewCoordinator.swift b/LeaveGo/LeaveGo/Features/PlaceMap/Coordinator/NaverMapViewCoordinator.swift index cb37a27..09edd9e 100644 --- a/LeaveGo/LeaveGo/Features/PlaceMap/Coordinator/NaverMapViewCoordinator.swift +++ b/LeaveGo/LeaveGo/Features/PlaceMap/Coordinator/NaverMapViewCoordinator.swift @@ -41,10 +41,19 @@ class NaverMapViewCoordinator: NSObject { // MARK: - 마커 이미지 캐시 + /// 장소별 마커 이미지 캐시 + private var markerImageCache: [String: NMFOverlayImage] = [:] + private var selectedMarkerImageCache: [String: NMFOverlayImage] = [:] + + private let imageRepository = ImageRepository.shared + + private let defaultMarkerSize = CGSize(width: 50, height: 100) + private let selectedMarkerSize = CGSize(width: 60, height: 120) + /// 선택되지 않은 마커의 이미지 private lazy var defaultMarkerImage: NMFOverlayImage? = { let markerView = PlaceMarkerView(isSelected: false) - guard let uiImage = markerView.asMarkerImage(size: CGSize(width: 50, height: 50)) else { + guard let uiImage = markerView.asMarkerImage(size: defaultMarkerSize) else { return nil } return NMFOverlayImage(image: uiImage) @@ -53,7 +62,7 @@ class NaverMapViewCoordinator: NSObject { /// 선택된 마커의 이미지 private lazy var selectedMarkerImage: NMFOverlayImage? = { let markerView = PlaceMarkerView(isSelected: true) - guard let uiImage = markerView.asMarkerImage(size: CGSize(width: 60, height: 60)) else { + guard let uiImage = markerView.asMarkerImage(size: selectedMarkerSize) else { return nil } return NMFOverlayImage(image: uiImage) @@ -147,6 +156,14 @@ class NaverMapViewCoordinator: NSObject { return true } + // 장소별 썸네일 마크를 미리 생성 및 캐싱 + if let thumbnailURLString = place.thumbnailImage, + let thumbnailURL = URL(string: thumbnailURLString) { + loadThumbnailAndUpdateMarker(placeID: place.id, + url: thumbnailURL, + marker: marker) + } + return marker } @@ -159,6 +176,10 @@ class NaverMapViewCoordinator: NSObject { marker.touchHandler = nil marker.mapView = nil currentMarkers.removeValue(forKey: id) + + // 마커 썸네일 이미지 캐시 정리 + markerImageCache.removeValue(forKey: id) + selectedMarkerImageCache.removeValue(forKey: id) } // MARK: updateSelectedMarker @@ -178,7 +199,7 @@ class NaverMapViewCoordinator: NSObject { // 1. 이전 선택 마커를 기본 스타일로 복원 if let prevID = previousSelectedID, let prevMarker = currentMarkers[prevID] { - prevMarker.iconImage = defaultMarkerImage ?? NMFOverlayImage() + prevMarker.iconImage = markerImageCache[prevID] ?? defaultMarkerImage ?? NMFOverlayImage() prevMarker.width = CGFloat(NMF_MARKER_SIZE_AUTO) prevMarker.height = CGFloat(NMF_MARKER_SIZE_AUTO) prevMarker.zIndex = 0 @@ -187,13 +208,48 @@ class NaverMapViewCoordinator: NSObject { // 2. 새로운 선택 마커를 강조 스타일로 변경 if let newID = selectedID, let newMarker = currentMarkers[newID] { - newMarker.iconImage = selectedMarkerImage ?? NMFOverlayImage() + newMarker.iconImage = selectedMarkerImageCache[newID] ?? selectedMarkerImage ?? NMFOverlayImage() newMarker.width = CGFloat(NMF_MARKER_SIZE_AUTO) newMarker.height = CGFloat(NMF_MARKER_SIZE_AUTO) newMarker.zIndex = 1 } } + // MARK: loadThumbnailAndUpdateMarker + + private func loadThumbnailAndUpdateMarker(placeID: String, + url: URL, + marker: NMFMarker) { + if let cachedImage = markerImageCache[placeID] { + marker.iconImage = cachedImage + return + } + + Task { @MainActor in + guard let thumbnailImage = await imageRepository.loadImage(from: url) else { + return + } + + let markerView = PlaceMarkerView(isSelected: false, thumbnail: thumbnailImage) + guard let uiImage = markerView.asMarkerImage(size: defaultMarkerSize) else { + return + } + + let overlayImage = NMFOverlayImage(image: uiImage) + self.markerImageCache[placeID] = overlayImage + + let selectedMarkerView = PlaceMarkerView(isSelected: true, thumbnail: thumbnailImage) + if let selectedUIImage = selectedMarkerView.asMarkerImage(size: selectedMarkerSize) { + self.selectedMarkerImageCache[placeID] = NMFOverlayImage(image: selectedUIImage) + } + + if marker.mapView != nil { + marker.iconImage = overlayImage + } + } + + } + // MARK: - DeInit deinit { @@ -202,6 +258,8 @@ class NaverMapViewCoordinator: NSObject { marker.mapView = nil } currentMarkers.removeAll() + markerImageCache.removeAll() + selectedMarkerImageCache.removeAll() } } From 6b524e7711e97a031efee2010d0c4e8f00feded4 Mon Sep 17 00:00:00 2001 From: logicHoon Date: Thu, 1 Jan 2026 16:48:49 +0900 Subject: [PATCH 5/9] =?UTF-8?q?fix:=20map=ED=99=94=EB=A9=B4=EC=97=90?= =?UTF-8?q?=EC=84=9C=20PlaceDetailSheetView=EA=B0=80=20dismiss=EB=90=98?= =?UTF-8?q?=EA=B3=A0=20=EC=97=AC=EC=A0=84=ED=9E=88=20=ED=95=B4=EB=8B=B9=20?= =?UTF-8?q?=EC=9E=A5=EC=86=8C=EA=B0=80=20selectedList=EC=97=90=20=ED=8F=AC?= =?UTF-8?q?=ED=95=A8=EB=90=98=EB=8A=94=20=EC=97=90=EB=9F=AC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LeaveGo/LeaveGo/Features/PlaceMap/MapView.swift | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/LeaveGo/LeaveGo/Features/PlaceMap/MapView.swift b/LeaveGo/LeaveGo/Features/PlaceMap/MapView.swift index c459106..41a1fd3 100644 --- a/LeaveGo/LeaveGo/Features/PlaceMap/MapView.swift +++ b/LeaveGo/LeaveGo/Features/PlaceMap/MapView.swift @@ -10,12 +10,29 @@ import SwiftUI struct MapView: View { @State private var viewModel = MapViewModel() +// @State private var selectedPlace: PlaceDTO? + + private var selectedPlaceBinding: Binding { + Binding( + get: { viewModel.selectedPlace }, + set: { newValue in + // nil이 전달되면 명시적으로 선택 해제 + Task { @MainActor in + await viewModel.setSelectedPlaceID(id: newValue?.id) + } + } + ) + } var body: some View { ZStack { NaverMapView() .environment(viewModel) + .sheet(item: selectedPlaceBinding) { place in + PlaceDetailSheetView(place: place, buttonTitle: "경로 찾기") + .presentationDetents([.medium, .large]) + } // Map Launch Screen if !viewModel.isLocationLoaded { From f41bf85d52fabb47a31e881352d51f5b87cc45f1 Mon Sep 17 00:00:00 2001 From: logicHoon Date: Thu, 1 Jan 2026 16:57:29 +0900 Subject: [PATCH 6/9] =?UTF-8?q?docs:=20NaverMapViewCoordinator=EC=9D=98=20?= =?UTF-8?q?loadThumbnailAndUpdateMarker=20=EC=84=A4=EB=AA=85=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PlaceMap/Coordinator/NaverMapViewCoordinator.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/LeaveGo/LeaveGo/Features/PlaceMap/Coordinator/NaverMapViewCoordinator.swift b/LeaveGo/LeaveGo/Features/PlaceMap/Coordinator/NaverMapViewCoordinator.swift index 09edd9e..c0143c5 100644 --- a/LeaveGo/LeaveGo/Features/PlaceMap/Coordinator/NaverMapViewCoordinator.swift +++ b/LeaveGo/LeaveGo/Features/PlaceMap/Coordinator/NaverMapViewCoordinator.swift @@ -217,6 +217,14 @@ class NaverMapViewCoordinator: NSObject { // MARK: loadThumbnailAndUpdateMarker + /// 썸네일 이미지가 그려진 장소 marker를 생성해 캐싱합니다. + /// + /// - Parameters: + /// - placeID: 작업을 진행할 장소의 ID + /// - url: 장소 썸네일 이미지의 url + /// - marker: 다시 그릴 Marker의 class 참조 + /// + /// - Note: 초기 시점엔 썸네일 이미지가 없는 'defaultMarkerImage' 프로퍼티를 올려놓습니다. 이후 장소 썸네일 이미지 Load가 끝나면 이 함수가 실행되어 썸네일이 포함된 마커 이미지로 다시 그립니다. private func loadThumbnailAndUpdateMarker(placeID: String, url: URL, marker: NMFMarker) { From ef219b30730755b94339f64f853c1e3225eaeb3f Mon Sep 17 00:00:00 2001 From: logicHoon Date: Thu, 1 Jan 2026 18:57:01 +0900 Subject: [PATCH 7/9] =?UTF-8?q?feat:=20=EC=A7=80=EC=97=AD=20=EB=A7=88?= =?UTF-8?q?=EC=BB=A4=EB=A5=BC=20=EC=84=A0=ED=83=9D=ED=95=98=EA=B3=A0=20pla?= =?UTF-8?q?cedetailsheetview=EA=B0=80=20=EB=82=98=ED=83=80=EB=82=AC?= =?UTF-8?q?=EC=9D=84=20=EB=95=8C=20=EC=A7=80=EB=8F=84=20=ED=8F=AC=EC=BB=A4?= =?UTF-8?q?=EC=8B=B1=EC=9D=84=20=ED=95=B4=EB=8B=B9=20=EB=A7=88=EC=BB=A4?= =?UTF-8?q?=EB=A1=9C(sheetview=20=EC=9C=84=EB=A1=9C)=20=EC=9D=B4=EB=8F=99?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Coordinator/NaverMapViewCoordinator.swift | 3 +++ .../LeaveGo/Features/PlaceMap/MapView.swift | 4 +++ .../Features/PlaceMap/NaverMapView.swift | 25 ++++++++++++++++++- .../PlaceMap/ViewModels/MapViewModel.swift | 16 ++++++++++++ .../CLLocationCoordinate2D+Extension.swift | 15 +++++++++++ 5 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 LeaveGo/LeaveGo/Shared/Extension/CLLocationCoordinate2D+Extension.swift diff --git a/LeaveGo/LeaveGo/Features/PlaceMap/Coordinator/NaverMapViewCoordinator.swift b/LeaveGo/LeaveGo/Features/PlaceMap/Coordinator/NaverMapViewCoordinator.swift index c0143c5..48005c3 100644 --- a/LeaveGo/LeaveGo/Features/PlaceMap/Coordinator/NaverMapViewCoordinator.swift +++ b/LeaveGo/LeaveGo/Features/PlaceMap/Coordinator/NaverMapViewCoordinator.swift @@ -39,6 +39,9 @@ class NaverMapViewCoordinator: NSObject { // 상태 플래그 var hasMovedToUserLocation = false + /// 마지막으로 이동한 카메라 위치 (중복 이동 방지용) + var lastTargetCameraLocation: CLLocationCoordinate2D? + // MARK: - 마커 이미지 캐시 /// 장소별 마커 이미지 캐시 diff --git a/LeaveGo/LeaveGo/Features/PlaceMap/MapView.swift b/LeaveGo/LeaveGo/Features/PlaceMap/MapView.swift index 41a1fd3..86bffbc 100644 --- a/LeaveGo/LeaveGo/Features/PlaceMap/MapView.swift +++ b/LeaveGo/LeaveGo/Features/PlaceMap/MapView.swift @@ -32,6 +32,10 @@ struct MapView: View { .sheet(item: selectedPlaceBinding) { place in PlaceDetailSheetView(place: place, buttonTitle: "경로 찾기") .presentationDetents([.medium, .large]) + .onAppear { + // Sheet가 나타날 때 해당 마커 위치로 카메라 이동 + viewModel.moveCameraToSelectedPlace() + } } // Map Launch Screen diff --git a/LeaveGo/LeaveGo/Features/PlaceMap/NaverMapView.swift b/LeaveGo/LeaveGo/Features/PlaceMap/NaverMapView.swift index cda0c34..351beac 100644 --- a/LeaveGo/LeaveGo/Features/PlaceMap/NaverMapView.swift +++ b/LeaveGo/LeaveGo/Features/PlaceMap/NaverMapView.swift @@ -30,7 +30,8 @@ struct NaverMapView: UIViewRepresentable { let view = NMFNaverMapView() // 기본 설정 - view.showZoomControls = false +// view.showZoomControls = false + // TODO: custom zoom controller 도입 예정 view.mapView.positionMode = .direction view.mapView.zoomLevel = 15 view.mapView.isIndoorMapEnabled = true @@ -77,6 +78,15 @@ struct NaverMapView: UIViewRepresentable { func updateUIView(_ uiView: NMFNaverMapView, context: Context) { let coordinator = context.coordinator + /// marker가 PlaceDetailSheetView에 가려지지 않도록 contentInset을 sheet 높이 만큼 보정합니다. + if viewModel.selectedPlaceID != nil { + let sheetHeight = UIScreen.main.bounds.height * 0.4 + uiView.mapView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: sheetHeight, right: 0) + } else { + uiView.mapView.contentInset = .zero + } + + // 사용자 위치 이동 처리 if let location = viewModel.userLocation, !coordinator.hasMovedToUserLocation { let userCoord = NMGLatLng(lat: location.latitude, @@ -92,6 +102,19 @@ struct NaverMapView: UIViewRepresentable { coordinator.hasMovedToUserLocation = true } + // 카메라 이동 처리 + if let targetLocation = viewModel.targetCameraLocation, + coordinator.lastTargetCameraLocation != targetLocation { + let targetCoord = NMGLatLng(lat: targetLocation.latitude, + lng: targetLocation.longitude) + let cameraUpdate = NMFCameraUpdate(scrollTo: targetCoord) + cameraUpdate.animation = .easeIn + cameraUpdate.animationDuration = 0.3 + uiView.mapView.moveCamera(cameraUpdate) + + coordinator.lastTargetCameraLocation = targetLocation + } + coordinator.updateMarkers(on: uiView.mapView, with: viewModel.placeList) coordinator.updateSelectedMarker( diff --git a/LeaveGo/LeaveGo/Features/PlaceMap/ViewModels/MapViewModel.swift b/LeaveGo/LeaveGo/Features/PlaceMap/ViewModels/MapViewModel.swift index 44de609..e8ae63b 100644 --- a/LeaveGo/LeaveGo/Features/PlaceMap/ViewModels/MapViewModel.swift +++ b/LeaveGo/LeaveGo/Features/PlaceMap/ViewModels/MapViewModel.swift @@ -24,6 +24,9 @@ final class MapViewModel { public var selectedPlaceID: String? private var previousSelectedPlaceID: String? + /// 카메라를 이동시킬 목표 좌표 (설정하면 지도가 해당 위치로 이동) + public var targetCameraLocation: CLLocationCoordinate2D? + public var selectedPlace: PlaceDTO? { placeList.first { $0.id == selectedPlaceID } } @@ -48,6 +51,19 @@ final class MapViewModel { return previousSelectedPlaceID } + /// 선택된 장소의 위치로 카메라를 이동시킵니다 + func moveCameraToSelectedPlace() { + guard let place = selectedPlace, + let latStr = place.mapY, + let lngStr = place.mapX, + let lat = Double(latStr), + let lng = Double(lngStr) else { + return + } + + targetCameraLocation = CLLocationCoordinate2D(latitude: lat, longitude: lng) + } + // MARK: - LocationManager @MainActor diff --git a/LeaveGo/LeaveGo/Shared/Extension/CLLocationCoordinate2D+Extension.swift b/LeaveGo/LeaveGo/Shared/Extension/CLLocationCoordinate2D+Extension.swift new file mode 100644 index 0000000..5495b46 --- /dev/null +++ b/LeaveGo/LeaveGo/Shared/Extension/CLLocationCoordinate2D+Extension.swift @@ -0,0 +1,15 @@ +// +// CLLocation+Extension.swift +// LeaveGo +// +// Created by 이치훈 on 1/1/26. +// + +import CoreLocation + +// CLLocationCoordinate2D를 Equatable로 확장 (카메라 위치 비교용) +extension CLLocationCoordinate2D: @retroactive Equatable { + public static func == (lhs: CLLocationCoordinate2D, rhs: CLLocationCoordinate2D) -> Bool { + return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude + } +} From ad111881bc91bbd983de4e732b9fe8cb54f4daa2 Mon Sep 17 00:00:00 2001 From: logicHoon Date: Fri, 2 Jan 2026 20:05:03 +0900 Subject: [PATCH 8/9] =?UTF-8?q?fix:=20=EB=A7=88=EC=BB=A4=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=EC=8B=9C=20=EC=9D=B4=EC=A0=84=20deselect=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=20=EB=A7=88=EC=BB=A4=EA=B0=80=20=EB=92=A4?= =?UTF-8?q?=EC=97=90=20=EB=82=A8=EC=95=84=EC=9E=88=EB=8A=94=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Coordinator/NaverMapViewCoordinator.swift | 49 ++++++++++++++----- .../Features/PlaceMap/NaverMapView.swift | 9 ++-- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/LeaveGo/LeaveGo/Features/PlaceMap/Coordinator/NaverMapViewCoordinator.swift b/LeaveGo/LeaveGo/Features/PlaceMap/Coordinator/NaverMapViewCoordinator.swift index 48005c3..8579579 100644 --- a/LeaveGo/LeaveGo/Features/PlaceMap/Coordinator/NaverMapViewCoordinator.swift +++ b/LeaveGo/LeaveGo/Features/PlaceMap/Coordinator/NaverMapViewCoordinator.swift @@ -42,7 +42,7 @@ class NaverMapViewCoordinator: NSObject { /// 마지막으로 이동한 카메라 위치 (중복 이동 방지용) var lastTargetCameraLocation: CLLocationCoordinate2D? - // MARK: - 마커 이미지 캐시 + // MARK: 마커 이미지 캐시 /// 장소별 마커 이미지 캐시 private var markerImageCache: [String: NMFOverlayImage] = [:] @@ -71,6 +71,8 @@ class NaverMapViewCoordinator: NSObject { return NMFOverlayImage(image: uiImage) }() + private var markerUpdateTask: Task? + init(viewModel: MapViewModel) { naverMapViewDelegate = viewModel } @@ -199,22 +201,43 @@ class NaverMapViewCoordinator: NSObject { /// /// - Note: 캐시된 이미지를 재사용하므로 이미지 재생성 비용이 발생하지 않습니다. public func updateSelectedMarker(selectedID: String?, previousSelectedID: String?) { - // 1. 이전 선택 마커를 기본 스타일로 복원 + + markerUpdateTask?.cancel() + if let prevID = previousSelectedID, let prevMarker = currentMarkers[prevID] { - prevMarker.iconImage = markerImageCache[prevID] ?? defaultMarkerImage ?? NMFOverlayImage() - prevMarker.width = CGFloat(NMF_MARKER_SIZE_AUTO) - prevMarker.height = CGFloat(NMF_MARKER_SIZE_AUTO) - prevMarker.zIndex = 0 + prevMarker.hidden = true } - // 2. 새로운 선택 마커를 강조 스타일로 변경 if let newID = selectedID, let newMarker = currentMarkers[newID] { - newMarker.iconImage = selectedMarkerImageCache[newID] ?? selectedMarkerImage ?? NMFOverlayImage() - newMarker.width = CGFloat(NMF_MARKER_SIZE_AUTO) - newMarker.height = CGFloat(NMF_MARKER_SIZE_AUTO) - newMarker.zIndex = 1 + newMarker.hidden = true + } + + markerUpdateTask = Task { @MainActor in + try? await Task.sleep(for: .milliseconds(50)) + + guard !Task.isCancelled else { return } + + // 1. 이전 선택 마커를 기본 스타일(deselected)로 복원 + if let prevID = previousSelectedID, + let prevMarker = self.currentMarkers[prevID] { + prevMarker.iconImage = self.markerImageCache[prevID] ?? self.defaultMarkerImage ?? NMFOverlayImage() + prevMarker.width = CGFloat(NMF_MARKER_SIZE_AUTO) + prevMarker.height = CGFloat(NMF_MARKER_SIZE_AUTO) + prevMarker.zIndex = 0 + prevMarker.hidden = false + } + + // 2. 새로 선택된 마커를 강조 스타일(selected)로 변경 + if let newID = selectedID, + let newMarker = self.currentMarkers[newID] { + newMarker.iconImage = selectedMarkerImageCache[newID] ?? selectedMarkerImage ?? NMFOverlayImage() + newMarker.width = CGFloat(NMF_MARKER_SIZE_AUTO) + newMarker.height = CGFloat(NMF_MARKER_SIZE_AUTO) + newMarker.zIndex = 1 + newMarker.hidden = false + } } } @@ -241,6 +264,7 @@ class NaverMapViewCoordinator: NSObject { return } + // deselected 버전 marker image let markerView = PlaceMarkerView(isSelected: false, thumbnail: thumbnailImage) guard let uiImage = markerView.asMarkerImage(size: defaultMarkerSize) else { return @@ -249,6 +273,7 @@ class NaverMapViewCoordinator: NSObject { let overlayImage = NMFOverlayImage(image: uiImage) self.markerImageCache[placeID] = overlayImage + // selected 버전 marker image let selectedMarkerView = PlaceMarkerView(isSelected: true, thumbnail: thumbnailImage) if let selectedUIImage = selectedMarkerView.asMarkerImage(size: selectedMarkerSize) { self.selectedMarkerImageCache[placeID] = NMFOverlayImage(image: selectedUIImage) @@ -258,7 +283,6 @@ class NaverMapViewCoordinator: NSObject { marker.iconImage = overlayImage } } - } // MARK: - DeInit @@ -271,6 +295,7 @@ class NaverMapViewCoordinator: NSObject { currentMarkers.removeAll() markerImageCache.removeAll() selectedMarkerImageCache.removeAll() + markerUpdateTask?.cancel() } } diff --git a/LeaveGo/LeaveGo/Features/PlaceMap/NaverMapView.swift b/LeaveGo/LeaveGo/Features/PlaceMap/NaverMapView.swift index 351beac..b475e9e 100644 --- a/LeaveGo/LeaveGo/Features/PlaceMap/NaverMapView.swift +++ b/LeaveGo/LeaveGo/Features/PlaceMap/NaverMapView.swift @@ -12,6 +12,7 @@ struct NaverMapView: UIViewRepresentable { @Environment(MapViewModel.self) private var viewModel + // MARK: makeUIView /// Naver 지도의 UIKit 뷰를 생성하고 초기 설정을 수행합니다. /// /// 이 메서드는 UIViewRepresentable 프로토콜의 필수 구현으로, @@ -30,12 +31,12 @@ struct NaverMapView: UIViewRepresentable { let view = NMFNaverMapView() // 기본 설정 -// view.showZoomControls = false - // TODO: custom zoom controller 도입 예정 view.mapView.positionMode = .direction view.mapView.zoomLevel = 15 view.mapView.isIndoorMapEnabled = true - view.showLocationButton = true + view.showZoomControls = false + view.showLocationButton = false + // TODO: custom zoom controller 도입 예정 view.showCompass = true // 지도 터치 델리게이트 설정 @@ -56,6 +57,7 @@ struct NaverMapView: UIViewRepresentable { return view } + // MARK: updateUIView /// 이 메서드는 UIViewRepresentable 프로토콜의 필수 구현으로, /// SwiftUI의 상태(@State, @Binding, @Environment 등)가 변경될 때마다 자동으로 호출됩니다. /// ViewModel의 변경사항을 UIKit 기반 지도 뷰에 반영하는 역할을 합니다. @@ -122,6 +124,7 @@ struct NaverMapView: UIViewRepresentable { previousSelectedID: viewModel.getPreviousSelectedPlaceID()) } + // MARK: makeCoordinator /// Coordinator 생성 func makeCoordinator() -> NaverMapViewCoordinator { NaverMapViewCoordinator(viewModel: viewModel) From b2c392e4460d83ebcf55284e3de88c07b2b72ba6 Mon Sep 17 00:00:00 2001 From: logicHoon Date: Fri, 2 Jan 2026 20:43:18 +0900 Subject: [PATCH 9/9] =?UTF-8?q?design:=20=EC=8D=B8=EB=84=A4=EC=9D=BC?= =?UTF-8?q?=EC=9D=B4=20=EC=97=86=EB=8A=94=20selected=20=EB=A7=88=ED=81=ACv?= =?UTF-8?q?iew=EA=B0=80=20=EC=9E=98=20=EB=B3=B4=EC=9D=B4=EB=8F=84=EB=A1=9D?= =?UTF-8?q?=20=EA=B8=B0=EB=B3=B8=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EB=92=A4?= =?UTF-8?q?=EC=97=90=20=EB=8B=A4=EB=A5=B8=EC=83=89=EC=9D=98=20=EB=B0=B0?= =?UTF-8?q?=EA=B2=BD=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PlaceMap/Components/PlaceMarkerView.swift | 5 +++ .../lgSubAccentColor.colorset/Contents.json | 38 +++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 LeaveGo/Resource/Assets.xcassets/Color/lgSubAccentColor.colorset/Contents.json diff --git a/LeaveGo/LeaveGo/Features/PlaceMap/Components/PlaceMarkerView.swift b/LeaveGo/LeaveGo/Features/PlaceMap/Components/PlaceMarkerView.swift index 2c513c7..91abd5b 100644 --- a/LeaveGo/LeaveGo/Features/PlaceMap/Components/PlaceMarkerView.swift +++ b/LeaveGo/LeaveGo/Features/PlaceMap/Components/PlaceMarkerView.swift @@ -33,6 +33,11 @@ struct PlaceMarkerView: View { if let thumbnail { thumbnailImage(Image(uiImage: thumbnail)) } else { + Circle() + .fill(isSelected ? .lgSubAccent : .clear) + .frame(width: 40, + height: 40) + thumbnailImage(Image("img_logoWithNoBg")) } } diff --git a/LeaveGo/Resource/Assets.xcassets/Color/lgSubAccentColor.colorset/Contents.json b/LeaveGo/Resource/Assets.xcassets/Color/lgSubAccentColor.colorset/Contents.json new file mode 100644 index 0000000..b551710 --- /dev/null +++ b/LeaveGo/Resource/Assets.xcassets/Color/lgSubAccentColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "111", + "green" : "186", + "red" : "244" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "display-p3", + "components" : { + "alpha" : "1.000", + "blue" : "111", + "green" : "186", + "red" : "244" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +}