How Do I add Pins (Annotations) with Xcode 6 (Swift) - ios

I'm new to the swift language, and haven't done an application with mapkit yet. But I have the map and regions set, but I'm hung up on how to allow users to add pins.
Let me clarify, I have no idea of even where to start, All I have at the moment (for the pins) is my variable, but I'm not even sure if that's correct. Any help would be much appreciated!!
What I have...
var MyPins: MKPinAnnotatoinView!
......
override func viewDidLoad() {
super.viewDidLoad()
Mapview code
.....
.....
}

Your pin variable is correct. Now you just need to add this annotation to MKMapView.
You can also create a custom class for MKAnnotation to add custom annotation to map view.
A beta demo for MapExampleiOS8 => Which supports Swift 2.1
Follow steps below:
1. Add MapKit.framework to project.
2. Create Storyboard variable IBOutlet of map view control or create it in view controller. Set delegate for this variable to override it's delegate methods:
Add delegate signature to view controller interface:
class ViewController: UIViewController, MKMapViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Set map view delegate with controller
self.mapView.delegate = self
}
}
3. Override its delegate methods:
Here we need to override mapView(_:viewForAnnotation:) method to show annotation pins on map.
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if (annotation is MKUserLocation) {
return nil
}
if (annotation.isKind(of: CustomAnnotation.self)) {
let customAnnotation = annotation as? CustomAnnotation
mapView.translatesAutoresizingMaskIntoConstraints = false
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "CustomAnnotation") as MKAnnotationView!
if (annotationView == nil) {
annotationView = customAnnotation?.annotationView()
} else {
annotationView?.annotation = annotation;
}
self.addBounceAnimationToView(annotationView)
return annotationView
} else {
return nil
}
}
4. Add MKPointAnnotation to map view.
You can add pin to location in map view. For simplicity add code to viewDidLoad() method.
override func viewDidLoad() {
super.viewDidLoad()
// Set map view delegate with controller
self.mapView.delegate = self
let newYorkLocation = CLLocationCoordinate2DMake(40.730872, -74.003066)
// Drop a pin
let dropPin = MKPointAnnotation()
dropPin.coordinate = newYorkLocation
dropPin.title = "New York City"
mapView.addAnnotation(dropPin)
}

You will need to call a method for when and where the user needs to add the pin. If you want it to add a pin where the user taps and holds on the map, you will need to call a gestureRecognizer, but if you want to do it via a button you will obviously just call that in an action. Either way the documentation for adding pins is throughly discussed Here

Related

Disappearing markers during clustering in MapKit [duplicate]

I have an array of latitudes and another array of longitudes that I add to an array of type CLLocationCoordinate2D. I then use the new array to annotate multiple points on the map. Some, or most, or maybe even all of the annotations are displaying on the map but as I zoom in (yes, zoom IN), some of the annotations disappear, then come back, or dont. Any ideas on how to keep them all visible? This is behavior I would expect while zooming out, not in.
Here is the code i'm using for what i've described above.
import UIKit
import MapKit
import CoreLocation
class MultiMapVC: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var multiEventMap: MKMapView!
var latDouble = Double()
var longDouble = Double()
let manager = CLLocationManager()
var receivedArrayOfLats = [Double]()
var receivedArrayOfLongs = [Double]()
var locations = [CLLocationCoordinate2D]()
func locationManager(_ manager: CLLocationManager, didUpdateLocations uLocation: [CLLocation]) {
let userLocation = uLocation[0]
let span:MKCoordinateSpan = MKCoordinateSpanMake(0.3, 0.3)
let usersLocation = userLocation.coordinate
let region:MKCoordinateRegion = MKCoordinateRegionMake(usersLocation, span)
multiEventMap.setRegion(region, animated: true)
manager.distanceFilter = 1000
self.multiEventMap.showsUserLocation = true
}
func multiPoint() {
var coordinateArray: [CLLocationCoordinate2D] = []
print ("Received Longitude Count = \(receivedArrayOfLongs.count)")
print ("Received Latitude Count = \(receivedArrayOfLats.count)")
if receivedArrayOfLats.count == receivedArrayOfLongs.count {
for i in 0 ..< receivedArrayOfLats.count {
let eventLocation = CLLocationCoordinate2DMake(receivedArrayOfLats[i], receivedArrayOfLongs[i])
coordinateArray.append(eventLocation)
print (coordinateArray.count)
}
}
for events in coordinateArray {
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2D(latitude: events.latitude, longitude: events.longitude)
multiEventMap.addAnnotation(annotation)
}
}
override func viewDidLoad() {
super.viewDidLoad()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestWhenInUseAuthorization()
manager.startUpdatingLocation()
multiPoint()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
multiEventMap.removeFromSuperview()
self.multiEventMap = nil
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
NiltiakSivad's solution works but it reverts to the old iOS 10 look. If you want to keep the new iOS 11 balloon markers for iOS 11 and use the old pin look only for older iOS versions then you can implement the delegate method as below:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let reuseIdentifier = "annotationView"
var view = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier)
if #available(iOS 11.0, *) {
if view == nil {
view = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier)
}
view?.displayPriority = .required
} else {
if view == nil {
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier)
}
}
view?.annotation = annotation
view?.canShowCallout = true
return view
}
The accepted answer from Leszek Szary is correct.
But there is some fineprint. Sometimes MKMarkerAnnotationViews are not rendered, even if
view.displayPriority = .required
is set.
What you are seeing is a combination of different rules.
MKAnnotationViews are rendered from top to bottom of the map. (It doesn't matter where north is).
If MapKit decides to draw overlapping MKAnnotationViews, then the MKAnnotationView nearer to the bottom is drawn on top (because it's drawn later)
Not only MKAnnotationViews, also titles rendered below MKMArkerAnnotationViews need space. The rendering of those titles is influenced by markerView.titleVisibility. If markerView.titleVisibility is set to .visible (instead of the default .adaptive), then this title is stronger than a MarkerAnnotationView that is rendered later, even if the later MarkerAnnotationView has a displayPriority = .required. The MarkerAnnotationView nearer to the bottom is not rendered.
This even happens if the MarkerAnnotationView nearer to the top has a low displayPriority. So a MarkerAnnotationView with low displayPriority and .titleVisibility = .visible can make a MarkerAnnotationView nearer to the bottom with displayPriority = .required disappear.
I am not aware of a documentation of this behaviour. This is the result of my experiments with iOS 12. My description is sipmplified.
I was experiencing a similar issue. My best guess is that it has something to do with how iOS 11 detects pin collisions. Implementing a custom annotation view or reverting to use the iOS 10 pin fixed the problem for me.
For example, implementing the following should fix your code:
class MultiMapVC: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard let annotation = annotation as? MKPointAnnotation else { return nil }
let identifier = "pin-marker"
var view: MKAnnotationView
if let dequeuedView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKPinAnnotationView {
dequeuedView.annotation = annotation
view = dequeuedView
} else {
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
}
return view
}
}
If this doesn't work, there is a displayPriority property that is worth looking into as it is responsible for helping to determine when pins should be hidden/shown at different zoom levels. More info at https://developer.apple.com/documentation/mapkit/mkannotationview/2867298-displaypriority
Hope this helps.
I was setting annotationView.displayPriority = .required only when the MKMarkAnnotationView was first allocated. Normally thats all you should need to do, but setting it each time the cell was reused fixed the issue for me.
I was seeing a similar problem with Xcode 10, and iOS 12+ as my deployment target. A post from an Apple staffer (https://forums.developer.apple.com/thread/92839) recommends toggling the .isHidden property on the dequeued marker. That has improved things but has not completely solved the problem.
result?.isHidden = true
result?.isHidden = false
return result

Swift how to segue views by tapping a right accessory callout button on a map annotation

I am rather new to swift and object oriented programming and I am trying to switch views from a view that contains a map to another view by tapping on a right accessory callout button in the annotation of a pin that is dropped by a long press. In the storyboard, I have created a segue between the two views and assigned the segue with the identifier mapseg. Unfortunately, after trying everything I could find via google I cannot get the segue to occur when I tap the right accessory callout button and have no idea why.The application itself is a tabbed application with three tabs. The view for the second tab is the one that contains the map. Also, I don't know if this could have something to do with it, but the view I am trying to transition from is embedded in a navigation controller. Here is my code for the view that I am trying to transition from.
import UIKit
import MapKit
class SecondViewController: UIViewController, MKMapViewDelegate {
#IBOutlet weak var MapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
self.MapView.delegate = self
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
let reuseID = "pin"
var pinView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseID) as? MKPinAnnotationView
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseID)
pinView!.canShowCallout = true
pinView!.animatesDrop = true
pinView!.enabled=true
let btn = UIButton(type: .DetailDisclosure)
btn.titleForState(UIControlState.Normal)
pinView!.rightCalloutAccessoryView = btn as UIView
return pinView
}
#IBAction func dropPin(sender: UILongPressGestureRecognizer) {
if sender.state == UIGestureRecognizerState.Began {
let location = sender.locationInView(self.MapView)
let locCoord = self.MapView.convertPoint(location, toCoordinateFromView: self.MapView)
let annotation = MKPointAnnotation()
annotation.coordinate = locCoord
annotation.title = "City Name"
annotation.subtitle = ""
self.MapView.removeAnnotations(MapView.annotations)
self.MapView.addAnnotation(annotation)
self.MapView.selectAnnotation(annotation, animated: true)
}
func mapView(mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
if control == view.rightCalloutAccessoryView {
self.performSegueWithIdentifier("mapseg", sender: self)
}
}
}
}
I think you need to override prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) method. And check whether if working

how to create a custom callout button from each annotation view?

Im trying to create a button within each annotation view to bring the user to a new page custom to the selected annotation. I've successfully implemented the mapview to show the annotations with titles and subtitles loaded from my parse database but am struggling to find a way of 1) adding a button to the annotation view to bring the user to a new view 2) finding a way of creating custom views for each of the annotations when selected. Any help would be greatly appreciated ?
Code
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet weak var mapView: MKMapView!
var MapViewLocationManager:CLLocationManager! = CLLocationManager()
let btn = UIButton(type: .DetailDisclosure)
override func viewDidLoad() {
super.viewDidLoad()
mapView.showsUserLocation = true
mapView.delegate = self
MapViewLocationManager.delegate = self
MapViewLocationManager.startUpdatingLocation()
mapView.setUserTrackingMode(MKUserTrackingMode.Follow, animated: true)
}
override func viewDidAppear(animated: Bool) {
let annotationQuery = PFQuery(className: "Clubs")
annotationQuery.findObjectsInBackgroundWithBlock {
(clubs, error) -> Void in
if error == nil {
// The find succeeded.
print("Successful query for annotations")
// Do something with the found objects
let myClubs = clubs! as [PFObject]
for club in myClubs {
//data for annotation
let annotation = MKPointAnnotation()
let place = club["location"] as? PFGeoPoint
let clubName = club["clubName"] as? String
let stadiumName = club["stadium"] as? String
annotation.title = clubName
annotation.subtitle = stadiumName
annotation.coordinate = CLLocationCoordinate2DMake(place!.latitude,place!.longitude)
//add annotations
self.mapView.addAnnotation(annotation)
}
} else {
// Log details of the failure
print("Error: \(error)")
}
}
}
For 1st - You can use leftCalloutAccessoryView & rightCalloutAccessoryView on annoationView. You will have to implement MapViewDelegate
mapView(_:viewForAnnotation:).
For 2nd - Similar to calloutAccessoryView, you have access to detailCalloutAccessoryView on annoationView. You can use that to customize your callout.
This is Objective-C solution, hope it may help you.
To set button to annotationView,
UIButton *detailButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
annotationView.rightCalloutAccessoryView = detailButton;
When click performed on this button, following method called like delegate, so override following method too,
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
You can do whatever customisation you want in above method because you know that user tapped on accessory view by now.

How do I make a pin annotation callout in Swift?

I tried to make the callout work but that didn't happen as I did something wrong in my prepare for segue. I want to know how to be able to make a pin annotation callout to another view?
The process of segueing to another scene when the button in the callout is tapped is like so:
Set the delegate of the map view to be the view controller. You can do this either in Interface Builder's "Connections Inspector" or programmatically. You want to specify that the view controller conforms to MKMapViewDelegate, too.
When you create the annotation, make sure to set the title, too:
let annotation = MKPointAnnotation()
annotation.coordinate = coordinate
annotation.title = ...
mapView.addAnnotation(annotation)
Define an annotation view subclass with callout with a button:
class CustomAnnotationView: MKPinAnnotationView { // or nowadays, you might use MKMarkerAnnotationView
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
canShowCallout = true
rightCalloutAccessoryView = UIButton(type: .infoLight)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Instruct your MKMapView to use this annotation view. iOS 11 has simplified that process, but I’ll describe how to do it both ways:
If your minimum iOS version is 11 (or later), you’d just register the custom annotation view in as the default and you’re done. You generally don't implement mapView(_:viewFor:) at all in iOS 11 and later. (The only time you might implement that method is if you needed to register multiple reuse identifiers because you had multiple types of custom annotation types.)
override func viewDidLoad() {
super.viewDidLoad()
mapView.register(CustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
}
If you need to support iOS versions prior to 11, you would make sure to specify your view controller as the delegate for the MKMapView and then would implement mapView(_:viewFor:):
extension ViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation { return nil }
let reuseIdentifier = "..."
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier)
if annotationView == nil {
annotationView = CustomAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier)
} else {
annotationView?.annotation = annotation
}
return annotationView
}
}
For example, that yields a callout something that looks like the following, with the .infoLight button on the right:
Implement calloutAccessoryControlTapped that programmatically performs the segue:
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
performSegue(withIdentifier: "SegueToSecondViewController", sender: view)
}
Obviously, this assumes that you've defined a segue between the two view controllers.
When you segue, pass the necessary information to the destination scene. For example, you might pass a reference to the annotation:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? SecondViewController,
let annotationView = sender as? MKPinAnnotationView {
destination.annotation = annotationView.annotation as? MKPointAnnotation
}
}
For more information, see Creating Callouts in the Location and Maps Programming Guide.
For Swift 2 implementation of the above, see previous revision of this answer.

Add different pin color with MapKit in swift 2.1

I'm new in Swift. I'm trying to have different color pin or custom pin on specific pin. My code works. I've a purple pin, but I want make a difference between them. How can I do it? I think there something to do in MapView delegate method but I didn't find it.
import UIKit
import MapKit
class MapsViewController: UIViewController , MKMapViewDelegate{
var shops: NSArray? {
didSet{
self.loadMaps()
}
}
#IBOutlet weak var map: MKMapView?
override func viewDidLoad() {
super.viewDidLoad()
loadMaps()
self.title = "Carte"
self.map!.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
// simple and inefficient example
let annotationView = MKPinAnnotationView()
annotationView.pinTintColor = UIColor.purpleColor()
return annotationView
}
func loadMaps(){
// navigationController?.navigationBar.topItem!.title = "Carte"
let shopsArray = self.shops! as NSArray
for shop in shopsArray {
let location = CLLocationCoordinate2D(
latitude: shop["lat"] as! Double,
longitude: shop["long"] as! Double
)
let annotation = MKPointAnnotation()
annotation.coordinate = location
annotation.title = shop["name"] as? String
annotation.subtitle = shop["addresse"] as? String
map?.addAnnotation(annotation)
}
// add point
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
A better approach is to use a custom annotation class that implements the MKAnnotation protocol (an easy way to do that is to subclass MKPointAnnotation) and add whatever properties are needed to help implement the custom logic.
In the custom class, add a property, say pinColor, which you can use to customize the color of the annotation.
This example subclasses MKPointAnnotation:
import UIKit
import MapKit
class ColorPointAnnotation: MKPointAnnotation {
var pinColor: UIColor
init(pinColor: UIColor) {
self.pinColor = pinColor
super.init()
}
}
Create annotations of type ColorPointAnnotation and set their pinColor:
let annotation = ColorPointAnnotation(pinColor: UIColor.blueColor())
annotation.coordinate = coordinate
annotation.title = "title"
annotation.subtitle = "subtitle"
self.mapView.addAnnotation(annotation)
In viewForAnnotation, use the pinColor property to set the view's pinTintColor:
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
let reuseId = "pin"
var pinView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId) as? MKPinAnnotationView
if pinView == nil {
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
let colorPointAnnotation = annotation as! ColorPointAnnotation
pinView?.pinTintColor = colorPointAnnotation.pinColor
}
else {
pinView?.annotation = annotation
}
return pinView
}

Resources