목차
머리말
구현 내용
기능 | 버튼 클릭 시 DB 저장 & 방문 처리 | 모달창 변화 & 어노테이션 선택 여부 |
||
GIF | ||||
내용 | post 메서드에 의한 `addObserver` (1) annotation의 데이터 저장 `saveAnnotationToRealm` (2) annotation 변경 `toggleAnnotation` |
뷰의 생명 주기에 따른 annotation의 선택 여부 결정 (1) 뷰가 나타나려고 할 때 (viewWillAppear) → selectAnnotation (2) 뷰가 사라지려고 할 때(viewWillDisappear) → annotation 선택 해제 deselectAnnotation |
GitHub PR 링크
annotation 저장 시 realm에 저장 및 방문 처리
NotificationCenter를 이용한 MapKit의 annotation 방문 처리
1) 가독성을 위한 extension
편의성을 위해 NSNotification.Name을 `extension`으로 처리했다.
extension Notification.Name {
static let stampButtonClicked = Notification.Name("stampButtonClicked")
static let selectAnnotation = Notification.Name("selectAnnotation")
static let deselectAnnotation = Notification.Name("deselectAnnotation")
}
2) 작동할 `addObserver` 사용
`addObserver`는 이벤트가 발생했음을 감지하는 역할을 한다.
final class StampMapViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(updateAnnotation), name: NSNotification.Name.stampButtonClicked, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(selectAnnotation), name: NSNotification.Name.selectAnnotation, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(deselectAnnotation), name: NSNotification.Name.deselectAnnotation, object: nil)
}
}
3) `post` (feat. 뷰의 생명 주기)
`NotificationCenter.default.post`
이벤트가 발생했다고 알려주는 역할
class PlaceArrivalViewController: BaseViewController {
// ...
override func viewDidLoad() {
super.viewDidLoad()
}
// 뷰가 나타나려고 할 때
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.post(name: NSNotification.Name.selectAnnotation, object: nil)
}
// 뷰가 사라지려고 할 때
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.post(name: NSNotification.Name.deselectAnnotation, object: nil)
}
// 스탬프 버튼 클릭 시
@objc
private func stampButtonClicked() {
NotificationCenter.default.post(name: NSNotification.Name.stampButtonClicked, object: nil)
dismiss(animated: true) // 뷰 내리기
}
}
Q & A) removeObserver가 필요한가?
요약) addObserver의 종류에 따라 다르다
(2023-10-16) 업데이트
개발 도중 의문이 들었다.
addObserver를 했으니 반대로 제거를 해야 메모리 유수를 대비할 수 있지 않을까
그래서 공식 문서를 찾아 보았다.
`removeObserver`공식 문서
removeObserver(_:) | Apple Developer Documentation
a) `forName`의 addObserver
이 경우에는 반드시 사용해야 한다
If you used addObserver(forName:object:queue:using:) to create your observer, you should call this method or removeObserver(_:name:object:) before the system deallocates any object that addObserver(forName:object:queue:using:) specifies.
`addObserver(forName:object:queue:using:)` 메서드로 옵저버를 만들었다면, 운영체제가 해당 메서드가 명시한 객체를 메모리에서 해제하기 전에, `removeObserver(_:)` 또는 `removeObserver(_:name:object:)`를 반드시 호출해야 한다.
b) `selector`의 addObserver
이때는 사용하지 않아도 된다
If your app targets iOS 9.0 and later or macOS 10.11 and later, and you used addObserver(_:selector:name:object:), you do not need to unregister the observer. If you forget or are unable to remove the observer, the system cleans up the next time it would have posted to it.
해당 메서드는 운영체제에서 알아서 이를 비워주기 때문에 observer에 대한 제거를 생각하지 않아도 된다는 내용이 있었다. 다행히도 나는 2번 케이스에 해당하여 사용하지 않았다. 휴... 다행이다
코드
(1) 버튼 클릭 시 Realm에 데이터 저장 및 방문 처리
post에 따라 addObserver가 작동
- annotation의 데이터 저장 (saveAnnotationToRealm)
- annotation 변경 (toggleAnnotation)
final class StampMapViewController: BaseViewController {
// ...
@objc
private func updateAnnotation() {
saveAnnotationToRealm()
toggleAnnotation()
}
// 실제 Realm에 데이터를 업데이트 하는 함수
private func saveAnnotationToRealm() {
guard let nearestAnnotation = nearestAnnotation,
let place = nearestAnnotation.place else { return }
let task = PlaceRealm(
title: place.title,
subtitle: place.subtitle,
category: place.category,
address: place.address,
town: place.town,
image: place.image,
url: place.url,
detail: place.detail,
isCreatedAt: Date()
)
repository.createItem(task)
}
// 해당 annotation을 기존에서 제거하고 변경된 annotation view를 적용하여 다시 추가
private func toggleAnnotation() {
guard let nearestAnnotation = nearestAnnotation else { return }
mapView.removeAnnotation(nearestAnnotation)
let visitedAnnotationView = VisitedPlaceAnnotationView(annotation: nearestAnnotation, reuseIdentifier: VisitedPlaceAnnotationView.reuseIdentifier)
guard let visitedAnnotation = visitedAnnotationView.annotation else { return }
mapView.addAnnotation(visitedAnnotation)
placeAnnotations = placeAnnotations.filter { $0 !== nearestAnnotation }
isArrivedToPlace = false
}
}
(+) 실제로 realm 파일에 저장된 annotation 데이터
(2) 모달창 변화에 따른 annotation 선택 처리 (feat. 뷰의 생명주기)
뷰의 생명 주기에 따라 annotation의 선택 처리
- 뷰가 나타나려고 할 때 (viewWillAppear) → annotation 선택(selectAnnotation)
- 뷰가 사라지려고 할 때(viewWillDisappear) → annotation 선택 해제(deselectAnnotation)
class PlaceArrivalViewController: BaseViewController {
// ...
override func viewDidLoad() {
super.viewDidLoad()
}
// 뷰가 나타나려고 할 때
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.post(name: NSNotification.Name.selectAnnotation, object: nil)
}
// 뷰가 사라지려고 할 때
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.post(name: NSNotification.Name.deselectAnnotation, object: nil)
}
// 스탬프 버튼 클릭 시
@objc
private func stampButtonClicked() {
NotificationCenter.default.post(name: NSNotification.Name.stampButtonClicked, object: nil)
dismiss(animated: true) // 뷰 내리기
}
}
final class StampMapViewController: BaseViewController {
// ...
@objc
private func selectAnnotation() {
guard let nearestAnnotation else { return }
mapView.selectAnnotation(nearestAnnotation, animated: true)
}
@objc
private func deselectAnnotation() {
guard let nearestAnnotation else { return }
mapView.deselectAnnotation(nearestAnnotation, animated: true)
}
}
꼬리말
수정일: 2024-01-02 화
댓글