I’m making a map app for an autonomous robotic. I’m having problem with annotations and polylines shifting collectively when dragging the annotation. At present the annotation will transfer after which after a quick pause the polyline will transfer and catch as much as it.
I need each to maneuver in unison with a “rubber band” conduct. I’ve not been capable of finding a tutorial or instance of dragging an annotation that has polylines linked to it when dragging. Thanks for any assist/steering you could give.
// LAMErtkAnnotationManager.swift
// LAME-RTK
// Created: 11/19/2024
import Basis
import MapboxMaps
import CoreData
import Turf
import UIKit
// MARK: - Notification Extension
extension Notification.Title {
/// Notification posted when an annotation's coordinate is up to date.
static let didUpdateAnnotation = Notification.Title("didUpdateAnnotation")
}
// MARK: - LAMErtkAnnotationManager
/// Manages annotations (drag handles) and polylines on the Mapbox map.
/// Ensures that dragging an annotation updates the corresponding polyline in real-time.
class LAMErtkAnnotationManager: NSObject, AnnotationInteractionDelegate {
// MARK: - Properties
/// The MapView occasion the place annotations and polylines are managed.
var mapView: MapView
/// The Core Knowledge context for fetching and saving GPS information factors.
var viewContext: NSManagedObjectContext
/// Supervisor for level annotations (drag handles).
var pointAnnotationManager: PointAnnotationManager
/// Supervisor for polyline annotations.
var polylineAnnotationManager: PolylineAnnotationManager
/// Dictionary mapping `zoneID` to its corresponding polyline annotation.
non-public var polylinesByZone: [String: PolylineAnnotation] = [:]
/// Dictionary to maintain observe of the final recognized coordinates of annotations.
/// Used to detect modifications throughout dragging.
non-public var annotationCoordinates: [String: CLLocationCoordinate2D] = [:]
/// Timer to periodically verify for annotation coordinate modifications.
/// Since MapboxMaps SDK lacks direct drag occasion delegates, this timer facilitates synchronization.
non-public var annotationCheckTimer: Timer?
// MARK: - Initialization
/// Initializes the `LAMErtkAnnotationManager` with the offered `MapView` and `NSManagedObjectContext`.
///
/// - Parameters:
/// - mapView: The `MapView` occasion the place annotations and polylines are managed.
/// - viewContext: The Core Knowledge context for fetching and saving GPS information factors.
init(mapView: MapView, viewContext: NSManagedObjectContext) {
self.mapView = mapView
self.viewContext = viewContext
self.pointAnnotationManager = mapView.annotations.makePointAnnotationManager()
self.polylineAnnotationManager = mapView.annotations.makePolylineAnnotationManager()
tremendous.init()
// Assign the delegate for interplay occasions.
self.pointAnnotationManager.delegate = self
self.polylineAnnotationManager.delegate = self
print("***** LAMErtkAnnotationManager initialized.")
// Initialize saved annotation coordinates.
initializeAnnotationCoordinates()
// Begin the timer to verify for annotation updates each second.
annotationCheckTimer = Timer.scheduledTimer(timeInterval: 1.0,
goal: self,
selector: #selector(checkForAnnotationUpdates),
userInfo: nil,
repeats: true)
// Observe annotation replace notifications.
NotificationCenter.default.addObserver(self,
selector: #selector(handleAnnotationUpdate(notification:)),
identify: .didUpdateAnnotation,
object: nil)
}
deinit {
// Invalidate the timer and take away observers to forestall reminiscence leaks.
annotationCheckTimer?.invalidate()
NotificationCenter.default.removeObserver(self)
}
// MARK: - Annotation Administration
/// Clears all level annotations and polylines from the map.
func clearAllAnnotations() {
pointAnnotationManager.annotations.removeAll()
polylineAnnotationManager.annotations.removeAll()
polylinesByZone.removeAll()
annotationCoordinates.removeAll()
print("***** All annotations cleared.")
}
/// Masses all level annotations and corresponding polylines from Core Knowledge.
func loadAllAnnotations() {
clearAllAnnotations()
loadPointAnnotations()
loadPolylines()
print("***** All annotations loaded.")
}
/// Masses level annotations (drag handles) from Core Knowledge.
non-public func loadPointAnnotations() {
let fetchRequest: NSFetchRequest = GPSDataPoint.fetchRequest()
fetchRequest.sortDescriptors = [
NSSortDescriptor(key: "zoneID", ascending: true),
NSSortDescriptor(key: "order", ascending: true)
]
do {
let gpsDataPoints = strive viewContext.fetch(fetchRequest)
pointAnnotationManager.annotations = gpsDataPoints.compactMap { level -> PointAnnotation? in
guard let zoneID = level.zoneID else { return nil }
let coordinate = CLLocationCoordinate2D(latitude: level.latitude, longitude: level.longitude)
var annotation = PointAnnotation(coordinate: coordinate)
annotation.isDraggable = true
annotation.customData = ["zoneID": .string(zoneID)]
annotation.iconImage = "diamondFill" // Set the icon picture to "diamondFill"
// Retailer the preliminary coordinates.
annotationCoordinates[zoneID] = coordinate
return annotation
}
print("***** Loaded (pointAnnotationManager.annotations.rely) level annotations.")
} catch {
print("***** Error fetching level annotations: (error.localizedDescription)")
}
}
/// Masses polylines from Core Knowledge, grouping them by `zoneID`.
non-public func loadPolylines() {
let fetchRequest: NSFetchRequest = GPSDataPoint.fetchRequest()
fetchRequest.sortDescriptors = [
NSSortDescriptor(key: "zoneID", ascending: true),
NSSortDescriptor(key: "order", ascending: true)
]
do {
let gpsDataPoints = strive viewContext.fetch(fetchRequest)
let groupedPoints = Dictionary(grouping: gpsDataPoints, by: { $0.zoneID ?? "default" })
for (zoneID, factors) in groupedPoints {
let coordinates = factors.map { CLLocationCoordinate2D(latitude: $0.latitude, longitude: $0.longitude) }
if let lineString = strive? LineString(coordinates) {
var polyline = PolylineAnnotation(lineString: lineString)
polyline.lineColor = StyleColor(.systemBlue)
polyline.lineWidth = 3.0
polyline.customData = ["zoneID": .string(zoneID)]
polylineAnnotationManager.annotations.append(polyline)
polylinesByZone[zoneID] = polyline
print("***** Added polyline for zoneID: (zoneID) with (coordinates.rely) factors.")
}
}
} catch {
print("***** Error fetching polylines: (error.localizedDescription)")
}
}
/// Initializes the saved coordinates from present annotations.
non-public func initializeAnnotationCoordinates() {
for annotation in pointAnnotationManager.annotations {
if let zoneIDValue = annotation.customData["zoneID"],
case let .string(zoneID) = zoneIDValue {
annotationCoordinates[zoneID] = annotation.coordinate
}
}
}
// MARK: - Dynamic Polyline Updates with Turf
/// Updates the polyline related to a given `zoneID` utilizing Turf.
///
/// - Parameter zoneID: The `zoneID` whose polyline must be up to date.
func updatePolyline(for zoneID: String) {
guard var polyline = polylinesByZone[zoneID] else { return }
let updatedCoordinates = pointAnnotationManager.annotations.compactMap { annotation -> CLLocationCoordinate2D? in
guard let annotationZoneID = annotation.customData["zoneID"],
case let .string(annotationZone) = annotationZoneID,
annotationZone == zoneID else { return nil }
return annotation.coordinate
}
guard !updatedCoordinates.isEmpty else { return }
// Use Turf to validate and create a brand new LineString.
if let updatedLineString = strive? LineString(updatedCoordinates) {
polyline.lineString = updatedLineString
polylineAnnotationManager.annotations = polylineAnnotationManager.annotations.map { $0.id == polyline.id ? polyline : $0 }
print("***** Up to date polyline for zoneID: (zoneID) with (updatedCoordinates.rely) factors.")
} else {
print("***** Error creating LineString for zoneID: (zoneID).")
}
}
// MARK: - AnnotationInteractionDelegate
/// Handles faucet interactions on annotations.
///
/// - Parameters:
/// - supervisor: The `AnnotationManager` occasion.
/// - annotations: The record of annotations that had been tapped.
func annotationManager(_ supervisor: AnnotationManager, didDetectTappedAnnotations annotations: [Annotation]) {
for annotation in annotations {
if let level = annotation as? PointAnnotation,
let zoneIDValue = level.customData["zoneID"],
case let .string(zoneID) = zoneIDValue {
updatePolyline(for: zoneID)
}
}
}
// MARK: - Dealing with Annotation Drags
/// Handles updates to annotations when they're dragged.
/// Since MapboxMaps SDK would not present direct drag occasion delegates, this technique is triggered by way of notifications.
///
/// - Parameter notification: The notification containing the up to date annotation.
@objc non-public func handleAnnotationUpdate(notification: Notification) {
guard let userInfo = notification.userInfo,
let updatedAnnotation = userInfo["annotation"] as? PointAnnotation,
let zoneIDValue = updatedAnnotation.customData["zoneID"] else { return }
// Use sample matching to extract the string from JSONValue
guard case let .string(zoneID) = zoneIDValue else { return }
let newCoordinate = updatedAnnotation.coordinate
print("***** Annotation for zoneID: (zoneID) moved to new coordinate: (newCoordinate)")
// Replace the corresponding GPSDataPoint in Core Knowledge.
let fetchRequest: NSFetchRequest = GPSDataPoint.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "zoneID == %@", zoneID)
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "order", ascending: true)]
fetchRequest.fetchLimit = 1
do {
if let gpsDataPoint = strive viewContext.fetch(fetchRequest).first {
gpsDataPoint.latitude = newCoordinate.latitude
gpsDataPoint.longitude = newCoordinate.longitude
strive viewContext.save()
print("***** Up to date GPSDataPoint for zoneID: (zoneID) with new coordinates.")
}
} catch {
print("***** Error updating GPSDataPoint for zoneID: (zoneID): (error.localizedDescription)")
}
// Replace the corresponding polyline.
updatePolyline(for: zoneID)
}
// MARK: - Timer for Checking Annotation Updates
/// Periodically checks for modifications in annotation coordinates to detect drags.
@objc non-public func checkForAnnotationUpdates() {
for annotation in pointAnnotationManager.annotations {
if let zoneIDValue = annotation.customData["zoneID"],
case let .string(zoneID) = zoneIDValue,
let oldCoordinate = annotationCoordinates[zoneID],
oldCoordinate.latitude != annotation.coordinate.latitude ||
oldCoordinate.longitude != annotation.coordinate.longitude {
// Replace the saved coordinate.
annotationCoordinates[zoneID] = annotation.coordinate
// Submit a notification with the up to date annotation.
NotificationCenter.default.submit(identify: .didUpdateAnnotation,
object: nil,
userInfo: ["annotation": annotation])
}
}
}
}