Swift/iOS MapKit 으로 지도에 이동 기록 나타내기(with LocalDB Realm)

안녕하세요 kchoi입니다.

오늘은 MapKit을 사용하는 법부터 MapKit을 가지고 Map 위에 우리가 움직인 기록을 그려보도록 하겠습니다.

그리고 그 결과를 LocalDB에 저장하고 그 데이터를 불러와보고, 또 삭제도 해봅시다.

 

우리가 지도를 가지고 할 수 있는 것들은 매우 다양합니다.

그중에서 지도 자체를 가지고 놀려면 역시 내 위치를 띄우는 것이 기본이 되겠죠.

그런데 위치를 띄우기만 하는 것 가지고는 만족하지 못한 저는 제가 움직인 이동 동선을 지도에 그려보고 싶었습니다.

예를 들어 다음 그림처럼요.

 

스크린샷 2021-05-07 오후 7 09 32

(시뮬레이터의 위치가 미국이라서 영어로 된 길들이 보이네요 ^^;)

 

그럼 본격적으로 한번 글을 시작해보겠습니다.

실험환경은 : Xcode 12.5 / Swift5 입니다.

 

MapKit으로 지도 띄우기

MapKit으로 지도부터 띄워봅시다.

먼저 import를 할건데, 위치를 받아오기 위해 CoreLocation을,

MapKit을 위해 MapKit을,

마지막으로 있다가 Local DB 를 사용하기 위해 RealmSwift를 import 해줍니다.

 

그리고 storyBoard에 Map Kit View를 하나 올려주고 원하는 크기로 constraints를 부여한 뒤,

@IBOutlet weak var mapKit: MKMapView! 처럼 선언해줍니다.

 

그런 다음 MapKit이 어플을 종료하고 다시 실행했을 때 발생할 수 있는 오류를 방지해주기 위해 우리가 선언한 변수를 MKMapType.standard로 선언해줍니다. 이렇게요: self.MapView.mapType = MKMapType.standard

그리고 지도에 내 위치를 표시하기 위해서 self.MapView.showsUserLocation = true 로 설정해주고,

현재 내 위치 기준으로 지도를 움직이기 위해서 self.MapView.setUserTrackingMode(.follow, animated: true) 로 설정해줍니다.

 

Location 정보 얻어오기 권한 설정하기

여기 글에서 정리했듯이 우리는 gps data를 얻어오기 위해 몇 가지 설정을 해줘야 합니다.

지난 번에 정리한 적이 있기 때문에 자세한 설명은 생략하고 어떤 것들을 코드로 적어줬는지만 정리해놓겠습니다.

구체적인 것들이 궁금하신 분들은 위 링크를 타고 들어가주세요 !

 

1 - locationManager 생성.

    lazy var locationManager: CLLocationManager = {
        let manager = CLLocationManager()
        manager.desiredAccuracy = kCLLocationAccuracyBest
        manager.startUpdatingLocation() // startUpdate를 해야 didUpdateLocation 메서드가 호출됨.
        manager.delegate = self
        return manager
    }()
    

 

2 - locationManager를 통해 gps 사용 허가 받기

    override func viewDidLoad() {
        super.viewDidLoad()

        getLocationUsagePermission()
      ...
    }

    func getLocationUsagePermission() {
        self.locationManager.requestWhenInUseAuthorization()
    }

 

3 - view가 화면에서 사라질 때 locationManager가 위치 업데이트를 중단하도록 하기

    override func viewWillDisappear(_ animated: Bool) {
        self.locationManager.stopUpdatingLocation()
    }

 

4 - GPS 권한 설정 여부에 따른 로직 처리하기

(아래 TrackMyTraceViewController는 제가 만든 뷰 컨트롤러의 이름입니다.)

extension TrackMyTraceViewController: CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
            switch status {
            case .authorizedAlways, .authorizedWhenInUse:
                print("GPS 권한 설정됨")
            case .restricted, .notDetermined:
                print("GPS 권한 설정되지 않음")
                DispatchQueue.main.async {
                    self.getLocationUsagePermission()
                }
            case .denied:
                print("GPS 권한 요청 거부됨")
                DispatchQueue.main.async {
                    self.getLocationUsagePermission()
                }
            default:
                print("GPS: Default")
            }
        }
  ...
}

 

5 - Info.plist에 다음을 추가하기.

 

여기까지 했다면 Map이 지도에 잘 출력될 것입니다.

시뮬레이터의(혹은 테스트 기기의) 위치 또한 정상적으로 잡혀 있을 것입니다.

 

이동 기록 그려내기

이번에는 맵 위에 이동 기록을 그려내보겠습니다.

이동 기록을 그리는 아이디어는 이렇습니다:

위치를 업데이트 한다 -> 위치가 업데이트 되면 이전 위치와 현재 위치를 선으로 잇는다.

 

먼저 이동 기록을 그려내기 위해서는 이전 위치를 저장해야 합니다.

그래서 저는 CLLocationManagerDelegate를 구현한 extension에서

func locationManager(_ manger: CLLocationManager, didUpdateLocations locations: [CLLocation]) {}

을 구현했습니다.

그리고 그것을 구현할 때 필요한 변수가 var previousCoordinate: CLLocationCoordinate2D? 이니까 미리 선언해둡시다.

 

그 코드는 다음과 같습니다.

   func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

        guard let location = locations.last
        else {return}
        let latitude = location.coordinate.latitude
        let longtitude = location.coordinate.longitude

        if let previousCoordinate = self.previousCoordinate {
            var points: [CLLocationCoordinate2D] = []
            let point1 = CLLocationCoordinate2DMake(previousCoordinate.latitude, previousCoordinate.longitude)
            let point2: CLLocationCoordinate2D
            = CLLocationCoordinate2DMake(latitude, longtitude)
            points.append(point1)
            points.append(point2)
            let lineDraw = MKPolyline(coordinates: points, count:points.count)
            self.MapView.addOverlay(lineDraw)
        }

        self.previousCoordinate = location.coordinate
    }

코드를 설명하자면 point1과 point2에 각각 이전 위치, 현재 위치를 담고 그것들을 MKPolyline으로 잇고,

내가 만든 mapView 객체 위에 그려내는 것입니다.

 

MKOverlayRenderer로 그려보기

위에서 설명한 코드에서 self.MapView.addOverlay(lineDraw) 를 하면

func mapView(...rendererFor overlay: MKOverlay...) -> MKOverlayRenderer 함수가 호출됩니다.

이 함수는 MKMapViewDelegate를 구현해야 사용할 수 있습니다.

extension을 맨 아래에 추가해서 MKMapViewDelegate를 내 ViewController가 구현하도록 만들어주고,

그 안에 해당 함수를 적어줍시다.

이렇게요:

extension TrackMyTraceViewController: MKMapViewDelegate {
    func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
        guard let polyLine = overlay as? MKPolyline
        else {
            print("can't draw polyline")
            return MKOverlayRenderer()
        }
        let renderer = MKPolylineRenderer(polyline: polyLine)
            renderer.strokeColor = .orange
            renderer.lineWidth = 5.0
            renderer.alpha = 1.0

        return renderer
    }
}

 

위 함수는 polyline 객체가 들어왔을 때 그것을 그리는 메서드입니다.

해당 polyline의 색깔, 두께 등을 지정할 수 있습니다.

 

그리고 해당 delegate를 사용하기 위해서는 viewDidLoad() 시에 꼭 !

self.MapView.delegate = self 처럼 delegate를 현재 VC로 설정해주어야 합니다.

여기까지 하셨다면 이제 지도를 켜봅시다.

 

잘 나오시나요?

저는 다음 사진처럼 나오고 있습니다 :)

 

MapKit View

혹시나 안 되시는 분들을 위해 전체 코드를 올려놓겠습니다.

//
//  TrackMyTraceViewController.swift
//  HoTechCourse
//
//  Created by 최강훈 on 2021/05/06.
//

import UIKit
import CoreLocation
import RealmSwift
import MapKit

class TrackMyTraceViewController: UIViewController {

    @IBOutlet weak var MapView: MKMapView!

    lazy var locationManager: CLLocationManager = {
        let manager = CLLocationManager()
        manager.desiredAccuracy = kCLLocationAccuracyBest
        manager.startUpdatingLocation()
        manager.delegate = self
        return manager
     }()

    var previousCoordinate: CLLocationCoordinate2D?

    override func viewDidLoad() {
        super.viewDidLoad()

        getLocationUsagePermission()
        // xcode 종료 후 어플을 다시 실행했을 때 뜨는 오류 방지.
        self.MapView.mapType = MKMapType.standard
        // 지도에 내 위치 표시
        MapView.showsUserLocation = true
        // 현재 내 위치 기준으로 지도를 움직임.
        self.MapView.setUserTrackingMode(.follow, animated: true)

        self.MapView.isZoomEnabled = true
        self.MapView.delegate = self

        self.trackData.date = Date()
    }

    func getLocationUsagePermission() {
        self.locationManager.requestWhenInUseAuthorization()
    }

    override func viewWillDisappear(_ animated: Bool) {
        self.locationManager.stopUpdatingLocation()
    }



}



extension TrackMyTraceViewController: CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
            switch status {
            case .authorizedAlways, .authorizedWhenInUse:
                print("GPS 권한 설정됨")
            case .restricted, .notDetermined:
                print("GPS 권한 설정되지 않음")
                DispatchQueue.main.async {
                    self.getLocationUsagePermission()
                }
            case .denied:
                print("GPS 권한 요청 거부됨")
                DispatchQueue.main.async {
                    self.getLocationUsagePermission()
                }
            default:
                print("GPS: Default")
            }
        }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {


        guard let location = locations.last
        else {return}
        let latitude = location.coordinate.latitude
        let longtitude = location.coordinate.longitude

        self.labelLocationInfo1?.text
            = "위도: \(latitude) / 경도: \(longtitude)"

        if let previousCoordinate = self.previousCoordinate {
            var points: [CLLocationCoordinate2D] = []
            let point1 = CLLocationCoordinate2DMake(previousCoordinate.latitude, previousCoordinate.longitude)
            let point2: CLLocationCoordinate2D
            = CLLocationCoordinate2DMake(latitude, longtitude)
            points.append(point1)
            points.append(point2)
            let lineDraw = MKPolyline(coordinates: points, count:points.count)
            self.MapView.addOverlay(lineDraw)
        }

        self.previousCoordinate = location.coordinate

    }
}

extension TrackMyTraceViewController: MKMapViewDelegate {
    func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
        guard let polyLine = overlay as? MKPolyline
        else {
            print("can't draw polyline")
            return MKOverlayRenderer()
        }
        let renderer = MKPolylineRenderer(polyline: polyLine)
            renderer.strokeColor = .orange
            renderer.lineWidth = 5.0
            renderer.alpha = 1.0

        return renderer
    }
}

 

여기까지 잘 따라하셨다면 원하시는 정보를 모두 얻으셨을 거라 생각합니다.

혹시나 localDB(Realm) 을 어떻게 쓰는지 궁금해서 들어오신 분들을 위해 github 주소를 남겨놓을테니 clone해서 봐주세요!

해당 github repo 내 - HoTechCourse - TrackMyTrace 폴더입니다.

그리고 여기에 Realm 사용법을 따로 정리해두었으니 여기도 참고하시면 좋을 것 같습니다 :)

 

github:

github.com/ChoiKanghun/HoTecho_iOS

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기
// custom