원문 보러가기 -> 런로그 위키
MKPolyLine → MKMulitPolyLine으로 바꿔보기 : 하나의 폴리 라인이면 문제가 해결될 것이라고 생각해서, MultiPolyLine을 찾아보았는데, 실제로는 내부적으로 동일하게 여러개의 PolyLine으로 이루어져 있는 구조여서 동일한 결과를 얻음.
Mapkit View를 스크린샷 찍어서 저장하기 : 디테일뷰에서는 버튼까지 함께 캡쳐된다는 문제가 발생했고, 내가 이미지를 저장하고자 하는 시점인 운동 종료 버튼을 누른 직후는, 뷰가 생성되지 않아 캡쳐가 불가능한 상황이었음
MKMapSnapshotter을 활용하여, 맵킷 내부의 스크린샷을 찍고 실제 좌표값으로 UIBezierPath를 그리기 → 1차적으로 성공하였음!
- 더미데이터로 테스트한 결과
더미데이터는 하나의 운동 기록이라 문제가 없었는데, 여러개의 운동 기록을 표시해보니 문제가 발생했다.
- 다른 운동 세션 간에 연결선이 생겨버리는 문제였다.
private func setSnapshotOption(_ coordinates: [CLLocationCoordinate2D], region: MKCoordinateRegion) -> MKMapSnapshotter.Options {
let option = MKMapSnapshotter.Options()
option.region = region
option.size = CGSize(width: 500, height: 500)
let configuration = MKStandardMapConfiguration(emphasisStyle: .muted)
configuration.pointOfInterestFilter = .excludingAll
option.preferredConfiguration = configuration
return option
}
private func setSnapshotOption(_ coordinates: [CLLocationCoordinate2D]) -> MKMapSnapshotter.Options {
let option = MKMapSnapshotter.Options()
let configuration = MKStandardMapConfiguration(emphasisStyle: .muted)
configuration.pointOfInterestFilter = .excludingAll
option.preferredConfiguration = configuration
return option
}
private func getRouteCenterCoordinate(_ coordinates: [CLLocationCoordinate2D]) throws -> CLLocationCoordinate2D {
guard coordinates.isEmpty == false || coordinates.count > 1 else {
throw MediaUseCaseError.noCenterPos
}
var centerLat : CLLocationDegrees = 0
var centerLon : CLLocationDegrees = 0
for coordinate in coordinates {
centerLat += coordinate.latitude
centerLon += coordinate.longitude
}
centerLat /= Double(coordinates.count)
centerLon /= Double(coordinates.count)
return CLLocationCoordinate2D(latitude: centerLat, longitude: centerLon)
}
private func makeRouteSizeRegion(center: CLLocationCoordinate2D, coordinates: [CLLocationCoordinate2D]) -> MKCoordinateRegion {
let minLat = coordinates.min { $0.latitude < $1.latitude }?.latitude ?? 0
let maxLat = coordinates.max { $0.latitude < $1.latitude }?.latitude ?? 0
let minLon = coordinates.min { $0.longitude < $1.longitude }?.longitude ?? 0
let maxLon = coordinates.max { $0.longitude < $1.longitude }?.longitude ?? 0
let span = MKCoordinateSpan(latitudeDelta: (maxLat - minLat)*1.7, longitudeDelta: (maxLon - minLon)*1.7)
return MKCoordinateRegion(center: center, span: span)
}
func convertSectionsToCoordinates(sections: [Section]) -> [[CLLocationCoordinate2D]] {
var coordinates = [[CLLocationCoordinate2D]]()
for section in sections {
var coors = [CLLocationCoordinate2D]()
for point in section.route.sorted(by: { $0.timestamp < $1.timestamp }) {
let coordinate = CLLocationCoordinate2D(latitude: point.latitude, longitude: point.longitude)
coors.append(coordinate)
}
coordinates.append(coors)
}
return coordinates
}
func setRouteImage(route coordinates: [[CLLocationCoordinate2D]]) async throws -> UIImage {
let centerCoordinate = try getRouteCenterCoordinate(coordinates.flatMap { $0 })
let region = makeRouteSizeRegion(center: centerCoordinate, coordinates: coordinates.flatMap { $0 })
/// 스냅샷 옵션 설정
let option = setSnapshotOption(coordinates.flatMap { $0 }, region: region)
let snapShotter = MKMapSnapshotter(options: option)
/// 스냅샷 생성
let snapshot : MKMapSnapshotter.Snapshot? = await withCheckedContinuation { continuation in
snapShotter.start { snapshot, error in
guard let snapshot = snapshot, error == nil else {
print("Error: \(String(describing: error))")
continuation.resume(returning: nil)
return
}
continuation.resume(returning: snapshot)
}
}
guard let snapshot = snapshot else {
throw MediaUseCaseError.snapshotFailed
}
let mapImage = snapshot.image
let overlayImage = UIGraphicsImageRenderer(size: mapImage.size).image { context in
mapImage.draw(at: .zero)
for route in coordinates {
let points = route.map { snapshot.point(for: $0) }
let path = UIBezierPath()
path.move(to: points.first ?? CGPoint(x: 0, y: 0))
for point in points.dropFirst() {
path.addLine(to: point)
}
path.lineWidth = 2
UIColor.LightGreen.setStroke()
path.stroke()
}
}
return overlayImage
}
문제 해결 후 정상 동작 확인 여부
- 원하는대로 UIImage를 비동기적으로 생성해서 DB에 잘 들어가서 다른 화면에 적용되는 걸 확인하였다.
MapKit으로 이미 코드를 만들어 둔 근웅님과 민준님 파트 코드에 대한 이해도가 높아졌다
초기에 구상했던 로직과 실제로 개발한 로직이 변화하면서 MKMapSnapshotter에 대해서 새로 알게 되었고, 여러 가지 방식으로 이미지 추출을 도전해보면서, MKPolyLine, UIGraphicsImageRenderer, MKStandardMapConfiguration의 공식 문서를 읽으면서 개발할 수 있었다.
내가 원하는 기능에 대한 딱 맞는 레퍼런스가 없어서 도전적인 기능 개발이었던 것 같다. 어려운 기능 개발할 때가 재밌다~
실제로 저장된 이미지를 확인하는 과정에서 시뮬레이터의 DocumentDirectory에 접근하는 코드를 작성했는데, FileManager와 아직도 친숙하지 않은 나에게 복습할만한 기능도 있었다.