
⎮ 팀 프로젝트 4일차
지도에서 킥보드를 선택할 수 있게 하려면, 지도 위에 킥보드가 올라가 있어야 한다.
네이버 Map 문서를 보니, 마커라는 기능이 있어 이를 활용해보고자 한다.

⎮ 네이버 마커
https://navermaps.github.io/ios-map-sdk/guide-ko/5-2.html
마커 · NAVER Map iOS SDK
No results matching ""
navermaps.github.io
마커는 지도상의 한 지점을 나타내는 오버레이로, 우리가 네이버 맵을 사용하면서 자주 보던 UI다.
마커는 지도상 좌표에 아이콘 형태로 표시되는데, 무언가 검색했을 때 뜨던 이 아이콘이 바로 마커이다.

// 네이버에서 제공하는 예제
let marker = NMFMarker()
marker.position = NMGLatLng(lat: 37.5670135, lng: 126.9783740)
marker.mapView = mapView
마커는 위의 코드처럼 비교적 간단하게 맵에 띄울 수 있다(lat, lng은 좌표)
⎮ 마커 커스텀하기
마커에는 iconImage라는 속성이 있고, 이를 활용하면 마커를 커스텀할 수 있다.
아이콘을 지정하기에 앞서 먼저 NMGOverlayImage 객체를 만들어야 한다.
(NMGOverlayImage : 오버레이에서 사용할 수 있는 비트맵 이미지를 나타내는 클래스)
// 내가 작성한 코드
marker.iconImage = NMFOverlayImage(name: "kickBoard")
marker.position = NMGLatLng(lat: 37.5557, lng: 126.9708)
위의 코드처럼 이미지를 넣어주고, 좌표를 정해주면 해당 좌표에 마커가 찍히게 된다.
iconTIntColor로 마커의 색상을 변경해줄 수도 있고, 크기의 변경도 가능하다
//네이버에서 제공하는 예제
marker.iconImage = NMF_MARKER_IMAGE_BLACK
marker.iconTintColor = UIColor.red
marker.width = 25
marker.height = 40
⎮ 마커에 이벤트 적용하기
https://navermaps.github.io/ios-map-sdk/guide-ko/5-1.html
오버레이 공통 · NAVER Map iOS SDK
오버레이 공통 오버레이는 지리적 정보를 시각적으로 나타내는 요소로, 개발자가 지도 위에 자유롭게 배치할 수 있습니다. 네이버 지도 SDK는 마커, 정보 창, 셰이프 등 다양한 유형의 오버레이
navermaps.github.io
네이버는 기본적으로, 오버레이의 이벤트를 도와주는 overlay handler를 제공한다.
overlay.touchHandler = { (overlay: NMFOverlay) -> Bool in
print("오버레이 터치됨")
return true
}
overlay에 touchHandler 속성을 지정하면, 터치 이벤트가 발생했을 때 클로저 블럭이 실행된다.
그런데 여기서 궁금한게 하나 생긴다.
왜 overlay를 받아서 Bool Type을 리턴할까?
기본적으로 네이버 지도는 수많은 요소들이 겹쳐있는 View다
요소들은 대부분 터치 이벤트를 소화할 수 있게 되어 있는데
그렇다면, 누가 이 이벤트를 처리할지 결정해야 한다
예를 들어보면,
1. 마커 위에 유저가 손가락으로 터치
2. 1번 상황에서 마커가 터치 이벤트를 받음
3. 마커의 touchHandler 클로저가 실행됨
4. touchHandler 클로저가 true를 리턴
- "나 마커인데, 이벤트 여기서 처리했다~"
- 다른 오버레이가 이 이벤트를 더이상 처리하지 않게 됨
5. touchHandler 클로저가 false를 리턴
- "나 마커인데, 이벤트 처리 안할게"
- 다른 오버레이가 이 이벤트를 처리할 수 있게 됨
// true 리턴 시
marker.touchHandler = { overlay in
// 마커 클릭 시 원하는 동작 수행
print("마커 클릭됨")
return true // 여기서 이벤트를 끝냄 (마커가 터치 이벤트를 전부 처리한 것)
}
// false 리턴 시
marker.touchHandler = { overlay in
print("터치 이벤트를 실행하지 않음")
return false // 터치 이벤트가 지도나 다른 오버레이로 계속 전달됨
}
⎮ userInfo
네이버 맵은 오버레이에 추가적인 정보를 지정할 수 있도록 userInfo 속성을 제공하는데,
userInfo는 기본적으로 딕셔너리 타입이며, 터치 이벤트 리스너와 결합해 사용할 수 있다.
touchHandler에서 userInfo에 저장된 정보를 확인해서 어떤 오버레이가 탭 되었는지를 식별할 수 있어
여러 오버레이가 터치 핸들러를 공유하도록 구현할 수 있다.
// 네이버 제공 예제
let handler: overlayTouchHandler = { (overlay) -> Bool in
print("마커 \(overlay.userInfo["tag"] ?? "tag") 터치됨")
return false
}
marker1.userInfo = ["tag": 1]
marker2.userInfo = ["tag": 2]
marker1.touchHandler = handler
marker2.touchHandler = handler
나는 위의 정보들을 사용하여 아래와 같은 코드를 작성했다.
private func setMarker() {
let markers: [NMFMarker] = [
NMFMarker(position: NMGLatLng(lat: 37.5557, lng: 126.9708)),
NMFMarker(position: NMGLatLng(lat: 37.5560, lng: 126.9720)),
NMFMarker(position: NMGLatLng(lat: 37.5570, lng: 126.9700)),
NMFMarker(position: NMGLatLng(lat: 37.5550, lng: 126.9750)),
NMFMarker(position: NMGLatLng(lat: 37.5530, lng: 126.9686))
]
// 네이버에서 제공하는 Overlay 핸들러(네이버 지도 마커의 터치 이벤트를 처리하는 클로저)
let handler: (NMFOverlay) -> Bool = { [weak self] overlay in
guard let marker = overlay as? NMFMarker else { return false }
// 유저 Info 딕셔너리를 디코딩해서 쓰는 방법 ??? enum이나 어딘가에 담아서 써야 함
let isSelected = marker.userInfo["selected"] as? Bool ?? false
if isSelected {
marker.iconImage = NMFOverlayImage(name: "kickBoard")
marker.userInfo["selected"] = false
} else {
marker.iconImage = NMFOverlayImage(name: "seletedKickBoard")
marker.userInfo["selected"] = true
self?.rentButton.isHidden = false
}
return true
}
for marker in markers {
marker.mapView = myView.mapView
marker.iconImage = NMFOverlayImage(name: "kickBoard")
marker.userInfo["selected"] = false
marker.touchHandler = handler
}
}
상세히 보자면,
// 희망하는 위도와 경도에 배열로 마커를 만들어주었다
let markers: [NMFMarker] = [
NMFMarker(position: NMGLatLng(lat: 37.5557, lng: 126.9708)),
NMFMarker(position: NMGLatLng(lat: 37.5560, lng: 126.9720)),
NMFMarker(position: NMGLatLng(lat: 37.5570, lng: 126.9700)),
NMFMarker(position: NMGLatLng(lat: 37.5550, lng: 126.9750)),
NMFMarker(position: NMGLatLng(lat: 37.5530, lng: 126.9686))
]
/*
네이버에서 제공하는 Overlay 핸들러(네이버 지도 마커의 터치 이벤트를 처리하는 클로저)
핸들러는 NMFOverlay를 받아서 Bool Type을 retur함
*/
let handler: (NMFOverlay) -> Bool = { [weak self] overlay in
// overlay는 다양하므로 NMFMarKer로 다운캐스팅. 만약 캐스팅이 안되면 false 리턴
guard let marker = overlay as? NMFMarker else { return false }
// isSelected라는 상수에 marker.userInfo["seleted"]라는 키가 있는지 확인.
// 값이 없으면 기본적으로 false. 값이 있으면 Bool로 타입 캐스팅을 함
let isSelected = marker.userInfo["selected"] as? Bool ?? false
// isSelected가 true면
if isSelected {
// marker의 iconImage를 기본 이미지로 세팅하고, false로 변경
marker.iconImage = NMFOverlayImage(name: "kickBoard")
marker.userInfo["selected"] = false
// 버튼을 감추는 것으로 설정
self?.rentButton.isHidden = true
// 만약 isSelected가 false면
} else {
// marker의 iconImage를 선택 이미지로 변경하고
marker.iconImage = NMFOverlayImage(name: "seletedKickBoard")
marker.userInfo["selected"] = true
// 버튼을 보여주는 것으로 설정
self?.rentButton.isHidden = false
}
return true
}
// markers 배열에서 marker를 하나씩 순회하면서
for marker in markers {
// marker를 mapView에 반영해주고
marker.mapView = myView.mapView
// iconImage를 설정해주고
marker.iconImage = NMFOverlayImage(name: "kickBoard")
// 기본값을 false로 지정함
marker.userInfo["selected"] = false
// marker에 handler를 달아줌
marker.touchHandler = handler
}
위의 코드는 userInfo["selected"]가 직접 입력하게 되어 있어서 안전한 코드가 아니다.
따라서 휴먼 에러를 줄이기 위해 해당 Key값을 enum Type으로 변경하였다
enum MarkerUserInfo: String {
case seleted = "seleted"
}
private func setMarker() {
let markers: [NMFMarker] = [
NMFMarker(position: NMGLatLng(lat: 37.5557, lng: 126.9708)),
NMFMarker(position: NMGLatLng(lat: 37.5560, lng: 126.9720)),
NMFMarker(position: NMGLatLng(lat: 37.5570, lng: 126.9700)),
NMFMarker(position: NMGLatLng(lat: 37.5550, lng: 126.9750)),
NMFMarker(position: NMGLatLng(lat: 37.5530, lng: 126.9686))
]
// 네이버에서 제공하는 Overlay 핸들러(네이버 지도 마커의 터치 이벤트를 처리하는 클로저)
let handler: (NMFOverlay) -> Bool = { [weak self] overlay in
guard let marker = overlay as? NMFMarker else { return false }
// 유저 Info 딕셔너리를 디코딩해서 쓰는 방법 ??? enum이나 어딘가에 담아서 써야 함
let isSelected = marker.userInfo[MarkerUserInfo.seleted.rawValue] as? Bool ?? false
if isSelected {
marker.iconImage = NMFOverlayImage(name: "kickBoard")
marker.userInfo[MarkerUserInfo.seleted.rawValue] = false
self?.rentButton.isHidden = true
} else {
marker.iconImage = NMFOverlayImage(name: "seletedKickBoard")
marker.userInfo[MarkerUserInfo.seleted.rawValue] = true
self?.rentButton.isHidden = false
}
return true
}
for marker in markers {
marker.mapView = myView.mapView
marker.iconImage = NMFOverlayImage(name: "kickBoard")
marker.userInfo[MarkerUserInfo.seleted.rawValue] = false
marker.touchHandler = handler
}
}
이렇게 작성했더니, 마커에 터치 이벤트를 핸들링할 수 있게 되었다.

그러나 위의 화면처럼 중복 선택이 가능한 문제가 있어
다음 시간에는 예외처리에 집중하고자 한다
'스파르타코딩 클럽 > 팀프로젝트' 카테고리의 다른 글
| 팀프로젝트3 [날씨 앱 만들기(2) - Moya 라이브러리 사용기] (1) | 2025.05.25 |
|---|---|
| 팀프로젝트3 [날씨 앱 만들기(1) - 앱 기획] (0) | 2025.05.24 |
| 팀프로젝트2 [공유 킥보드 앱 만들기(3) - CoreLocation, GeoCoding] (1) | 2025.04.29 |
| 팀프로젝트2 [공유 킥보드 앱 만들기(2) - UITabBarController] (0) | 2025.04.28 |
| 팀프로젝트2 [공유 킥보드 앱 만들기(1) - 디자인, 네이버 Maps API] (0) | 2025.04.26 |