88import Foundation
99import SwiftUI
1010import MapKit
11+ import CoreLocation
1112
1213// Custom Map for managing map interactions between SwiftUI and UIKit components
1314struct CustomMap : UIViewRepresentable {
@@ -17,6 +18,9 @@ struct CustomMap: UIViewRepresentable {
1718 var items : [ DisplayUnitWithCoordinate ]
1819 @Binding var selectedQuest : DisplayUnit ?
1920 @Binding var isPresented : Bool
21+ @StateObject var locationManagerDelegate = LocationManagerDelegate ( )
22+
23+ var contextualInfo : ( ( String ) -> Void ) ?
2024
2125 // Creates and configures the UIView
2226 func makeUIView( context: Context ) -> MKMapView {
@@ -47,8 +51,11 @@ struct CustomMap: UIViewRepresentable {
4751 class Coordinator : NSObject , MKMapViewDelegate {
4852 var parent : CustomMap
4953 var isRegionSet = false // boolean flag to track if region has been set
54+ var contextualInfo : ( ( String ) -> Void ) ?
55+
5056 init ( _ parent: CustomMap ) {
5157 self . parent = parent
58+ self . contextualInfo = parent. contextualInfo
5259 }
5360
5461 // Customizes the view for each annotation
@@ -74,6 +81,17 @@ struct CustomMap: UIViewRepresentable {
7481 if let selectedQuest = annotation as? DisplayUnitAnnotation {
7582 parent. selectedQuest = selectedQuest. displayUnit
7683 parent. isPresented = true
84+
85+ let distance = parent. calculateDistance ( selectedAnnotation: selectedQuest. coordinate)
86+ let direction = parent. inferDirection ( selectedAnnotation: selectedQuest. coordinate)
87+
88+ let annotationLocation = CLLocation ( latitude: selectedQuest. coordinate. latitude, longitude: selectedQuest. coordinate. longitude)
89+ parent. inferStreetName ( location: annotationLocation) { streetName in
90+ if let streetName = streetName {
91+ let contextualString = " The \( selectedQuest. title!) is on \( streetName) at \( distance) meters \( direction) of you "
92+ self . contextualInfo ? ( contextualString)
93+ }
94+ }
7795 }
7896 // Deselect the annotation to prevent re-adding on selection
7997 mapView. deselectAnnotation ( annotation, animated: false )
@@ -144,6 +162,70 @@ struct CustomMap: UIViewRepresentable {
144162 }
145163 }
146164
165+ // calculate distance between user current location and selected annotation
166+ func calculateDistance( selectedAnnotation: CLLocationCoordinate2D ) -> CLLocationDistance {
167+ let userCurrentLocation = locationManagerDelegate. locationManager. location!. coordinate
168+ let fromLocation = CLLocation ( latitude: userCurrentLocation. latitude, longitude: userCurrentLocation. longitude)
169+ let toLocation = CLLocation ( latitude: selectedAnnotation. latitude, longitude: selectedAnnotation. longitude)
170+ return CLLocationDistance ( Int ( fromLocation. distance ( from: toLocation) ) )
171+ }
172+
173+ // infer direction
174+ func inferDirection( selectedAnnotation: CLLocationCoordinate2D ) -> String {
175+ let userCurrentLocation = locationManagerDelegate. locationManager. location!. coordinate
176+ let userLocationPoint = MKMapPoint ( userCurrentLocation)
177+ let destinationPoint = MKMapPoint ( selectedAnnotation)
178+ let angleRadians = atan2 ( destinationPoint. y - userLocationPoint. y, destinationPoint. x - userLocationPoint. x)
179+ var angleDegrees = angleRadians * 180 / . pi
180+ angleDegrees += 90
181+
182+ if angleDegrees < 0 {
183+ angleDegrees += 360
184+ } else if angleDegrees >= 360 {
185+ angleDegrees -= 360
186+ }
187+
188+ angleDegrees = ( angleDegrees * 10 ) . rounded ( ) / 10
189+
190+ var direction = " "
191+
192+ // Convert angle into relative direction
193+ if angleDegrees >= 337.5 || angleDegrees < 22.5 {
194+ direction = " ahead "
195+ } else if angleDegrees >= 22.5 && angleDegrees < 112.5 {
196+ direction = " right "
197+ } else if angleDegrees >= 112.5 && angleDegrees < 202.5 {
198+ direction = " behind "
199+ } else if angleDegrees >= 202.5 && angleDegrees < 292.5 {
200+ direction = " left "
201+ } else {
202+ direction = " right "
203+ }
204+
205+ return direction
206+ }
207+
208+ // infer street name from user location coordinates
209+ func inferStreetName( location: CLLocation , completion: @escaping ( String ? ) -> Void ) {
210+
211+ let geocoder = CLGeocoder ( )
212+ geocoder. reverseGeocodeLocation ( location) { placemarks, error in
213+ guard let placemark = placemarks? . first else {
214+ completion ( " " )
215+ return
216+ }
217+ var addressComponents : [ String ] = [ ]
218+ if let streetNumber = placemark. subThoroughfare {
219+ addressComponents. append ( streetNumber)
220+ }
221+ if let streetName = placemark. thoroughfare {
222+ addressComponents. append ( streetName)
223+ }
224+
225+ let address = addressComponents. joined ( separator: " , " )
226+ completion ( address)
227+ }
228+ }
147229}
148230
149231// Extension to convert MapUserTrackingMode to MKUserTrackingMode
0 commit comments