Swift 4.2 : How to select/deselect multiple marker on google map - swift

I'm implementing multiple markers on google map using swift in iOS. I've successfully placed all the markers on the map and can select the marker using 'didTap marker'method. But I'm unable to deselect it. When user select on one marker than the rest of the markers should be deselected.
extension MapVC {
// Mark:- Create Marker and set position
fileprivate func setMarkerOnMap() {
if self.markerArray.count != 0 {
for i in 0...self.markerArray.count - 1 {
let data = self.markerArray[i]
guard let lat = data.lat else {
return
}
guard let lon = data.long else {
return
}
let camera: GMSCameraPosition = GMSCameraPosition.camera(withLatitude: Double(lat)!, longitude: Double(lon)!, zoom: zoomLevel)
showMarker(position: camera.target, index: i)
}
}
}
// Mark:- Show marker on map
fileprivate func showMarker(position: CLLocationCoordinate2D, inde x: Int) {
let marker = GMSMarker()
marker.position = position
marker.icon = UIImage(named: "marker-unselected-icon")
marker.accessibilityLabel = "\(index)" // get index from array when click on each marker to identify
marker.map = self.mapView
}
}
//MARK - Map
extension MapVC: GMSMapViewDelegate {
// MARK:- DidTap marker
func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker)-> Bool {
if let markerLbl = marker.accessibilityLabel{
index = Int(markerLbl)
}
if marker != self.userMarker{
marker.icon = UIImage(named: "marker-selected-icon")
self.restaurantDetailView.isHidden = false
self.distanceView.isHidden = true
self.infoView.isHidden = true
}
var isReturn = true
mapView.selectedMarker = nil
if self.markerArray.count != 0 {
for i in 0...markerArray.count - 1 {
if i == index {
let storeInfo = markerArray[i]
storeInfo.isSelected = !storeInfo.isSelected
self.destinationLat = Double(storeInfo.lat!)!
self.destinationLng = Double(storeInfo.long!)!
marker.icon = UIImage(named: "marker-selected-icon")
markerArray[i] = storeInfo
} else {
marker.icon = UIImage(named: "marker-unselected-icon")
let storeInfo = markerArray[i]
storeInfo.isSelected = false
markerArray[i] = storeInfo
}
}
} else {
mapView.selectedMarker = nil
isReturn = true
return isReturn
}
return isReturn
}
}
I'm changing the selected marker image in did tap method but unfortunately, it is not working. I don't know what I'm doing wrong. Thanks in Advance.

When user want to select one marker and want to deselect all the other markers then following code would be usefull.
To Deselect all Marker on Map:
for (marker in self.markerArray) {
self.mapview.selectedMarker = nil
}
To Select Marker on Map:
self.mapView.selectedMarker = self.userMarker

Related

Mapbox Maps SDK for iOS - Maps won't display anymore (or gets cut off)

Maps (from MapBox API) were working just fine in our app, but about a month they stopped displaying on a swiftui view. BUT the tricky thing is that the maps in some phones still work (or works partially but being cut off), and they still show the annotations/pins (see screenshot).
We have the MapBox Maps SDK for iOS installed as cocoapods into Xcode.
It's a simple app, it just detects a vehicle trip and add event points to the map (eg. point A as trip start, and point B as trip end).
Versions:
Xcode: 13.2.1
pod 'Mapbox-iOS-SDK', '~> 6.4.1'
pod 'MapboxNavigation', '~> 1.4.2'
Basically, the following is our main code to show the map.
Note: It also adds speeding, hard brake and rapid accelaration annotations to the map.
Does someone have any idea why the map is not showing properly?
Thanks much in advance.
import SwiftUI
import Mapbox
import MapboxDirections
import Polyline
struct TestMapDriveView: View {
var selectedTrip: VehicleTripData // The selected trip that needs to be shown
#Binding var speedingCount: Int? // the number of speeding events in the selected trip
#Binding var showCallouts: Bool // Whether to show callout when a point annotation is selected
#Binding var showInfoSheet: Bool // whether show the event details view or not (show the details when the user tap on a specific marker on the map)
#Binding var isFullScreen: Bool // whether show full screen map or not
#Binding var selectedEncodedPolyline: String // The encoded polyline for the trip
#State var alreadyClean: Bool = false
#State var changed: Bool = false
#State var showSpecificPoint = false // Whether focues on a specific trip point or not
let action: () -> () // Used to toggle the full screen state variable
var body: some View {
VStack {
NavigationLink(destination: EmptyView(), label: {EmptyView()})
NavigationLink(destination: EventDetails(tripPoint: selectedTripPoint, selectedTrip: selectedTrip, typeInfo: .constant(selectedTypeInfo))
, isActive: $showInfoSheet, label: {EmptyView()}).navigationTitle("").navigationBarTitle("").navigationBarHidden(true)
MapboxMap(selectedTrip: selectedTrip, speedingCount: $speedingCount, showCallouts: $showCallouts, showInfoSheet: $showInfoSheet, isFullScreen: $isFullScreen, selectedEncodedPolyline: $selectedEncodedPolyline, alreadyClean: $alreadyClean, changed: $changed, showSpecificPoint: $showSpecificPoint)
.onAppear {
showDetails = false
// The reason for adding a timer here is that since showDetails is a global variable,
// and it is not a state variable, we canot trigger its value
// We need a timer so that we can check the value of the showDetails every 0.1 second
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
showInfoSheet = showDetails
if showInfoSheet {
isFullScreen = false
findTripPoint()
timer.invalidate()
}
}
}
}
}
}
// MARK: - New Map Provided by MapBox
// MARK: - Since MapBox is designed to be implemented with the UIView, for SwiftUI, we need to transfer the UIView to View
struct MapboxMap: UIViewRepresentable {
var selectedTrip: VehicleTripData
#Binding var speedingCount: Int?
#Binding var showCallouts: Bool
#Binding var showInfoSheet: Bool
#Binding var isFullScreen: Bool
#Binding var selectedEncodedPolyline: String
#Binding var alreadyClean: Bool
#Binding var changed: Bool
#Binding var showSpecificPoint: Bool
private let mapView: MGLMapView = MGLMapView(frame: .zero, styleURL: MGLStyle.streetsStyleURL)
class Coordinator: NSObject, MGLMapViewDelegate {
var showCallouts: Bool
init(showcallouts: Bool) {
self.showCallouts = showcallouts
}
func mapView(_ mapView: MGLMapView, shapeAnnotationIsEnabled annotation: MGLShape) -> Bool {
return annotation.title != nil && annotation.title != "Speeding"
}
// When you tap on a point on the map, whether show the callout or not
func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
if let title = annotation.title {
return (!(title == "Speeding") && showCallouts)
}
else {
return false
}
}
// MARK: - This function is used to replace the default marker icon with our icons for specific events
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
guard annotation is MGLPointAnnotation else {
return nil
}
guard annotation.title != nil else {return nil}
guard annotation.title! != nil else {return nil}
if annotation.title != ""{
let identifier = annotation.title!!
print("error \(annotation.title)")
var image = UIImage()
if identifier.hasPrefix("Speeding") {
image = UIImage(named: "Speeding Marker")!
} else if identifier.hasPrefix("Hard"){
image = UIImage(named: "Hard Brake Pin")!
} else if identifier.hasPrefix("Rapid") {
image = UIImage(named: "Rapid Accel Pin")!
} else {
image = UIImage(named: identifier)!
}
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
if annotationView == nil {
annotationView = MGLAnnotationView(annotation: annotation, reuseIdentifier: identifier as! String)
let imageView = UIImageView(image: image)
annotationView!.frame = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
annotationView?.addSubview(imageView)
annotationView?.centerOffset = CGVector(dx: 0, dy: -image.size.height / 2.0)
}
return annotationView
}
return nil
}
// MARK: - This function is executed when the user taps on "View Details"
// In fact, we should set showInfoSheet to be true in order to show the EventDetailView
// However, since we are using a coordinator here, we cannnot change showInfoSheet directly
// Therefore, we change the global variable "showDetails" to be true
#objc func labelAction(_ sender: UITapGestureRecognizer) {
showDetails = true
}
// MARK: - Add the "View Details" Label in the Callout View
// MARK: - We should use UIButtion, however, if we use UIButtion, it will never be shown. Need further tests
func mapView(_ mapView: MGLMapView, leftCalloutAccessoryViewFor annotation: MGLAnnotation) -> UIView? {
guard let title = annotation.title else {return nil}
if title != nil {
if title!.hasPrefix("Speeding:") {
selectedTypeInfo = "speeding"
} else if title!.hasPrefix("Hard") {
selectedTypeInfo = "hardBrake"
} else if title!.hasPrefix("Rapid") {
selectedTypeInfo = "rapidAccel"
} else {
return nil
}
let labelTap = UITapGestureRecognizer(target: self, action: #selector(labelAction(_:)))
labelTap.numberOfTapsRequired = 1
// Callout height is fixed; width expands to fit its content.
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 60, height: 50))
label.textAlignment = .center
label.textColor = UIColor(named: "Blue")
label.text = "View Details"
label.numberOfLines = 2
label.font = UIFont(name: "NotoSans-Regular", size: 12)
label.isUserInteractionEnabled = true
label.addGestureRecognizer(labelTap)
selectedCoor = annotation.coordinate
return label
}
return nil
}
// MARK: - This function is used to change the color of the polyline based on the event name
func mapView(_ mapView: MGLMapView, strokeColorForShapeAnnotation annotation: MGLShape) -> UIColor {
guard let title = annotation.title else {return UIColor(named: "Blue")!}
if title.hasPrefix("Speeding") {
return UIColor(named: "MapRed")!
} else {
return UIColor(named: "Blue")!
}
}
}
func makeCoordinator() -> MapboxMap.Coordinator {
return Coordinator(showcallouts: showCallouts)
}
// MARK: - Initialize the MapView
func makeUIView(context: Context) -> MGLMapView {
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.setCenter(CLLocationCoordinate2D(latitude: selectedTrip.startedLatitude!, longitude: selectedTrip.startedLongitude!), animated: true)
mapView.delegate = context.coordinator
return mapView
}
func updateUIView(_ uiView: MGLMapView, context: Context) {
if (uiView.annotations ?? []).count == 0 {
mapViewDidFinishLoadingMap(uiView, didFinishLoading: MGLStyle())
}
}
func mapViewDidFinishLoadingMap(_ mapView: MGLMapView, didFinishLoading style: MGLStyle) {
mapView.isRotateEnabled = false
// MARK: - Get the result from the encoded polyline string
let result = Polyline.init(encodedPolyline: selectedEncodedPolyline, encodedLevels: .none)
// MARK: - Get the coordinates in the polyline for the trip
let coordinates = result.coordinates
guard let tripPoints = coordinates else {return}
// MARK: - Get the start and end point
let startPoint: CLLocationCoordinate2D = coordinates?.first ?? CLLocationCoordinate2D(latitude: 0, longitude: 0)
let endPoint: CLLocationCoordinate2D = coordinates?.last ?? CLLocationCoordinate2D(latitude: 0, longitude: 0)
var startTripPoint = MGLPointAnnotation()
var endTripPoint = MGLPointAnnotation()
startTripPoint.title = "Starting Point"
startTripPoint.coordinate = startPoint
endTripPoint.title = "End Point"
endTripPoint.coordinate = endPoint
mapView.addAnnotation(startTripPoint)
mapView.addAnnotation(endTripPoint)
let tryLine = MGLPolyline(coordinates: coordinates!, count: UInt(coordinates!.count))
mapView.addAnnotation(tryLine)
var speedingArray = [[TripPoint]]()
DispatchQueue.global(qos: .background).async {
// MARK: - Deal with the speeding events in the trip
// MARK: - The speeding icon will be placed at the point which has the maximum speed for each speeding polyline
var ctr = 0
while ctr < selectedTrip.speedingPoints!.count{
var speedPath = [TripPoint]()
speedPath.append(selectedTrip.speedingPoints![ctr])
while ctr + 1 < selectedTrip.speedingPoints!.count && selectedTrip.speedingPoints![ctr + 1].timeStamp!.timeIntervalSince1970 - selectedTrip.speedingPoints![ctr].timeStamp!.timeIntervalSince1970 < 10{
speedPath.append(selectedTrip.speedingPoints![ctr+1])
ctr += 1
}
speedingArray.append(speedPath)
ctr += 1
}
for speedingLine in speedingArray {
var maxDelta = 0.0
var maxDeltaPoint:TripPoint? = nil
var path: [CLLocationCoordinate2D] = []
for speedingPoint in speedingLine{
path.append(CLLocationCoordinate2D(latitude: speedingPoint.latitude!, longitude: speedingPoint.longitude!))
if speedingPoint.speed_limit_delta! > maxDelta{
maxDelta = speedingPoint.speed_limit_delta!
maxDeltaPoint = speedingPoint
}
}
if let markerPoint = maxDeltaPoint{
tripPointsArray.append(markerPoint)
var speedMarker = MGLPointAnnotation()
speedMarker.coordinate = CLLocationCoordinate2D(latitude: markerPoint.latitude!, longitude: markerPoint.longitude!)
speedMarker.title = "Speeding: \(Int(markerPoint.speed!)) " + markerPoint.speedUnit!
let speedLimitBR = Double(markerPoint.speed!) - markerPoint.speed_limit_delta! //before rounding
var speedLimit = 10 * Int((speedLimitBR / 10.0).rounded())//round to nearest 10
speedMarker.subtitle = "Speed Limit: ~ \(speedLimit) " + markerPoint.speedUnit!
DispatchQueue.main.async {
mapView.addAnnotation(speedMarker)
}
}
let speedingPolyline = MGLPolyline(coordinates: path, count: UInt(path.count))
speedingPolyline.title = "Speeding"
DispatchQueue.main.async {
mapView.addAnnotation(speedingPolyline)
}
}
speedingCount = speedingArray.count
// MARK: - Deal with hard brakes in the trip
for hardBrakePoint in selectedTrip.hardBrakePoints!{
tripPointsArray.append(hardBrakePoint)
let hardBrakeMarker = MGLPointAnnotation()
hardBrakeMarker.coordinate = CLLocationCoordinate2D(latitude: hardBrakePoint.latitude!, longitude: hardBrakePoint.longitude!)
hardBrakeMarker.title = "Hard Brake"
hardBrakeMarker.subtitle = "Acceleration: \(String(format: "%.1f", hardBrakePoint.acceleration! * 3.6)) " + "km/h/s"
DispatchQueue.main.async {
mapView.addAnnotation(hardBrakeMarker)
}
}
// MARK: - Deal with rapid accel in the trip
for rapidAccelPoint in selectedTrip.rapidAccelPoints!{
tripPointsArray.append(rapidAccelPoint)
let rapidAccelMarker = MGLPointAnnotation()
rapidAccelMarker.coordinate = CLLocationCoordinate2D(latitude: rapidAccelPoint.latitude!, longitude: rapidAccelPoint.longitude!)
rapidAccelMarker.title = "Rapid Accel"
rapidAccelMarker.subtitle = "Acceleration: \(String(format: "%.1f", rapidAccelPoint.acceleration! * 3.6)) " + "km/h/s"
DispatchQueue.main.async {
mapView.addAnnotation(rapidAccelMarker)
}
}
}
// MARK: - If we are not in EventDetailView, then the mapView.showAnnotations will help us to zoom the map to the proper level so that all the markers and annotations in the map will be shown
if !showSpecificPoint {
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { timer in
guard let annotations = mapView.annotations else {return}
mapView.showAnnotations(annotations, edgePadding: .init(top: 60, left: 40, bottom: 10, right: 40), animated: true, completionHandler: nil)
}
} else {
// MARK: - If we need to zoom into a specific point, we need to find which point it is among all the trip points and zoom into that specific point
var alreadyAdded = false
var point = MGLPointAnnotation()
point.coordinate = CLLocationCoordinate2D(latitude: selectedTripPoint.latitude ?? 0, longitude: selectedTripPoint.longitude ?? 0)
for hardBrakePoint in selectedTrip.hardBrakePoints! {
if hardBrakePoint.latitude == selectedTripPoint.latitude && hardBrakePoint.longitude == selectedTripPoint.longitude {
point.title = "Hard Brake"
point.subtitle = "Acceleration: \(String(format: "%.1f", hardBrakePoint.acceleration! * 3.6)) " + "km/h/s"
mapView.addAnnotation(point)
alreadyAdded = true
break
}
}
if !alreadyAdded {
for rapidAccelPoint in selectedTrip.rapidAccelPoints!{
if rapidAccelPoint.latitude == selectedTripPoint.latitude && rapidAccelPoint.longitude == selectedTripPoint.longitude {
point.title = "Rapid Accel"
point.title! += "Acceleration: \(String(format: "%.1f", rapidAccelPoint.acceleration! * 3.6)) " + "km/h/s"
mapView.addAnnotation(point)
alreadyAdded = true
break
}
}
}
if !alreadyAdded {
var presentPolyLine: MGLPolyline = MGLPolyline()
for speedingLine in speedingArray{
var maxDelta = 0.0
var maxDeltaPoint:TripPoint? = nil
var path: [CLLocationCoordinate2D] = []
for speedingPoint in speedingLine{
path.append(CLLocationCoordinate2D(latitude: speedingPoint.latitude!, longitude: speedingPoint.longitude!))
if speedingPoint.speed_limit_delta! > maxDelta{
maxDelta = speedingPoint.speed_limit_delta!
maxDeltaPoint = speedingPoint
}
}
if let markerPoint = maxDeltaPoint{
if selectedTripPoint.latitude == markerPoint.latitude && selectedTripPoint.longitude == markerPoint.longitude {
let specificMarker = MGLPointAnnotation()
specificMarker.coordinate = CLLocationCoordinate2D(latitude: markerPoint.latitude ?? 0, longitude: markerPoint.longitude ?? 0)
var speedMarker = MGLPointAnnotation()
speedMarker.coordinate = CLLocationCoordinate2D(latitude: markerPoint.latitude!, longitude: markerPoint.longitude!)
speedMarker.title = "Speeding: \(Int(markerPoint.speed!)) " + markerPoint.speedUnit!
let speedLimitBR = Double(markerPoint.speed!) - markerPoint.speed_limit_delta! //before rounding
var speedLimit = 10 * Int((speedLimitBR / 10.0).rounded())//round to nearest 10
speedMarker.subtitle = "Speed Limit: ~ \(speedLimit) " + markerPoint.speedUnit!
point = speedMarker
DispatchQueue.main.async {
let speedingPolyline = MGLPolyline(coordinates: path, count: UInt(path.count))
speedingPolyline.title = "Speeding"
presentPolyLine = speedingPolyline
}
break
} else {
path.removeAll()
}
}
}
mapView.showAnnotations([point], edgePadding: .init(top: 60, left: 40, bottom: 10, right: 40), animated: true, completionHandler: nil)
} else {
mapView.showAnnotations([point], edgePadding: .init(top: -10, left: -10, bottom: -10, right: -10), animated: true, completionHandler: nil)
}
}
}
}
I tried updating the pods from 6.3 to 6.4.1 but it didn't work.
I know MapBox has a new framework as of v6.4.1 called v10, but I haven't installed yet cause that would change the whole code.
I am expecting to see the map as it was displaying before this new bug. eg. see screenshot

How to update multiple location without clearing GoogleMap

I have load couple of users in map with clustering and now I want to update the users location as per the users new location which I receive from server.
I'm able to do that by clearing the map and load new users data from server but it looks like markers are jumping and it not looks proper.
Is there any way to update the marker locations without clearing the GoogleMap?
Here is the link of my question : Google Map with Cluster and Custom view marker lagging too much while zoomIn and zoomOut
Reference Screen
if some one is moved from his location then how can I update his/her location once I receive updated data from server. (Without clearing the map)
My Code
func setMarkers() {
for i in 0..<SharedData.sharedInstance.allFriends.count {
let marker = GMSMarker()
let friend = SharedData.sharedInstance.allFriends[i]
marker.position = CLLocationCoordinate2D.init(latitude: friend.user_details.latitude , longitude: friend.user_details.longitude)
marker.accessibilityHint = String(i)
marker.icon = #imageLiteral(resourceName: "trans")
marker.tracksViewChanges = true
marker.map = mapView
arrMarkers.append(marker)
self.generatePOIItems(String(format: "%d", i), position: marker.position, icon: nil, friend: friend, userIndex: i)
}
clusterManager.cluster()
clusterManager.setDelegate(self, mapDelegate: self)
}
func updateMarkers() {
for i in 0..<arrMarkers.count {
let marker = arrMarkers[i]
let friend = SharedData.sharedInstance.allFriends[i]
marker.position = CLLocationCoordinate2D.init(latitude: friend.user_details.latitude , longitude: friend.user_details.longitude)
marker.accessibilityHint = String(i)
marker.icon = #imageLiteral(resourceName: "trans")
marker.tracksViewChanges = true
marker.map = mapView
}
self.defaultCamera(latitude: SharedData.sharedInstance.userLocation.coordinate.latitude, longitude: SharedData.sharedInstance.userLocation.coordinate.longitude)
}
EDIT
func generatePOIItems(_ accessibilityLabel: String, position: CLLocationCoordinate2D, icon: UIImage?, friend: WallBeeppClass, userIndex: Int) {
let name = "Item \(accessibilityLabel)"
let item = POIItem(position: CLLocationCoordinate2DMake(position.latitude, position.longitude), name: name, friend: friend, userIndex: userIndex)
clusterManager.add(item)
}
func updateMarkers() {
for i in 0..<arrMarkers.count {
let marker = arrMarkers[i]
let friend = SharedData.sharedInstance.allFriends[i]
CATransaction.begin()
CATransaction.setAnimationDuration(1.0)
marker.position = CLLocationCoordinate2D.init(latitude: friend.user_details.latitude , longitude: friend.user_details.longitude)
CATransaction.commit()
}
}
EDIT 1
If I change some users location from backend and when I got users new location then I update the marker position using above code but the issue is that new location users are still located at old location. and if I clear & redraw the users data then it works fine.
EDIT 2
func renderer(_ renderer: GMUClusterRenderer, willRenderMarker marker: GMSMarker) {
marker.groundAnchor = CGPoint(x: 0.1, y: 0.45)
if let markerData = (marker.userData as? POIItem) {
let infoWindow = Bundle.main.loadNibNamed("InitialMapInfoView", owner: self, options: nil)?.first as! InitialMapInfoView
infoWindow.imgUser.sd_setImage(with: URL(string: markerData.friend.user_details.user_photo_small), placeholderImage: #imageLiteral(resourceName: "User_profile"), options: .highPriority, completed: nil)
if !markerData.friend.user_details.isUserOnline {
infoWindow.imgCar.image = UIImage.init(named: "small_inactive_" + markerData.friend.user_details.car_personality_name)
}
else {
infoWindow.imgCar.image = UIImage.init(named: "small_" + markerData.friend.user_details.car_personality_name)
}
if markerData.friend.user_details.user_id == 88 {
print("Will Rendrer Marker: \(markerData.friend.user_details.latitude)")
print("Will Rendrer Marker: \(markerData.friend.user_details.longitude)")
}
infoWindow.lblName.text = markerData.friend.user_details.name
infoWindow.btnImgVW.tag = markerData.userIndex
infoWindow.btnImgVW.addTarget(self, action: #selector(btnUserTapped(_:)), for: .touchUpInside)
marker.accessibilityHint = String(markerData.userIndex)
marker.iconView = infoWindow
marker.tracksViewChanges = false
}
Please guide me to do this.
you can subclass GMSMarker, add id of your objects, then get markers by object id you got from server and update position. Here is some code to explain what I mean
class MyMarker: GMSMarker {
var id: String? = nil
}
add id to your marker
func setMarkers() {
let marker = MyMarker()
let friend = SharedData.sharedInstance.allFriends[I]
marker.id = friend.id
than update by id
for friend in SharedData.sharedInstance.allFriends {
guard
let marker = array.first { $0.id == friend.id }
else { contiunue }
marker.position = CLLocationCoordinate2D.init(latitude: friend.user_details.latitude , longitude: friend.user_details.longitude)
}
don't add marker to map again, just change it's position

how to make the annotation move over the polyline

i have 3 annotation and i draw polyline between first and second annotation but i need the therd one move over that polyline but it's always move in street polyline to the destnation
-my code
func moveDelivery(_ destinationCoordinate : CLLocationCoordinate2D{
self.deliveryAnnotation.coordinate = CLLocationCoordinate2DMake(29.959640, 31.270421)
let sourcePlaceMark = MKPlacemark(coordinate: self.userAnnotation.coordinate)
//sourcePlaceMark.title
let destPlaceMkark = MKPlacemark(coordinate: self.deliveryAnnotation.coordinate)
let sourceItem = MKMapItem(placemark: sourcePlaceMark)
let destItem = MKMapItem(placemark: destPlaceMkark)
let directionRequest = MKDirections.Request()
directionRequest.source = sourceItem
directionRequest.destination = destItem
directionRequest.transportType = .any
let direction = MKDirections(request: directionRequest)
direction.calculate(completionHandler: {
response, error in
guard let response = response else {
if let error = error {
print(error.localizedDescription)
} else {
self.deliveryAnnotation.courseDegrees = self.getHeadingForDirectionFromCoordinate(self.kitchenAnnotation.coordinate, toLoc: self.userAnnotation.coordinate)
self.view.transform = CGAffineTransform(rotationAngle:CGFloat(self.deliveryAnnotation.courseDegrees))
}
return
}
guard let primaryRoute = response.routes.first else { return }
let route = response.routes[0]
self.mapView.addOverlay(route.polyline, level: .aboveRoads)
let rekt = route.polyline.boundingMapRect
self.mapView.setRegion(MKCoordinateRegion(rekt), animated: true)
})
//
UIView.animate(withDuration: Double(60), animations: {
self.deliveryAnnotation.coordinate = destinationCoordinate
}, completion: { success in
if success {
}
})
}
Your third annotation isn't following the route because you're animating it moving in a straight line between the first and second line. Try getting the coordinates from the MKRoute's polyline and animate between each one (According to apple's docs MKRoutes are made up of coordinates, but you might be able to use points as well)
If you'd like it to animate over the span of 60 seconds:
func moveDelivery(_ destinationCoordinate: CLLocationCoordinate2D) {
// I don't know why you have the delivery annotation start here, is this for testing?
deliveryAnnotation.coordinate = CLLocationCoordinate2DMake(29.959640, 31.270421)
let sourcePlaceMark = MKPlacemark(coordinate: destinationCoordinate)
let destPlaceMkark = MKPlacemark(coordinate: userAnnotation.coordinate)
let directionRequest = MKDirections.Request()
directionRequest.source = MKMapItem(placemark: sourcePlaceMark)
directionRequest.destination = MKMapItem(placemark: destPlaceMkark)
directionRequest.transportType = .any
let direction = MKDirections(request: directionRequest)
direction.calculate(completionHandler: {
response, error in
guard let response = response else {
print("MKRequest gave no response")
if let error = error {
print(error.localizedDescription)
} else {
self.deliveryAnnotation.courseDegrees = self.getHeadingForDirectionFromCoordinate(self.kitchenAnnotation.coordinate, toLoc: self.userAnnotation.coordinate)
self.view.transform = CGAffineTransform(rotationAngle:CGFloat(self.deliveryAnnotation.courseDegrees))
}
return
}
guard let primaryRoute = response.routes.first else {
print("response has no routes")
return
}
self.mapView.addOverlay(primaryRoute.polyline, level: .aboveRoads)
let rekt = primaryRoute.polyline.boundingMapRect
self.mapView.setRegion(MKCoordinateRegion(rekt), animated: true)
let coordinateArray = primaryRoute.polyline.coordinates
assert(coordinateArray.count > 0, "coordinate array is empty")
self.routeCoordinates = coordinateArray
// initiate recursive animations
self.coordinateIndex = 0
})
}
var routeCoordinates = [CLLocationCoordinate2D]()
var avgAnimationTime: Double {
return 60 / Double(routeCoordinates.count)
}
var coordinateIndex: Int! {
didSet {
guard coordinateIndex != routeCoordinates.count else {
print("animated through all coordinates, stopping function")
return
}
animateToNextCoordinate()
}
}
func animateToNextCoordinate() {
let coordinate = routeCoordinates[coordinateIndex]
UIView.animate(withDuration: avgAnimationTime, animations: {
self.deliveryAnnotation.coordinate = coordinate
}, completion: { _ in
self.coordinateIndex += 1
print("moved between coordinates")
})
}
EDIT
make sure to include this extension, otherwise you won't be able to get the coordinates of the MKRoute (source: https://gist.github.com/freak4pc/98c813d8adb8feb8aee3a11d2da1373f)
public extension MKMultiPoint {
var coordinates: [CLLocationCoordinate2D] {
var coords = [CLLocationCoordinate2D](repeating: kCLLocationCoordinate2DInvalid,
count: pointCount)
getCoordinates(&coords, range: NSRange(location: 0, length: pointCount))
return coords
}
}
EDIT #2
See above, edited original answer to animate through each coordinate after the previous finishes animating. Really rough but it should work.
EDIT #3
Added your code to get the destination variable as well as some assert and debug printing calls. If things aren't working this time, please tell me which debug messages you get.
EDIT #4
I just demo'd my code and it works. Here is the MapViewController class I used along with necessary extensions:
private let reuseId = "deliveryReuseId"
private let userTitle = "user"
private let startingPointTitle = "store"
private let deliveryTitle = "delivery truck"
class MapViewController: UIViewController {
var mapView: MKMapView!
// annotations for this demo, replace with your own annotations
var deliveryAnnotation: MKPointAnnotation = {
let annotation = MKPointAnnotation()
annotation.title = deliveryTitle
return annotation
}()
let userAnnotation: MKPointAnnotation = {
let annotation = MKPointAnnotation()
annotation.title = userTitle
annotation.coordinate = CLLocationCoordinate2DMake(29.956694, 31.276854)
return annotation
}()
let startingPointAnnotation: MKPointAnnotation = {
let annotation = MKPointAnnotation()
annotation.title = startingPointTitle
annotation.coordinate = CLLocationCoordinate2DMake(29.959622, 31.270363)
return annotation
}()
override func viewDidLoad() {
super.viewDidLoad()
loadMapView()
navigate()
}
func loadMapView() {
// set map
mapView = MKMapView()
view = mapView
mapView.delegate = self
mapView.register(MKAnnotationView.self, forAnnotationViewWithReuseIdentifier: reuseId)
// add annotations
mapView.addAnnotation(userAnnotation)
mapView.addAnnotation(startingPointAnnotation)
mapView.addAnnotation(deliveryAnnotation)
}
func navigate() {
let sourcePlaceMark = MKPlacemark(coordinate: startingPointAnnotation.coordinate)
let destPlaceMkark = MKPlacemark(coordinate: userAnnotation.coordinate)
let directionRequest = MKDirections.Request()
directionRequest.source = MKMapItem(placemark: sourcePlaceMark)
directionRequest.destination = MKMapItem(placemark: destPlaceMkark)
directionRequest.transportType = .any
let direction = MKDirections(request: directionRequest)
direction.calculate(completionHandler: { response, error in
if let error = error {
print(error.localizedDescription)
return
}
guard let primaryRoute = response!.routes.first else {
print("response has no routes")
return
}
self.mapView.addOverlay(primaryRoute.polyline, level: .aboveRoads)
self.mapView.setRegion(MKCoordinateRegion(primaryRoute.polyline.boundingMapRect), animated: true)
// initiate recursive animation
self.routeCoordinates = primaryRoute.polyline.coordinates
self.coordinateIndex = 0
})
}
var routeCoordinates = [CLLocationCoordinate2D]()
var avgAnimationTime: Double {
// to show delivery in 60 second, replace 60 with amount of seconds you'd like to show
return 60 / Double(routeCoordinates.count)
}
var coordinateIndex: Int! {
didSet {
guard coordinateIndex != routeCoordinates.count else {
print("animated through all coordinates, stopping function")
return
}
animateToNextCoordinate()
}
}
func animateToNextCoordinate() {
let coordinate = routeCoordinates[coordinateIndex]
UIView.animate(withDuration: avgAnimationTime, animations: {
self.deliveryAnnotation.coordinate = coordinate
}, completion: { _ in
self.coordinateIndex += 1
})
}
}
extension MapViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
// replace these images with your own
switch annotation.title {
case userTitle:
annotationView.image = UIImage(named: "user")
case startingPointTitle:
annotationView.image = UIImage(named: "store")
case deliveryTitle:
annotationView.image = UIImage(named: "deliveryTruck")
default: break
}
return annotationView
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
guard overlay is MKPolyline else {
return MKOverlayRenderer()
}
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.strokeColor = .black
renderer.lineWidth = 5
renderer.lineJoin = .round
return renderer
}
}
public extension MKMultiPoint {
var coordinates: [CLLocationCoordinate2D] {
var coords = [CLLocationCoordinate2D](repeating: kCLLocationCoordinate2DInvalid,
count: pointCount)
getCoordinates(&coords, range: NSRange(location: 0, length: pointCount))
return coords
}
}

Transferring Data from Firebase into Custom Info Window

So I'm trying to transfer the data I've snapshotted from firebase into the custom info window. I currently have four different categories of activities and as such there are four different marker arrays and functions. I have one generic custom info window that I wish to display the markers Title, rating and difficulty level into.
I am having an issue Ive currently tried appending all data into one structured array and then calling the data from that array but all I get is the same set of data in all the info windows. I want each info window to specifically display the data associated with that GMS Marker.
This is is the function that shows the board activities. I have four of these functions for each activity.
func showBoardIcon() {
ref = Database.database().reference()
ref.child("location").observe(.childAdded) { (snapshot:DataSnapshot) in
if let dict = snapshot.value as? [String:AnyObject] {
if dict["Activity"] as! String == "Board" {
let longitude = dict["Longitude"] as! String
let lattitude = dict["Lattitude"] as! String
let title = dict["Title"] as! String
let key = dict["id"] as! String
self.boardIconArray.insert(coordinate(title: title, carLat: lattitude, carLng: longitude, idKey: key), at: 0)
let n = self.boardIconArray.count
let heightWidth = (self.mapView.frame.height / 12)
for var Marker in 1...n {
let boardMarker = GMSMarker()
let boardIconView = UIImage(named: "boardPin")
let image = boardIconView
let location = CLLocationCoordinate2D(latitude: Double(lattitude)!, longitude: Double(longitude)!)
boardMarker.position = location
boardMarker.icon = image
boardMarker.title = title
boardMarker.icon = self.image(image!, scaledToSize: CGSize(width: heightWidth, height: heightWidth))
func displayBoard() {
if self.boardNumber == "1" {
boardMarker.map = self.mapView
self.arrBoardMarker.append(boardMarker)
} else {
boardMarker.map = nil
}
}
displayBoard()
break
}
}
}
}
}
This is the function that displays the custom info window.
func mapView(_ mapView: GMSMapView, markerInfoWindow marker: GMSMarker) -> UIView? {
let Markerview: infoWindow = UIView.fromNib()
let a = arrCarMarkers.count
let b = arrLegMarker.count
let c = arrWaterMarker.count
let d = arrBoardMarker.count
let all = 0 + a + b + d + c
Markerview.titleLbl.text = arrAllMarkers[key].title
Markerview.ratingLbl.text = ("\(arrAllMarkers[all].rating)/5")
Markerview.difficultyLbl.text = arrAllMarkers[all].diff
Markerview.idKey.text = arrAllMarkers[all].key
transferKey = arrAllMarkers[all].key
Markerview.alpha = 0.8
Markerview.layer.cornerRadius = 30
return Markerview
}
Im not sure if what Im doing is even correct. Like I said I just want the data being snapshotted for each marker to be shown to that specific marker.
So I Managed to solve the issue.
I added
boardMarker.title = key
inside the loop where the Marker is being created.
I then wrote this section of code
func mapView(_ mapView: GMSMapView, markerInfoWindow marker: GMSMarker) -> UIView? {
let markerTitle = marker.title as! String
let Markerview: infoWindow = UIView.fromNib()
filteredStruct = arrAllMarkers.filter{$0.key.range(of: markerTitle) != nil}
print(filteredStruct)
Markerview.titleLbl.text = filteredStruct[0].title
Markerview.ratingLbl.text = filteredStruct[0].rating
Markerview.difficultyLbl.text = filteredStruct[0].diff
transferKey = markerTitle
Markerview.alpha = 0.8
Markerview.layer.cornerRadius = 30
print(transferKey)
return Markerview
}
and it works perfectly!

Button is Showing GMSMarkers but isn't Hiding it, How do I get it to do both

Im creating an app that shows different locations. Currently when a button is pressed the car locations pop up on the map, however I want to then hide those shown markers if that same button is pressed again.
This is the function that takes a snapshot of my database from firebase, it then inserts the GMSMarker into the location.
func showCarIcon() {
ref = Database.database().reference()
ref.child("location").observe(.childAdded) { (snapshot:DataSnapshot) in
if let dict = snapshot.value as? [String:AnyObject] {
if dict["Activity"] as! String == "Car" {
let longitude = dict["Longitude"] as! String
let lattitude = dict["Lattitude"] as! String
let title = dict["Title"] as! String
self.carIconArray.insert(coordinate(carLat: lattitude, carLng: longitude), at: 0)
let n = self.carIconArray.count
let heightWidth = self.mapView.frame.height
for marker in 1...n {
let carMarker = GMSMarker()
let carIconView = UIImage(named: "carPin")
let image = carIconView
let location = CLLocationCoordinate2D(latitude: Double(lattitude)!, longitude: Double(longitude)!)
carMarker.position = location
carMarker.icon = image
carMarker.title = title
carMarker.icon = self.image(image!, scaledToSize: CGSize(width: heightWidth/6, height: heightWidth/6))
func displayIt() {
if self.carNumber == "1" {
carMarker.map = self.mapView
} else {
carMarker.map = nil
}
}
displayIt()
}
}
}
}
}
So this is the action function for when button is pressed.
var carNumber = String()
#IBAction func showCar(_ sender: Any) {
if motorisedVehicleButtonActive {
motorisedVehicleButton.setImage(UIImage(named: "carO"), for: .normal)
carNumber = "1"
} else {
motorisedVehicleButton.setImage(UIImage(named: "car"), for: .normal)
carNumber = "0"
}
print(carNumber)
motorisedVehicleButtonActive = !motorisedVehicleButtonActive
showCarIcon()
}
Let me explain what is issue with your code.
You are creating new marker every time when button press. So, new marker have different object id than older.
When you try to remove it, it will not works just because of it's different marker than you placed on map.
So you need to store marker in array and on remove time, get icon from array and remove it from map.
First you need to create array of GMSMarker, because you have to store every marker which is placed on map.
So, write following line of code at top of your class.
var arrCarMarkers = [GMSMarker]()
Then after, store every marker in this array which are you placing on map.
So, update your code as follow:
func displayIt() {
if self.carNumber == "1" {
carMarker.map = self.mapView
arrCarMarkers.append(carMarker) // Here is store marker in array
} else {
carMarker.map = nil
}
}
Now, you have all marker which are placed on map. So when you want to remove these markers just update your code as follow:
#IBAction func showCar(_ sender: Any) {
if motorisedVehicleButtonActive {
motorisedVehicleButton.setImage(UIImage(named: "carO"), for: .normal)
carNumber = "1"
showCarIcon()
} else {
motorisedVehicleButton.setImage(UIImage(named: "car"), for: .normal)
carNumber = "0"
self.arrCarMarkers.forEach { $0.map = nil }
}
print(carNumber)
motorisedVehicleButtonActive = !motorisedVehicleButtonActive
}
Above code will remove all markers from map.
I hope this will works for you.