Swift/학습

[Swift] MapView를 클릭했을 때 해당 위치의 주소를 알아내보자!

언클린 2020. 5. 25. 14:25
728x90

이번 글에서 만들어 보고자 하는 것은 MapKit을 사용하여 지도의 임의의 위치를 클릭했을 때 해당 위치의 주소를 추출해 보는 App을 만들어 보겠습니다. 일일이 주소를 입력해서 찾는 것보다 그 위치를 한 번의 클릭만으로 주소를 얻어 보고 싶을 때 한 번 사용해보면 좋을 것 같습니다. 

특별한 알고리즘을 사용하지 않기 때문에 만드는 데는 큰 어려움은 없습니다. 


1. 디자인

저는 간단히 MapView하나와 추출된 주소를 표시할 수 있는 라벨, 이 두 가지만 추가해 보았습니다.

2. 베이스 작성

맵의 임의의 위치를 클릭 했을 때이기 때문에 맵을 클릭할 수 있어야 합니다. 

때문에 저는 Tap 제스처를 추가하였습니다.

import UIKit
import MapKit

class ViewController: UIViewController {
    
    @IBOutlet weak var mapView: MKMapView!
    @IBOutlet weak var locationLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.initView()
    }
    
    private func initView() {
    	// 맵에 Tap 제스처를 추가합니다.
        let tap = UITapGestureRecognizer(target: self, action: #selector(self.didTappedMapView(_:)))
        self.mapView.addGestureRecognizer(tap)
    }
}

//
// MARK:- 맵을 터치 했을 때
//

extension ViewController {
    
    /// 제스처 동작
    @objc
    private func didTappedMapView(_ sender: UITapGestureRecognizer) {
    	// TODO: 제스처 동작 
    }
}

3. 위치 탐색

탐색은 Geocoder의 역 탐색을 사용합니다.

/// 제스처 조작
@objc
private func didTappedMapView(_ sender: UITapGestureRecognizer) {
	let location: CGPoint = sender.location(in: self.mapView) // 클릭 위치를 취득
	let mapPoint: CLLocationCoordinate2D = self.mapView.convert(location, toCoordinateFrom: self.mapView) // 해당 위치를 location으로 변환
        
   	if sender.state == .ended {
    	// 탐색 스타트
   		self.searchLocation(mapPoint)
   	}
}
    
/// 해당 포인트의 주소를 검색
private func searchLocation(_ point: CLLocationCoordinate2D) {
	let geocoder: CLGeocoder = CLGeocoder()
    let location = CLLocation(latitude: point.latitude, longitude: point.longitude)
                
    geocoder.reverseGeocodeLocation(location) { (placeMarks, error) in
    	if error == nil, let marks = placeMarks {
        	marks.forEach { (placeMark) in
            	let annotation = MKPointAnnotation()
                annotation.coordinate = CLLocationCoordinate2D(latitude: point.latitude, longitude: point.longitude)
                    
                self.locationLabel.text =
                """
                \(placeMark.administrativeArea ?? "")
                \(placeMark.locality ?? "")
                \(placeMark.thoroughfare ?? "")
                \(placeMark.subThoroughfare ?? "")
                """
                    
                self.mapView.addAnnotation(annotation)
           	}
      	} else {
        	self.locationLabel.text = "검색 실패"
      	}
  	}
}

추출하고 싶은 데이터는 아래와 같은 항목이 있습니다. 원하시는 데이터를 출력하시면 될 것 같습니다.

4. 전체 코드

이번 예시에서는 하나만의 위치만을 나타내기 위하여 포인트의 삭제도 구현하였습니다.

import UIKit
import MapKit

class ViewController: UIViewController {
    
    @IBOutlet weak var mapView: MKMapView!
    @IBOutlet weak var locationLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.initView()
    }
    
    private func initView() {
        let tap = UITapGestureRecognizer(target: self, action: #selector(self.didTappedMapView(_:)))
        self.mapView.addGestureRecognizer(tap)
    }
}

//
// MARK:- 맵을 터치 했을 때
//

extension ViewController {
    
    /// 제스처 조작
    @objc
    private func didTappedMapView(_ sender: UITapGestureRecognizer) {
        let location: CGPoint = sender.location(in: self.mapView)
        let mapPoint: CLLocationCoordinate2D = self.mapView.convert(location, toCoordinateFrom: self.mapView)
        
        if sender.state == .ended {
            self.searchLocation(mapPoint)
        }
    }
    
    /// 하나만 출력하기 위하여 모든 포인트를 삭제
    private func removeAllAnnotations() {
        let allAnnotations = self.mapView.annotations
        self.mapView.removeAnnotations(allAnnotations)
    }
    
    /// 해당 포인트의 주소를 검색
    private func searchLocation(_ point: CLLocationCoordinate2D) {
        let geocoder: CLGeocoder = CLGeocoder()
        let location = CLLocation(latitude: point.latitude, longitude: point.longitude)
        
        // 포인트 리셋
        self.removeAllAnnotations()
        
        geocoder.reverseGeocodeLocation(location) { (placeMarks, error) in
            if error == nil, let marks = placeMarks {
                marks.forEach { (placeMark) in
                    let annotation = MKPointAnnotation()
                    annotation.coordinate = CLLocationCoordinate2D(latitude: point.latitude, longitude: point.longitude)
                    
                    self.locationLabel.text =
                    """
                    \(placeMark.administrativeArea ?? "")
                    \(placeMark.locality ?? "")
                    \(placeMark.thoroughfare ?? "")
                    \(placeMark.subThoroughfare ?? "")
                    """
                    
                    self.mapView.addAnnotation(annotation)
                }
            } else {
                self.locationLabel.text = "검색 실패"
            }
        }
    }
}

5. 마무리

한 번 아무 위치나 클릭해보시면 해당 위치의 정보가 출력되는 것을 확인하실 수 있습니다.

궁금하신 점이나 지적 사항이 있으신 분은 댓글에 부탁드립니다.

많은 도움이 되었으면 좋겠습니다.

https://developer.apple.com/documentation/corelocation/clgeocoder/1423621-reversegeocodelocation

 

reverseGeocodeLocation(_:completionHandler:) - CLGeocoder | Apple Developer Documentation

Instance Method reverseGeocodeLocation(_:completionHandler:) Submits a reverse-geocoding request for the specified location. DeclarationParameterslocationThe location object containing the coordinate data to look up.completionHandlerThe handler block to ex

developer.apple.com


환경 

Xcode 11.3.1

Swift 5

 

 

 

 

728x90