I'm on Swift 5 and Xcode 11, currently trying to create a map with predefined annotations using custom images instead of the default markers. I've been playing around with Apple's sample code for AnnotatingMapWithCustomData and could tailor it to what I needed. But once I tried to copy the code into my own project, the annotations showed up as default markers instead of the custom annotationViews I configured.
import UIKit
import MapKit
class MapViewController: UIViewController {
#IBOutlet private weak var mapView: MKMapView!
private var allAnnotations: [MKAnnotation]?
private var displayedAnnotations: [MKAnnotation]? {
willSet {
if let currentAnnotations = displayedAnnotations {
mapView.removeAnnotations(currentAnnotations)
}
}
didSet {
if let newAnnotations = displayedAnnotations {
mapView.addAnnotations(newAnnotations)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
registerMapAnnotationViews()
// Create the array of annotations and the specific annotations for the points of interest.
allAnnotations = [ShrekAnnotation(), CoffeeAnnotation()]
// Dispaly all annotations on the map.
displayedAnnotations = allAnnotations
centerMapOnLondon()
}
/// - Tag: RegisterAnnotationViews
private func registerMapAnnotationViews() {
mapView.register(MKAnnotationView.self, forAnnotationViewWithReuseIdentifier: NSStringFromClass(ShrekAnnotation.self))
mapView.register(MKAnnotationView.self, forAnnotationViewWithReuseIdentifier: NSStringFromClass(CoffeeAnnotation.self))
}
private func centerMapOnLondon() {
let span = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
let center = CLLocationCoordinate2D(latitude: 51.507911, longitude: -0.132222)
mapView.setRegion(MKCoordinateRegion(center: center, span: span), animated: true)
}
}
extension MapViewController: MKMapViewDelegate {
/// - Tag: CreateAnnotationViews
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard !annotation.isKind(of: MKUserLocation.self) else {
// Make a fast exit if the annotation is the `MKUserLocation`, as it's not an annotation view we wish to customize.
return nil
}
var annotationView: MKAnnotationView?
if let annotation = annotation as? ShrekAnnotation {
annotationView = setupShrekAnnotationView(for: annotation, on: mapView)
} else if let annotation = annotation as? CoffeeAnnotation {
annotationView = setupCoffeeAnnotationView(for: annotation, on: mapView)
}
return annotationView
}
/// - Tag: ConfigureAnnotationViews
private func setupShrekAnnotationView(for annotation: ShrekAnnotation, on mapView: MKMapView) -> MKAnnotationView {
let reuseIdentifier = NSStringFromClass(ShrekAnnotation.self)
let view = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier, for: annotation)
view.canShowCallout = true
// Provide the annotation view's image.
let image = #imageLiteral(resourceName: "map-shrek1")
view.image = image
return view
}
/// - Tag: ConfigureAnnotationViews
private func setupCoffeeAnnotationView(for annotation: CoffeeAnnotation, on mapView: MKMapView) -> MKAnnotationView {
let reuseIdentifier = NSStringFromClass(CoffeeAnnotation.self)
let view = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier, for: annotation)
view.canShowCallout = true
// Provide the annotation view's image.
let image = #imageLiteral(resourceName: "map-cof1")
view.image = image
return view
}
}
Apparently the class extension is not being called but I don't know how to fix this. The code that worked before I tried implementing it in my own project is identical. Could I have missed something I should've copied too or can you find the reason for the bug in the code?
Thanks for any help.
I think you are not setting the mapView delegate hence viewFor annotation is not being triggered and your custom annotation view is not appearing on map. Set
mapView.delegate = self
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
registerMapAnnotationViews()
// Create the array of annotations and the specific annotations for the points of interest.
allAnnotations = [ShrekAnnotation(), CoffeeAnnotation()]
// Dispaly all annotations on the map.
displayedAnnotations = allAnnotations
centerMapOnLondon()
}
Hope this helps
Related
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'm working in SwiftUI and using MKMapView to show a map, annotations, and overlays on a view.
I was using the new Map() but the lack of overlay support has pulled me back to UIKit Representable.
When I was using Map it was easy to add annotations, but when using UIKitRepresentable I'm a bit confused on where to put the data, and how to make annotations from an array pulled from a network call.
Everything I've read has been either in Obj-C or adding a single annotation point. I'm trying to add (at present ~800) which is why I wanted to take advantage of the MKMapView in its reusability and clustering.
This is what I have at the moment:
struct UIMapView: UIViewRepresentable {
#EnvironmentObject var dataModel: DataModel
#EnvironmentObject var mapViewModel: MapViewModel
func makeCoordinator() -> Coordinator { Coordinator() }
func makeUIView(context: Context) -> MKMapView {
let view = mapViewModel.mapView
drawOverlayRing(view: view)
view.delegate = context.coordinator
return view
}
func updateUIView(_ uiView: MKMapView, context: Context) {}
class Coordinator: NSObject, MKMapViewDelegate {
func mapView(_ map: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation { return nil }
let identifier = "pinAnnotation"
var annotationView = map.dequeueReusableAnnotationView(
withIdentifier: identifier
) as? MKPinAnnotationView
if annotationView == nil {
annotationView = MKPinAnnotationView(
annotation: annotation,
reuseIdentifier: identifier
)
annotationView?.canShowCallout = true
} else {
annotationView?.annotation = annotation
}
return annotationView
}
}
func getAnnotations(view: MKMapView) {
for location in dataModel.locations {
let annotation = MKPointAnnotation()
annotation.title = location.title
annotation.coordinate = CLLocationCoordinate2D(
latitude: location.latitude,
longitude: location.longitude
)
view.addAnnotation(annotation)
}
}
}
I sometimes put my annotations in updateUIView, something like this:
func updateUIView() {
// remove the old ones
uiView.removeAnnotations(uiView.annotations)
uiView.addAnnotations(toMapAnnotations(locations: dataModel.locations))
}
func toMapAnnotations(locations: [CLLocationCoordinate2D]) -> [MapAnnotation] {
return locations.map { MapAnnotation(location: $0) }
}
final class MapAnnotation: NSObject, MKAnnotation {...}
The code will be provided later, since updateUIView is called multiple times.
I think it is best to avoid calling addAnnotations.
I'm struggling to get KVO updates within a callout already displayed.
My use case: I want to display on an open callout the real time distance between user location and the annotation I add to the map. Annotation does not change its position.
I add annotations to mapView, using a custom annotation I have defined. No issue here.
On each annotation selected, the callout displays all the information defined in the custom annotation
However, the distance is refreshed in the callout ONLY if I unselect the annotation and reselect it
The distance property is declared as #objc dynamic so it can be observed.
I compute the distance each time the user location change. This part works too.
I cannot figure out what I'm missing to have the callout updated without closing and reopening it.
The code I'm using is what is described here by Rob: Swift -How to Update Data in Custom MKAnnotation Callout?
So my question: is it possible to change realtime a value (observed) in a notificationView callout ? If yes is KVO the best approach ?
In the link below, how would be implemented the mapView viewFor method ?
Any example would be very helpful.
It's my first post here, so please if I did it wrong, let me know and I will provide more information and details.
But my situation is trivial: the standard callout performs Key-Value Observation (KVO) on title and subtitle. (And the annotation view observes changes to coordinate.). But how to display change of values in the current open callout ? That is the think I do not get.
CustomAnnotation class:
class CustomAnnotation: NSObject, MKAnnotation {
#objc dynamic var title: String?
#objc dynamic var subtitle: String?
#objc dynamic var coordinate: CLLocationCoordinate2D
#objc dynamic var distance: CLLocationDistance
var poiColor: String?
var poiPhone: String?
init(title: String, subtitle: String, coordinate: CLLocationCoordinate2D, poiColor: String, poiPhone: String, distance: CLLocationDistance) {
self.title = title
self.subtitle = subtitle
self.coordinate = coordinate
self.poiColor = poiColor
self.poiPhone = poiPhone
self.distance = distance
super.init()
}
}
CustomAnnotationView class:
class CustomAnnotationView: MKMarkerAnnotationView {
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
displayPriority = .required
canShowCallout = true
detailCalloutAccessoryView = createCallOutWithDataFrom(customAnnotation: annotation as? CustomAnnotation)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
removeAnyObservers()
}
override var annotation: MKAnnotation? {
didSet {
removeAnyObservers()
if let customAnnotation = annotation as? CustomAnnotation {
updateAndAddObservers(for: customAnnotation)
}
}
}
private var subtitleObserver: NSKeyValueObservation?
private var distanceObserver: NSKeyValueObservation?
private let subtitleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
private let distanceLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
}
private extension CustomAnnotationView {
func updateAndAddObservers(for customAnnotation: CustomAnnotation) {
subtitleLabel.text = customAnnotation.subtitle
subtitleObserver = customAnnotation.observe(\.subtitle) { [weak self] customAnnotation, _ in
self?.subtitleLabel.text = customAnnotation.subtitle
}
let locationManager = CLLocationManager()
let theLatitude:CLLocationDegrees = (locationManager.location?.coordinate.latitude)!
let theLongitude:CLLocationDegrees = (locationManager.location?.coordinate.longitude)!
// Get pin location
let pointLocation = CLLocation(latitude: customAnnotation.coordinate.latitude, longitude: customAnnotation.coordinate.longitude)
//Get user location
let userLocation = CLLocation(latitude: theLatitude, longitude: theLongitude)
// Return distance en meters
let distanceFromUser = pointLocation.distance(from: userLocation)
customAnnotation.distance = distanceFromUser*100
distanceLabel.text = String(format: "%.03f", customAnnotation.distance)+" cm"
distanceObserver = customAnnotation.observe(\.distance) { [weak self] customAnnotation, _ in
self?.distanceLabel.text = "\(customAnnotation.distance) cm"
}
}
func removeAnyObservers() {
subtitleObserver = nil
distanceObserver = nil
}
func createCallOutWithDataFrom(customAnnotation: CustomAnnotation?) -> UIView {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = true
view.addSubview(subtitleLabel)
view.addSubview(distanceLabel)
NSLayoutConstraint.activate([
subtitleLabel.topAnchor.constraint(equalTo: view.topAnchor),
subtitleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor),
subtitleLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor),
subtitleLabel.bottomAnchor.constraint(equalTo: distanceLabel.topAnchor),
distanceLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor),
distanceLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor),
distanceLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
if let customAnnotation = customAnnotation {
updateAndAddObservers(for: customAnnotation)
}
return view
}
}
And to finish:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation { return nil }
let annotation = annotation as? CustomAnnotation
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "CustomAnnotation") as? CustomAnnotationView
if annotationView == nil {
annotationView = CustomAnnotationView(annotation: annotation, reuseIdentifier: "CustomAnnotation")
annotationView?.canShowCallout = true
} else {
annotationView?.annotation = annotation
}
return annotationView
}
Thank you.
You would appear to have correctly configured the observers for the subtitle and distance. The problem is that a change in location is not triggering an update to distance. Thus, there is nothing triggering the KVO.
You have an observer for distance, which will trigger an update of the label. But you are not changing distance. You should remove the CLLocationManager code from that routine where you add the observers, and instead create a location manager (not within the annotation view, though) which uses its delegate to update all of the annotation distances, e.g.:
class ViewController: UIViewController {
#IBOutlet weak var mapView: MKMapView!
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.distanceFilter = 5
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
}
extension ViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let currentLocation = locations.last(where: { $0.horizontalAccuracy >= 0 }) else { return }
mapView.annotations
.compactMap { $0 as? CustomAnnotation }
.forEach {
$0.distance = CLLocation(latitude: $0.coordinate.latitude, longitude: $0.coordinate.longitude)
.distance(from: currentLocation)
}
}
}
Obviously, you would remove the CLLocationManager code from updateAndAddObservers.
I am trying to drop annotations/pins wherever a user touches a certain location on the map. Whenever a user drops more 2 or more pins, it creates a polyline that connects the points. I got it to work in regular Swift 5, but I am trying to make it work in SwiftUI. I am using Mapbox. I am having trouble figuring out how to make it work with the coordinator. Can anyone help me figure this out? Thanks!
import SwiftUI
import Mapbox
// Creates an annotation using title & coordinate
extension MGLPointAnnotation {
convenience init(title: String, coordinate: CLLocationCoordinate2D) {
self.init()
self.title = title
self.coordinate = coordinate
}
}
// Represents & Displays an MGLMapView in SwiftUI
struct MapView: UIViewRepresentable {
// Property binding to add annotations
#Binding var annotations: [MGLPointAnnotation]
//var mapView: MGLMapView!
//var coordinates = [CLLocationCoordinate2D]()
// Creates a mapView with MGLMapView type
private let mapView: MGLMapView = MGLMapView(frame: .zero, styleURL: MGLStyle.streetsStyleURL)
// Needed function for UIViewRepresentable
func makeUIView(context: UIViewRepresentableContext<MapView>) -> MGLMapView {
mapView.delegate = context.coordinator
// Add a single tap gesture recognizer. This gesture requires the built-in MGLMapView tap gestures (such as those for zoom and annotation selection) to fail.
let singleTap = UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleMapTap(sender:)))
for recognizer in mapView.gestureRecognizers! where recognizer is UITapGestureRecognizer {
singleTap.require(toFail: recognizer)
}
mapView.addGestureRecognizer(singleTap)
// Convert `mapView.centerCoordinate` (CLLocationCoordinate2D) to screen location (CGPoint).
let centerScreenPoint: CGPoint = mapView.convert(mapView.centerCoordinate, toPointTo: nil)
print("Screen center: \(centerScreenPoint) = \(mapView.center)")
return mapView
}
// Needed function for UIViewRepresentable
func updateUIView(_ uiView: MGLMapView, context: UIViewRepresentableContext<MapView>) {
updateAnnotations()
uiView.addAnnotations(annotations)
}
// Styles the map with a MapBox Studio URL
func styleURL(_ styleURL: URL) -> MapView {
mapView.styleURL = styleURL
return self
}
// Specifies where the map is centered
func centerCoordinate(_ centerCoordinate: CLLocationCoordinate2D) -> MapView {
mapView.centerCoordinate = centerCoordinate
return self
}
// Specifies the zoom level of the initial view
func zoomLevel(_ zoomLevel: Double) -> MapView {
mapView.zoomLevel = zoomLevel
return self
}
// Updates the annotations in the view
private func updateAnnotations() {
if let currentAnnotations = mapView.annotations {
mapView.removeAnnotations(currentAnnotations)
}
mapView.addAnnotations(annotations)
}
// Makes the coordinater (coordinator class below)
func makeCoordinator() -> MapView.Coordinator {
Coordinator(self, mapView)
}
// A coordinator used with a delegate to add the annotation view to the map
// A coordinator class is declared to implement and view MGLMapViewDelegate in SwiftUI
final class Coordinator: NSObject, MGLMapViewDelegate {
var control: MapView
var mapView: MGLMapView!
var coordinates = [CLLocationCoordinate2D]()
var pointAnnotations = [MGLPointAnnotation]()
init(_ control: MapView, _ mapView: MGLMapView) {
self.control = control
self.mapView = mapView
}
func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
return true
}
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
return nil
}
#objc func handleMapTap(sender: UITapGestureRecognizer) {
// Convert tap location (CGPoint) to geographic coordinate (CLLocationCoordinate2D).
let tapPoint: CGPoint = sender.location(in: mapView)
let tapCoordinate: CLLocationCoordinate2D = mapView.convert(tapPoint, toCoordinateFrom: mapView)
print("You tapped at: \(tapCoordinate.latitude), \(tapCoordinate.longitude)")
// Create an array of coordinates for our polyline, starting at the center of the map and ending at the tap coordinate.
//var coordinates: [CLLocationCoordinate2D] = [mapView.centerCoordinate]
coordinates.append(tapCoordinate)
print("Coordinates list: \(coordinates)")
//var pointAnnotations = [MGLPointAnnotation]()
for coordinate in coordinates {
let point = MGLPointAnnotation()
point.coordinate = coordinate
point.title = "\(coordinate.latitude), \(coordinate.longitude)"
pointAnnotations.append(point)
print("Annotations: \(pointAnnotations)")
let polyline = MGLPolyline(coordinates: &coordinates, count: UInt(coordinates.count))
mapView.addAnnotation(polyline)
}
mapView.addAnnotations(pointAnnotations)
// Remove any existing polyline(s) from the map.
//if mapView.annotations?.count != nil, let existingAnnotations = mapView.annotations {
//mapView.removeAnnotations(existingAnnotations)
//}
// Add a polyline with the new coordinates.
//let polyline = MGLPolyline(coordinates: &coordinates, count: UInt(coordinates.count))
//mapView.addAnnotation(polyline)
}
}
}
First of all, the single tap cannot work if making it 'to fail' for other recognisers, (I don't know why that happened in SwiftUI). Removing the for loop makes it work, but also disables other single tap features on the map, for example, annotations will not be clickable.
Therefore, I suggest using some other gesture instead, the following example is based on long press.
func makeUIView(context: UIViewRepresentableContext<MapView>) -> MGLMapView {
mapView.delegate = context.coordinator
let longTap = UILongPressGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleMapTap(sender:)))
mapView.addGestureRecognizer(longTap)
return mapView
}
Second, the 'mapView' in the handler function should be 'control.mapView' according to the first line of your 'Coordinator' class.
#objc func handleMapTap(sender: UILongPressGestureRecognizer) {
// Run the code unless user end the long press (Long press handler keep calling during press)
//guard sender.state == .ended else { return }
// Convert tap location (CGPoint) to geographic coordinate (CLLocationCoordinate2D).
let locationInMap = sender.location(in: control.mapView)
let coordinateSet = control.mapView.convert(locationInMap, toCoordinateFrom: nil)
print("You tapped at: \(coordinateSet.latitude)")
if sender.state == UIGestureRecognizer.State.ended {
print("You final tapped at: \(coordinateSet.latitude)")
}
}
I faced with the same issue with you.
I updated my library to the newest version and every thing worked!
Summary: Linking (NOT Query) Annotation with Parse from app
Completed:
What I'm trying to do is have the newly created annotation, become a PFObject as a result. The way I am attempting to do this is to get the coordinates based off of the MKPointAnnotation. Then turn that into a PFObject with coordinates. Then able to be Query later as a live update.
Problem
Still an issue is that with the code mentioned I'm not able to have the MKPointAnnotation which allows for me to add the title and UIImage, other than that all I would need tips on would be combining all of that together to finally be able to be queried from parse.
func handleLongPress(getstureRecognizer : UIGestureRecognizer) {
if getstureRecognizer.state != .Began { return }
let touchPoint = getstureRecognizer.locationInView(self.mapView)
let touchMapCoordinate = mapView.convertPoint(touchPoint, toCoordinateFromView: mapView)
createAnnotation(touchMapCoordinate)
}
private func createAnnotation(coordinate: CLLocationCoordinate2D) {
print("createAnnotation")
let myAnnotation = MyAnnotation(coordinate: coordinate)
mapView.addAnnotation(myAnnotation)
myAnnotation.saveInBackground()
}
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if !(annotation is MKPointAnnotation) {
return nil
}
let reuseId = "test"
var pinView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId)
if pinView == nil {
pinView = MyAnnotation(annotation: annotation, reuseIdentifier: reuseId)
pinView!.image = UIImage(named:"Cloud9")
pinView!.canShowCallout = true
let rightButton: AnyObject! = UIButton(type: UIButtonType.ContactAdd)
pinView?.rightCalloutAccessoryView = rightButton as? UIView
It is not quite clear to me what you are asking about (for example with Linking (NOT Query) or become a PFObject as a result).
I have understood your problem as being the following:
A series of CLLocationCoordinate2D are created by the user
You want to easily add these coordinates to the MKMapView
You want to easily store these coordinates in the Parse backend
I think the best solution for you is to create a custom class which subclasses PFObject and implements MKAnnotation (I'm not quite sure what you mean by and that the only solution is to subclass the PFObject, which at its current state there is no help with that either.)
I've tried to put together a sample project with the following three files. It's nice and clean since you can have the following three lines of code to achieve the two goals outlined above:
let myAnnotation = MyAnnotation(coordinate: coordinate)
mapView.addAnnotation(myAnnotation)
myAnnotation.saveInBackground() // Handle this background save better
Let me know if this helps with your problem, otherwise please rephrase your question to be more clear.
UPDATE 1: OP wanted title, subtitle and image functionality.
The title and subtitle properties are not unique to MKPointAnnotation, but rather a part of the MKAnnotation protocol, which is used to create the MyAnnotation in my example. This means you simply need to add these two properties to MyAnnotation in order to get the functionality you want.
Concerning the image, then this is it not a unique feature for MKPinAnnotationView, but again for the more generic MKAnnotationView. In reality, the image property has not purpose in the MKPinAnnotationView. As described in the documentation for the image property:
You can use the `MKAnnotationView` class as is or subclass it to provide custom behavior as needed.
The `image` property of the class lets you set the appearance of the annotation view without subclassing directly.
When using the MKPinAnnotationView, then the view will always be a pin, which is why the image property has not purpose.
So in order to use this image property, then you'll need to make use of MKAnnotationView in the delegation method mapView:viewForAnnotation: from MKMapViewDelegate.
Remember to set the delegate property of the mapView (I've set it in the storyboard).
I've updated my answer in order to reflect the changes.
The code:
//
// ViewController.swift
// ParseAnnotationFun
//
// Created by Stefan Veis Pennerup on 28/01/16.
// Copyright © 2016 Kumuluzz. All rights reserved.
//
import UIKit
import MapKit
class ViewController: UIViewController, MKMapViewDelegate {
// MARK: - Storyboard outlets
#IBOutlet weak var mapView: MKMapView!
// MARK: - Gesture Recognizers
#IBAction func longPressed(sender: UILongPressGestureRecognizer) {
print("longPressed")
if sender.state != .Began { return }
let touchPoint = sender.locationInView(mapView)
let touchMapCoordinate = mapView.convertPoint(touchPoint, toCoordinateFromView: mapView)
createAnnotation(touchMapCoordinate)
}
// MARK: - Model
private func createAnnotation(coordinate: CLLocationCoordinate2D) {
print("createAnnotation")
let myAnnotation = MyAnnotation(coordinate: coordinate)
mapView.addAnnotation(myAnnotation)
myAnnotation.saveInBackground() // Handle this background save better
}
// MARK: - MKMapViewDelegate
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
let id = "someIdentifier"
var view = mapView.dequeueReusableAnnotationViewWithIdentifier(id)
if view == nil {
view = MKAnnotationView(annotation: annotation, reuseIdentifier: id)
view?.canShowCallout = true
view?.image = UIImage(named: "oranges")
}
view?.annotation = annotation
return view
}
}
//
// MyAnnotation.swift
// ParseAnnotationFun
//
// Created by Stefan Veis Pennerup on 28/01/16.
// Copyright © 2016 Kumuluzz. All rights reserved.
//
import Foundation
import Parse
import MapKit
class MyAnnotation: PFObject, PFSubclassing, MKAnnotation {
// MARK: - Properties
#NSManaged var location: PFGeoPoint
// MARK: - Initializers
init(coordinate: CLLocationCoordinate2D) {
super.init()
location = PFGeoPoint(latitude: coordinate.latitude, longitude: coordinate.longitude)
}
override class func initialize() {
struct Static {
static var onceToken : dispatch_once_t = 0;
}
dispatch_once(&Static.onceToken) {
self.registerSubclass()
}
}
// MARK: - PFSubclassing protocol
static func parseClassName() -> String {
return "AnnotationPins"
}
// MARK: - MKAnnotation protocol
var coordinate: CLLocationCoordinate2D {
return CLLocationCoordinate2DMake(location.latitude, location.longitude)
}
var title: String? = "Awesome title"
var subtitle: String? = "Random subtitle"
}
//
// AppDelegate.swift
// ParseAnnotationFun
//
// Created by Stefan Veis Pennerup on 28/01/16.
// Copyright © 2016 Kumuluzz. All rights reserved.
//
import UIKit
import Parse
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
MyAnnotation.initialize()
Parse.setApplicationId("xxx",
clientKey: "xxx")
return true
}
}