감자주먹밥

[SwiftUI] 지도와 사용자 위치 정보 받기 + (주소<->좌표 변환) 본문

IOS/SwiftUI

[SwiftUI] 지도와 사용자 위치 정보 받기 + (주소<->좌표 변환)

JustHm 2023. 4. 5. 22:59
728x90

SwiftUI 에서 지도를 띄우기 위해서는 먼저 MapKit을 import 해야한다.

Map(coordinateRegion:, 
    interactionModes:, 
    showsUserLocation:,
    userTrackingMode:,
    annotationItems:,
    annotationContent:)

MapKit을 import 하면 지도를 보여주는 Map 을 사용해 View에 보여줄 수 있다.

  • coordinateRegion: Binding<MKCoordinateRegion> 타입으로 지도가 표시될 위치와 지도의 zoom level을 초기화해 주입한다.
MKCoordinateRegion(center:, span:)
MKCoordinateRegion(center:, latitudinalMeters:, longitudinalMeters:)

MKCoordinateSpan을 사용해 지도 레벨을 정의하거나, meters를 각각 넣어서 정의할 수 있다.

  • interactionModes: .all, .pan, .zoom 의 옵션이 있고, pan은 지도 좌표 이동, zoom은 확대/축소 all은 모든 interaction이 가능하다. 생략하면 고정이다.
  • showsUserLocation: Bool 타입으로 사용자의 위치를 표시할지 말지를 결정한다. 만약 사용한다면 사용자 위치 정보 권한을 얻어야 한다.
  • userTrackingMode: MapUserTrackingMode를 바인딩 하고 있고, .follow, .none이 존재한다. 
  • annotationItems: 지도의 마커역할을 할 Item을 배열로 넣을 수 있다.
  • annotationContent: annotationItems에 넣은 Item들을 하나씩 순회하면서 MapAnnotationProtocol을 반환하는 closure
Map(coordinateRegion: .constant(MKCoordinateRegion(center: coordinate, span: span)), 
    showsUserLocation: true,
    annotationItems: markers) { marker in
    MapMarker(coordinate: marker.coordinate)
}

annotationContent 클로저에서 annotaionItem에 대한 정보를 받아 MapMarker로 마커를 표시할 수 있다.

마커가 좀 안이쁘긴한데, MapAnnotaion을 사용해 커스텀도 가능하다.

MapAnnotation(coordinate: CLLocationCoordinate2D, anchorPoint: CGPoint) { Image("custom") }

지도를 사용하기 위해 사용자 위치 정보를 알아오고, 특정 위치에 대한 정보를 얻어 표시하는 작업을 하기 위해서는 CoreLocation을 사용하면 된다.

권한 체크

먼저 사용자 위치 정보를 알아오기 위해서는 권한을 받아야 한다.

When In Use 도 있고 Always In Use 도 있다 각각 앱을 사용할때만, 항상 사용할지의 권한에 대한 alert가 뜬다.

private func requestService() throws {
    let status = locationManager.authorizationStatus
    switch status {
    case .notDetermined:
        //포그라운드 상태에서 위치 추적 권한 요청
        locationManager.requestWhenInUseAuthorization()
    case .restricted:
        throw ServiceError.accessRestricted
    case .denied:
        throw ServiceError.accessDenied
    case .authorized, .authorizedAlways, .authorizedWhenInUse:
        break
    @unknown default:
        throw ServiceError.unknown
    }
}

코드로도 요청이 가능하다. CLLocationManager 객체를 생성해 authorizationStatus를 받을 수 있고, 위 코드에서는 아직 권한을 받지 않았을 때로 하긴 했지만 requestWhenInUseAuthorization 메서드를 사용해 info.plist에 설정한 권한 alert을 똑같이 다시 띄울 수 있다.

LocationManager 생성, 사용자 위치 받기

final class GeoServiceManager: NSObject, CLLocationManagerDelegate {
    private var locationManager: CLLocationManager!
    
    override init() {
        super.init()
        //locationManager 인스턴스 생성 및 델리게이트 채택
        locationManager = CLLocationManager()
        locationManager.delegate = self
        //배터리에 맞게 권장되는 최적의 정확도
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        //위치 정보 업데이트 시작
        locationManager.startUpdatingLocation()
    }
}

사용자의 위치를 받아오기 위해선 CLLocationManager객체를 생성하고 Delegate도 채택해주면 사용할 수 있따.

startUpdatingLocation() 메서드를 호출하면 그 시점부터 사용자의 위치정보를 받아올 수 있다.

만약 위치 정보를 사용할 일이 없다면 stopUpdatingLocation() 메서드를 호출하면 된다. 

if let location = locationManager.location {
    userLocation = location
    userCoordinate = location.coordinate
}

locationManager에서 location 프로퍼티를 접근하여 가장 최근의 위치 정보를 얻어올 수 있다. delegate 함수 중 didUpdateLocation을 사용해 업데이트 시 마다 좌표를 받을수도 있다.

Geocoder 도로명 주소 -> 좌표 / 좌표 -> 도로명 주소 변환

검색을 해보면 대부분 네이버, 카카오 지도를 사용해서 SDK 내부에 있는 geocoder를 사용해 변환하는 예제가 많았지만 애플에서도 기본적으로 geocoder를 지원하고 있다.

///좌표 -> 도로명 주소
func loadCurrentUserRoadAddress() async throws -> String {
    let geoCoder = CLGeocoder()
    let places = try await geoCoder.reverseGeocodeLocation(userLocation)
    guard let place = places.last,
          let sido = place.administrativeArea,
          let gugun = place.locality else { throw GeoError.canNotFound }
    return "\(sido) \(gugun)"
}
///도로명 주소 -> 좌표
func getCoordinateFromRoadAddress(from address: String) async throws -> CLLocationCoordinate2D {
    let geoCoder = CLGeocoder()
    let places = try await geoCoder.geocodeAddressString(address)
    guard let place = places.last,
          let coordinate = place.location?.coordinate else { throw GeoError.canNotFound }
    return coordinate
}

CLGeocoder 객체를 생성해 변환을 할 수 있다. 각각 reverseGeocodeLocation, geocodeAddressString을 사용해 변환이 가능하다.

도로명 주소에서 좌표로 변환할 경우 시-도 부터 시작해서 전체 주소로 넣어야 정확히 나온다. 간략한 한글 도로명 주소인데도 멕시코가 뜰 수도 있기 때문이다..

좌표에서 도로명 주소로 변환했을 때 CLPlaceMark 타입에 데이터가 들어오는데, CLPlaceMark의 프로퍼티는 생각보다 많다. 만약 사용한다면 아래 프로퍼티들 설명을 참고하는게 좋다.

CLPlacemark
장소의 이름, 주소 기타 관련 정보 포함하는 사용자에게 친화적인 설명들

name
장소의 이름. (예) 한깉로 12-3

isoCountryCode
축약된 국가 , 또는 지역 이름 (예) KR

country
국가 또는 지역의 이름 (예) 대한민국

postalCode
우편번호 (예) 16212

administrativeArea
시/도 정보 (예) 경기도

subAdminstrativeArea
추가 관리 지역 정보 (예) nil이 나오던데 모르겠슴

locality
장소 표시와 연결된 도시 (예) 수원시

subLocality
추가 도시 수준 정보 (예) 동작구

thoroughfare
상세 주소 (예) 한깉로251번길

subThoroughfare
추가 거리 정보 (예) 16-2

region
지리적 지역 (예) CLRegion으로 나옴 뭐 radius, center.. 위도경도값 나오는듯?

timeZone
연결된 시간대 (예) TimeZone 타입으로 나옴. Asia/Seoul이나GMT+9.. 등등으로 나옴

inlandWater
장소 표시와 연결된 내륙 수역의 이름

ocean
장소 표시와 연결된 바다의 이름

설명의 출처는 아래 블로그

 

[iOS] CLGeocoder 위도,경도 정보로 지역명 가져오기

화면상에 경도/위도값을 가지고 네트워크 연결을 하여, placemark를 뽑아주는 기능!!즉 coordinate 정보에 맞게 place name으로 변환시켜주는 코더라고 보면 된다.이런 간편한 기능을 제공하다니,, 🤔그

velog.io

 

728x90
Comments