네이버 지도에서 marker를 클릭했을 때 해당 마커의 정보를 사용할 수 있습니다
클러스터링을 통해서 지도의 마커를 한꺼번에 묶을 수 있는 것을 확인했었습니다. 참고
이제 해당 마커와 클러스터링을 완료했지만 만약 해당 마커를 클릭했을 때 해당 마커의 정보를 가지고 특정 동작을 수행하도록 원한다면 어떻게 해야될까? 이 방법에 대해서 다루겠습니다.
클러스터링
먼저 NMCClusterer 객체를 만들어 클러스터링을 작업을 진행하였습니다, 그리고 클러스터링 된 마커들은 지도에 표시됩니다. 다음은 지도에서 사용되는 마커의 정보를 정의한 클래스 입니다.
class ItemKey: NSObject, NMCClusteringKey {
let identifier: Int
let position: NMGLatLng
...
}
class ItemData: NSObject {
let name: String
let date: Date // 확진자 발생 날짜
let region: String // 확진자 발생 지역
let cases: Int // 확진자 수
init(name: String, date: Date, region: String, cases: Int) {
self.name = name
self.date = date
self.region = region
self.cases = cases
}
}
여기서 클러스터링을 하기 위해서는 클래스가 NMCClusteringKey 프로토콜을 준수해야 하는데 이 프로토콜을 좀 더 알아보겠습니다.
NMClusteringKey 프로토콜은 여러 데이터를 중복해서 클러스터링할 수 있도록 만들어주는 프로토콜입니다.
이 프로토콜은 다른 데이터지만 동일한 좌표의 서로 다른 데이터일 경우 중복하여 클러스터링하는 것을 방지하기 위해서 NSObject.isEqual 과 NSObject.hash 를 구현하는 것을 권장하고 있다고 적혀있습니다.
추가로 헤더 파일의 아래에 확인해보면 position이 존재하는 것을 알 수 있습니다. 이것은 이 프로토콜을 준수하면 반드시 position (데이터의 좌표)를 만들어야 함을 알 수 있습니다.
클러스터링 시작
이렇게 구현되었다면 이제 지도에 띄울 클러스터링을 진행하겠습니다. 코드는 아래와 같습니다.
func makeClusterer() {
let builder = NMCComplexBuilder<ItemKey>()
builder.minClusteringZoom = 9
builder.maxClusteringZoom = 16
builder.maxScreenDistance = 200
builder.clusterMarkerUpdater = self
builder.leafMarkerUpdater = self
builder.markerManager = MarkerManager()
self.clusterer = builder.build()
var keyTagMap = [ItemKey: ItemData]()
keyTagMap = [
ItemKey(identifier: 1, position: NMGLatLng(lat: 37.372, lng: 127.113)): ItemData(name: "1번 확진자", date: .now + 1, region: "발생지역1", cases: 1),
ItemKey(identifier: 2, position: NMGLatLng(lat: 37.366, lng: 127.106)): ItemData(name: "2번 확진자", date: .now + 3, region: "발생지역2", cases: 2),
ItemKey(identifier: 3, position: NMGLatLng(lat: 37.365, lng: 127.157)): ItemData(name: "3번 확진자", date: .now + 4, region: "발생지역3", cases: 1),
ItemKey(identifier: 4, position: NMGLatLng(lat: 37.361, lng: 127.105)): ItemData(name: "4번 확진자", date: .now + 8, region: "발생지역4", cases: 4),
ItemKey(identifier: 5, position: NMGLatLng(lat: 37.368, lng: 127.110)): ItemData(name: "5번 확진자", date: .now + 11, region: "발생지역5", cases: 5),
ItemKey(identifier: 6, position: NMGLatLng(lat: 37.360, lng: 127.106)): ItemData(name: "6번 확진자", date: .now + 14, region: "발생지역6", cases: 2),
ItemKey(identifier: 7, position: NMGLatLng(lat: 37.363, lng: 127.111)): ItemData(name: "7번 확진자", date: .now + 29, region: "발생지역7", cases: 9)
]
self.clusterer?.addAll(keyTagMap)
self.clusterer?.mapView = self.view.mapView
}
단일 마커
다음으로는 단일 마커의 대한 정보를 타나내기 위한 updateLeafMarker 메서드를 구현해줍니다.
이때 updateLeafMarker를 사용하기 위해서는 클래스가 NMCLeafMarkerUpdater 프로토콜을 준수하고 init() 메서드에 builder.leafMarkerUpdater = self 를 구현해야 합니다.
func updateLeafMarker(_ info: NMCLeafMarkerInfo, _ marker: NMFMarker) {
marker.iconImage = NMF_MARKER_IMAGE_GREEN
marker.touchHandler = { [weak self] (overlay: NMFOverlay) -> Bool in
print("탭")
return true
}
}
클러스터링된 마커
다음으로는 클러스터링된 마커의 대한 정보를 타나내기 위한 updateClusterMarker 메서드를 구현해줍니다.
updateLeafMarker와 마찬가지로 NMCClusterMarkerUpdater 프로토콜을 준수하고 init() 메서드에 builder.clusterMarkerUpdater = self 를 구현해야 합니다.
func updateClusterMarker(_ info: NMCClusterMarkerInfo, _ marker: NMFMarker) {
marker.captionText = "클러스터링에 포함된 데이터 수:" + String(info.size)
marker.captionTextSize = 16
marker.touchHandler = { (overlay: NMFOverlay) -> Bool in
print("클러스터링 마커 터치")
return true
}
if info.size > 0 && info.size < 3 {
marker.iconImage = NMF_MARKER_IMAGE_CLUSTER_LOW_DENSITY
} else if info.size < 10 {
marker.iconImage = NMF_MARKER_IMAGE_CLUSTER_MEDIUM_DENSITY
} else {
marker.iconImage = NMF_MARKER_IMAGE_CLUSTER_HIGH_DENSITY
}
}
클러스터링된 결과
이제 코드를 실행하게되면 다음과 같은 결과가 나옵니다.
마커 정보 가져오기
이제 클러스터링된 마커와 단일 마커에 대한 정보를 사용할 차례입니다.
마커에 대한 정보는 이미 아래 코드처럼 정의하여 마커를 만들었었습니다, 이 정보들을 이제 사용만 하면 됩니다.
keyTagMap = [
ItemKey(identifier: 1, position: NMGLatLng(lat: 37.372, lng: 127.113)): ItemData(name: "1번 확진자", date: .now + 1, region: "발생지역1", cases: 1),
ItemKey(identifier: 2, position: NMGLatLng(lat: 37.366, lng: 127.106)): ItemData(name: "2번 확진자", date: .now + 3, region: "발생지역2", cases: 2),
ItemKey(identifier: 3, position: NMGLatLng(lat: 37.365, lng: 127.157)): ItemData(name: "3번 확진자", date: .now + 4, region: "발생지역3", cases: 1),
ItemKey(identifier: 4, position: NMGLatLng(lat: 37.361, lng: 127.105)): ItemData(name: "4번 확진자", date: .now + 8, region: "발생지역4", cases: 4),
ItemKey(identifier: 5, position: NMGLatLng(lat: 37.368, lng: 127.110)): ItemData(name: "5번 확진자", date: .now + 11, region: "발생지역5", cases: 5),
ItemKey(identifier: 6, position: NMGLatLng(lat: 37.360, lng: 127.106)): ItemData(name: "6번 확진자", date: .now + 14, region: "발생지역6", cases: 2),
ItemKey(identifier: 7, position: NMGLatLng(lat: 37.363, lng: 127.111)): ItemData(name: "7번 확진자", date: .now + 29, region: "발생지역7", cases: 9)
]
클러스터링을 할 때 위에서 만들어진 딕셔너리 배열 값을 받는 addAll이라는 메서드를 사용하였습니다. 이 메서드는 여러 데이터를 한 번에 추가하며 [key : tag] 처럼 데이터의 키는 key, 값은 tag로 저장되어 있음을 알 수 있습니다.
이것을 알았으니 이제 저장된 데이터를 가져와 사용할 차례입니다. updateLeafMarker 메서드의 경우 단일 마커의 정보를 담고 있는 info 인자값을 통해서 데이터를 가져옵니다.
우리는 이미 위에서 저장한 딕셔너리인 KeyTagMap의 데이터에서 tag는 ItemData 타입을 저장하였음을 알 수 있습니다. 그렇기 때문에 아래처럼 info.tag 를 ItemData 타입으로 다운 캐스팅해줍니다. 그러면 이제 tag에 저장한 데이터를 사용할 수 있게 됩니다.
func updateLeafMarker(_ info: NMCLeafMarkerInfo, _ marker: NMFMarker) {
let tag = info.tag as! ItemData
marker.captionText = "확진자수:\(tag.cases)"
marker.iconImage = NMF_MARKER_IMAGE_GREEN
marker.touchHandler = { [weak self] (overlay: NMFOverlay) -> Bool in
self?.tappedMarkerInfo = info
print("info tag cases:\(tag.cases)")
print("info tag name: \(tag.name)")
print("info tag date: \(tag.date)")
print("info tag region: \(tag.region)")
return true
}
}
마찬가지로 클러스터링된 마커 또한 같은 로직으로 구현하면 됩니다.
마커를 탭하였을 때 sheet로 마커 정보 띄우기
위에처럼 구현이 완성됐다면 마커를 탭했을 때 .sheet를 사용하여 뷰에 원하는 정보를 띄울 수 있겠다는 생각을 할 수 있습니다. 아래에서는 이러한 작업을 진행합니다.
만약 단일 마커가 탭되면 markerTapped를 toggle하여 sheet 메서드가 실행되어 뷰가 만들어지게끔 하는 코드입니다.
만들어진 뷰에서는 tappedMarkerInfo에 저장된 데이터를 가지고 정보를 화면에 띄웁니다.
@Observable
class Coordinator: NSObject, NMFMapViewOptionDelegate, NMCClusterMarkerUpdater, NMCLeafMarkerUpdater {
...
func updateLeafMarker(_ info: NMCLeafMarkerInfo, _ marker: NMFMarker) {
let tag = info.tag as! ItemData
marker.captionText = tag.name
marker.iconImage = NMF_MARKER_IMAGE_GREEN
marker.touchHandler = { [weak self] (overlay: NMFOverlay) -> Bool in
self?.tappedMarkerInfo = info
self?.tappedMarkerTag = tag
self?.markerTapped.toggle()
return true
}
}
이후 마커의 정보를 나타내는 간단한 뷰를 만듭니다.
struct MarkerDetailView: View {
@Environment(Coordinator.self) var coordinator
var body: some View {
VStack {
if let tag = coordinator.tappedMarkerTag {
Text("확진자 이름:\(tag.name)")
Text("위험등급:\(tag.cases)")
Text("양성판정:\(tag.date)")
Text("발생지역:\(tag.region)")
}
}
}
}
물론 모든 뷰에서 @Observable 메크로가 선언된 클래스를 사용할 수 있도록 아래처럼 지정합니다.
@main
struct DiseaseTrackerMapApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.environment(Coordinator())
}
}
}
이제 테스트를 해봅니다. 만약 5번 확진자를 클릭하게 되면 5번 확진자에 대한 정보를 나타내는 뷰가 올라올 것입니다.
ItemKey(identifier: 5, position: NMGLatLng(lat: 37.368, lng: 127.110)): ItemData(name: "5번 확진자", date: .now + 11, region: "발생지역5", cases: 5)
이후 상황에 맞게 UI를 업데이트하여 사용하면 됩니다.
'SwiftUI' 카테고리의 다른 글
지도에서 내 현재 위치 가져오기 (네이버 지도) (0) | 2024.05.09 |
---|---|
카메라를 이용하여 QR code 스캔하기 (0) | 2024.05.07 |
네이버 지도 클러스터링 (0) | 2024.05.02 |
네이버 지도 시작하기 (0) | 2024.05.01 |
커스텀 원형 테두리 만들기 (1) | 2024.05.01 |