I have the following code in Swift to add an MKPolyline to a MapView. XCode isn't telling me there's an issue, and as far as I've read, this should be working.
Outlet for the MapView:
#IBOutlet weak var mapView: MKMapView!
Variable to hold the coordinates:
var coordinates: [CLLocationCoordinate2D] = []
Get my saved coordinates from Core Data:
var contextMap = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext!
var requestMap = NSFetchRequest(entityName: "Locations")
let predMap = NSPredicate(format: "game = %d", passedGameNumber)
requestMap.predicate = predMap
requestMap.sortDescriptors = [NSSortDescriptor(key:"time", ascending: false)]
self.locationsList = contextMap.executeFetchRequest(requestMap, error: nil)! as [Locations]
Add the coordinates from Core Data to my new array:
for index in 1..<self.locationsList.count{
var lat = Double(self.locationsList[index].latitude)
var long = Double(self.locationsList[index].longitude)
var coordinatesToAppend = CLLocationCoordinate2D(latitude: lat, longitude: long)
coordinates.append(coordinatesToAppend)
}
Create the polyline:
var polyLine = MKPolyline(coordinates: &coordinates, count: coordinates.count)
Add the overlay:
self.mapView.addOverlay(polyLine, level: MKOverlayLevel.AboveRoads)
Add it to the MapView:
func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer! {
if overlay.isKindOfClass(MKPolyline) {
// draw the track
let polyLine = overlay
let polyLineRenderer = MKPolylineRenderer(overlay: polyLine)
polyLineRenderer.strokeColor = UIColor.blueColor()
polyLineRenderer.lineWidth = 2.0
return polyLineRenderer
}
return nil
}
I simply get a blank MapView. I can print the coordinates array to the console, so I know the data has been added. Any ideas?
The above method will be written like this in Swift3:
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if overlay.isKind(of: MKPolyline.self) {
// draw the track
let polyLine = overlay
let polyLineRenderer = MKPolylineRenderer(overlay: polyLine)
polyLineRenderer.strokeColor = UIColor.blue
polyLineRenderer.lineWidth = 2.0
return polyLineRenderer
}
return MKPolylineRenderer()
}
As posted in the comments, the code in the question was fine. I simply wasn't setting the delegate.
mapView.delegate = self
Related
How can I pass more data with GMSmarker? For example how can I pass the place_ID? there is only one option for marker1.title. I tried marker2.title but it says marker2 could not be found in GMSmarker().
POIManager.testVariable.forEach {
let marker1 = GMSMarker()
marker1.position = CLLocationCoordinate2D(latitude: $0.geometry.location.lat, longitude: $0.geometry.location.lng)
marker1.title = $0.name
marker2.title = $0.place_id
marker1.map = mapView
locationManager.stopUpdatingLocation()
}
func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
print(marker1.title!)
print(marker2.title!)
return true
}
I want to build a map which shows the user location, the zoom scale and can be switched between standard maptype and hybrid maptype.
For testing reasons, I didn't integrate the mapstyle-picker yet. I helped me out by implementing the map.mapType variable, but that doesn't work.
Another problem I have is about the user location: I integrated a LocationManager class which returns the actual position - that works - but if I scroll or zoom on the map, after 5 seconds the screen jumps back to the user location.
I would be glad if you could help me with that. I attach my both files.
Thanks for your help!
UPDATE
So after I searched in stackoverflow I found this thread, SwiftUI mapkit set region to user's current location and implemented these into my code. But now I've the problem, that I didn't see my actual position when the map starts as centered view, I see only the hardcoded one.
MapModel.swift
struct MapModel: UIViewRepresentable {
#Binding var region: MKCoordinateRegion
var mapType : MKMapType
var userTracking: MKUserTrackingMode
var showsUserLocation: Bool
// var annotation: GCModel
init(
region: Binding<MKCoordinateRegion>,
mapType: MKMapType,
userTrackingMode: MKUserTrackingMode,
showsUserLocation: Bool = true
// annotation: GCModel = GCModel(title: "", coordinate: CLLocationCoordinate2D(latitude: 0, longitude: 0))
){
self._region = region
self.mapType = mapType
self.userTracking = userTrackingMode
self.showsUserLocation = showsUserLocation
// self.annotation = annotation
}
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.setRegion(region, animated: false)
mapView.mapType = mapType
mapView.showsUserLocation = showsUserLocation
mapView.userTrackingMode = userTracking
mapView.delegate = context.coordinator
// Add annotation to the map
// mapView.addAnnotation(annotation.pointAnnotation)
return mapView
}
func updateUIView(_ mapView: MKMapView, context: Context) {
mapView.mapType = mapType
// Update your region so that it is now your new region
mapView.setRegion(region, animated: false)
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, MKMapViewDelegate {
var parent: MapModel
init(_ parent: MapModel) {
self.parent = parent
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
// We should handle dequeue of annotation view's properly so we have to write this boiler plate.
// This basically dequeues an MKAnnotationView if it exists, otherwise it creates a new
// MKAnnotationView from our annotation.
guard annotation is MKPointAnnotation else { return nil }
let identifier = "Annotation"
guard let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) else {
let annotationView = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView.canShowCallout = true
return annotationView
}
annotationView.annotation = annotation
return annotationView
}
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
// We need to update the region when the user changes it
// otherwise when we zoom the mapview will return to its original region
DispatchQueue.main.async {
self.parent.region = mapView.region
}
}
}
}
MapView.swift
struct MapView: View {
#State var trackingMode: MKUserTrackingMode = .follow
#ObservedObject private var managerDelegate = LocationManager()
#State private var mapType: MKMapType = .standard
var body: some View {
VStack {
MapModel(
region: $managerDelegate.region,
mapType: mapType,
userTrackingMode: trackingMode,
showsUserLocation: true
).edgesIgnoringSafeArea([.bottom,.top])
Picker("", selection: $mapType) {
Text("Standard").tag(MKMapType.standard)
Text("Satellite").tag(MKMapType.satellite)
Text("Hybrid").tag(MKMapType.hybrid)
}
.pickerStyle(SegmentedPickerStyle())
.opacity(0.5)
Spacer()
}
}
}
I have been searching relentlessly for this solution and left with no choice except to rely on the expertise here on Stackoverflow. I am working on saving a map annotation if the app closes and I have been using the UserDefault to save the annotation.
This is the Objective C code that I found and I tried converting it into Swift and I think there is an error with it. I am not too sure. I place this code at viewDidLoad()
This is the save annotation
var pinnedAnnotation: CLLocationCoordinate2D = (parkedCarAnnotation?.coordinate)!
var coordinateData: NSData = NSData(bytesNoCopy: pinnedAnnotation, length: sizeof(pinnedAnnotation), freeWhenDone: false)
UserDefaults.standard.set(coordinateData, forKey: pinnedAnnotation)
UserDefaults.standard.synchronize()
And I needed a load annotation when the app open.
I dont know if viewDidLoad is the right place to put. Previously I put it in a mapView function of updatingLocation
Edited: Added code for further clarification of what I have done that needed to be corrected
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self as MKMapViewDelegate
checkLocationAuthorizationStatus()
tabBar.delegate = self
if UserDefaults.standard.object(forKey: "pinnedAnnotation") != nil {
let annotation = MKPointAnnotation()
self.mapView.addAnnotation(annotation)
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let pinnedAnnotation: CLLocationCoordinate2D = (parkedCarAnnotation?.coordinate)!
let locationData = ["latitude": parkedCarAnnotation?.coordinate.latitude, "longitude": parkedCarAnnotation?.coordinate.longitude]
UserDefaults.standard.set(locationData, forKey: "pinnedAnnotation")
UserDefaults.standard.synchronize()
print("Saving data ", UserDefaults.standard.set(locationData, forKey: "pinnedAnnotation"))
}
func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
if(item.tag == 0){
if mapView.annotations.count == 1{
mapView.addAnnotation(parkedCarAnnotation!)
} else {
mapView.removeAnnotation(mapView.annotations as! MKAnnotation)
}
}
extension ViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if let annotation = annotation as? ParkingSpot{
let identifier = "pin"
var view: MKPinAnnotationView
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
view.canShowCallout = true
view.animatesDrop = true
view.pinTintColor = UIColor.orange
view.calloutOffset = CGPoint(x: -8, y: -3)
view.rightCalloutAccessoryView = UIButton.init(type:.detailDisclosure) as UIView
return view
} else {
return nil
}
}
extension ViewController: CLLocationManagerDelegate{
func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) {
centerMapOnLocation(location: CLLocation(latitude: userLocation.coordinate.latitude, longitude: userLocation.coordinate.longitude))
let locationServiceCoordinate = LocationService.instance.locationManager.location!.coordinate
parkedCarAnnotation = ParkingSpot(title: "My Parking Spot", locationName: "Find your way back to your car location", coordinate: CLLocationCoordinate2D(latitude: locationServiceCoordinate.latitude, longitude: locationServiceCoordinate.longitude))
}
}
I'm not entirely sure what you're asking...
For starters when you're saving data to UserDefaults the key needs to be a string, I also believe you'll need to save you data in UserDefaults as a Dictionary
let locationData = ["lat": parkedCarAnnotation?.coordinate?.latitude, "long": parkedCarAnnotation?.coordinate.longitude]
UserDefaults.standard.set(locationData, forKey: "pinned_annotation")
And then to retrieve the data you would call
if let annotationData = UserDefaults.standard.object(forKey: "pinned_annotation") as? Dictionary {
guard let lat = annotationData["lat"], let long = annotationData["long"] else { return }
let coordinate = CLLocationCoordinate2D(latitude: lat, longitude: long)
}
Now you should hopefully be able to set your annotation with the coordinate
I am trying to create an application, one of its function is to drawing the line while users are moving.
Here is the class
class traceuserViewController: UIViewController,CLLocationManagerDelegate, MKMapViewDelegate {
var locationManager = CLLocationManager()
var startLocation: CLLocation?
var endLocation: CLLocation?
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
self.locationManager.distanceFilter = 30.0
self.locationManager.startMonitoringSignificantLocationChanges()
self.locationManager.startUpdatingLocation()
mapView.showsUserLocation = true
mapView.mapType = .hybrid
self.mapView.delegate = self
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
//user's current location
let nowlocation = locations.last
userLocations.append(nowlocation!)
print("HERE IS THE LOCATION ARRAY")
print(userLocations)
//show the current location region
let center = CLLocationCoordinate2D(latitude: nowlocation!.coordinate.latitude, longitude: nowlocation!.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.7, longitudeDelta: 0.7))
self.mapView.setRegion(region, animated: true)
drawRoute(locationArray: userLocations)
}
func drawRoute(locationArray: [CLLocation]) {
if (locationArray.count) > 1 {
var destinationLocIndex = (locationArray.count) - 1
var startLocIndex = (locationArray.count) - 2
let destinationloc = locationArray[destinationLocIndex].coordinate
let startLoc = locationArray[startLocIndex].coordinate
var routeArray = [startLoc, destinationloc]
//test if the function works well or not
print(routeArray)
var geodesicLine = MKGeodesicPolyline(coordinates: routeArray , count: routeArray.count)
mapView.add(geodesicLine, level: .aboveRoads)
}
}
//draw in the mapview
private func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer! {
if overlay is MKPolyline{
let polylineRenderer = MKPolylineRenderer(overlay: overlay)
polylineRenderer.strokeColor = UIColor.blue
polylineRenderer.lineWidth = 5.0
return polylineRenderer
}else{
os_log("Failed to draw the polyline", log: OSLog.default, type: .debug)
return nil
}
}
After many times trying, I still have no idea why it doesn't draw the route on the map when the user is moving, can anyone please I've me some hints?
cheers
I'm inferring that you are using Swift 3 from the code snippet (e.g. the signature of didUpdateLocations; the use of .hybrid rather than Swift 2.3's .Hybrid; etc.).
But, the signature for mapView(_:rendererFor:) is incorrect. In Swift 3, it is:
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
...
}
If you ever have a delegate method that doesn't appear to work, add a breakpoint in it and you can confirm if it's called at all or not (and if it is called, you can step through it and diagnose the problem further).
this is my code for drawing first Line,
for another line with another color how can i do ?????
func mapView(mapView : MKMapView! , rendererForOverlay overlay: MKOverlay!) ->MKOverlayRenderer! {
if overlay is MKPolyline {
var polyLineRenderer = MKPolylineRenderer(overlay: overlay)
polyLineRenderer.strokeColor = UIColor.blackColor()
polyLineRenderer.lineWidth = 10
return polyLineRenderer
}
return nil
}
Finally i found the way :
In the top of the class i put
var toGo : MKPolyline?
var toCome : MKPolyline?
and after that in view Did load :
var polyLineGoes = MKPolyline(coordinates: &coordinateGoes, count: coordinateGoes.count)
toGo = polyLineGoes
mapView.addOverlay(polyLineGoes)
var polyLineComes = MKPolyline(coordinates: &coordinateComes, count: coordinateComes.count)
toCome = polyLineComes
mapView.addOverlay(polyLineComes)
at the End of class :
func mapView(mapView : MKMapView! , rendererForOverlay overlay: MKOverlay!) ->MKOverlayRenderer! {
if overlay is MKPolyline {
if ( toGo != nil) && (toCome != nil ) {
if overlay as? MKPolyline == toGo {
var polyLineRenderer = MKPolylineRenderer(overlay: overlay)
polyLineRenderer.strokeColor = UIColor.redColor()
polyLineRenderer.lineWidth = 3
return polyLineRenderer
} else if overlay as? MKPolyline == toCome {
print(overlay.description)
var polyLineRenderer = MKPolylineRenderer(overlay: overlay)
polyLineRenderer.strokeColor = UIColor.blueColor()
polyLineRenderer.lineWidth = 3
return polyLineRenderer
}
}
}
return nil
}
I was facing a same problem, but I just find another way to solve it.
In the mapView the code will be:
func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer!{
if overlay is MKPolyline {
var polylineRenderer = MKPolylineRenderer(overlay: overlay)
polylineRenderer.strokeColor = variables.lineColor
polylineRenderer.lineWidth = 4
return polylineRenderer
}
return nil
}
and I just have a struct named variables, and it contains variable lineColor. Also, I have another CONSTANT struct it has some CONSTANT to make code in mapView class more readable. The code in these two structs will be:
struct CONSTANT {
static let greenColor = UIColor.greenColor().colorWithAlphaComponent(0.5)
static let blueColor = UIColor.blueColor().colorWithAlphaComponent(0.5)
static let redColor = UIColor.redColor().colorWithAlphaComponent(0.5)
}
struct variables {
// let the default lineColor be green
static var lineColor = CONSTANT.greenColor
}
With these, I will just change my variables.lineColor to the color in CONSTANT struct. For example,
lineColor = CONSTANT.greenColor
mapView.addOverlay(myPolyLine, level: MKOverlayLevel.AboveRoads)
lineColor = CONSTANT.blueColor
mapView.addOverlay(myPolyLine, level: MKOverlayLevel.AboveRoads)
lineColor = CONSTANT.redColor
mapView.addOverlay(myPolyLine, level: MKOverlayLevel.AboveRoads)
First post here :D
I have found yet another way.
First, we need to extend MKPolyline:
extension MKPolyline {
struct ColorHolder {
static var _color: UIColor?
}
var color: UIColor? {
get {
return ColorHolder._color
}
set(newValue) {
ColorHolder._color = newValue
}
}
}
In viewDidLoad, we can assign every polyline a color now:
var polyline1 = MKPolyline(coordinates: coordinates1, count: coordinates1.count)
polyline1.color = UIColor(.red)
mapView.addOverlay(polyline1)
var polyline2 = MKPolyline(coordinates: coordinates2, count: coordinates2.count)
polyline2.color = UIColor(.green)
mapView.addOverlay(polyline2)
Finally, we have our mapView-function:
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if let overlay_ = overlay as? MKPolyline {
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.strokeColor = overlay_.color
return renderer
} else {
return MKOverlayRenderer()
}
}