Adding accessory button on user location callout - ios

I'm making a clickable placemark. I set the title of the placemark. Title is adress. I want If you click on that adress It will push to another View to Show Adress Number,City,Country etc. I Tried this code. But It haven't UIButton in application in placemark, why?
EDIT: If I put breakpoint to func calloutAccessoryControlTapped it isn't crashing.
func mapView(Mapa: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
if annotation is MKUserLocation {
//return nil so map view draws "blue dot" for standard user location
return nil
}
var pin = "pin"
var view = Mapa.dequeueReusableAnnotationViewWithIdentifier(pin) as? MKPinAnnotationView
if view == nil {
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: pin)
view!.canShowCallout = true
view!.animatesDrop = true
var arrowButton = UIButton()
view!.rightCalloutAccessoryView = UIButton.buttonWithType(.DetailDisclosure) as! UIButton
} else {
view!.annotation = annotation
}
return view
}
func mapView(Mapa: MKMapView!, annotation: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
if control == annotation.rightCalloutAccessoryView {
println("Pressed!")
}
}
Whole code:
import UIKit
import CoreLocation
import MapKit
class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
#IBOutlet weak var Mapa: MKMapView!
let locationManager = CLLocationManager()
var detail: UIButton!
override func viewDidLoad()
{
super.viewDidLoad()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
Mapa.delegate = self
Mapa.mapType = MKMapType.Standard
Mapa.showsUserLocation = true
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
//--- Find Address of Current Location ---//
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!)
{
//--- CLGeocode to get address of current location ---//
CLGeocoder().reverseGeocodeLocation(manager.location, completionHandler: {(placemarks, error)->Void in
let spanX = 0.007
let spanY = 0.007
var newRegion = MKCoordinateRegion(center: self.Mapa.userLocation.coordinate, span: MKCoordinateSpanMake(spanX, spanY))
self.Mapa.setRegion(newRegion, animated: true)
if (error != nil)
{
println("Reverse geocoder failed with error" + error.localizedDescription)
return
}
if placemarks.count > 0
{
let pm = placemarks[0] as! CLPlacemark
self.displayLocationInfo(pm)
}
else
{
println("Problem with the data received from geocoder")
}
})
}
func displayLocationInfo(placemark: CLPlacemark?)
{
if let Placemark = placemark
{
//Stop updating kvôli vydrži baterke
locationManager.stopUpdatingLocation()
let location = self.locationManager.location
var latitude: Double = location.coordinate.latitude
var longitude: Double = location.coordinate.longitude
let adresa = (Placemark.thoroughfare != nil) ? Placemark.thoroughfare : "Ulica: "
let cislo = (Placemark.subThoroughfare != nil) ? Placemark.subThoroughfare : "Číslo ulice:"
let mesto = (Placemark.locality != nil) ? Placemark.locality : "Mesto: "
let stat = (Placemark.country != nil) ? Placemark.country : "Štát: "
var coordinates:CLLocationCoordinate2D = placemark!.location.coordinate
let theLocation: MKUserLocation = Mapa.userLocation
theLocation.title = adresa
println("GPS Súradnice :: \(latitude), \(longitude)")
println(mesto)
println(adresa)
println(cislo)
println(stat)
}
}
func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!)
{
println("Chyba pri aktualizovaní lokácie " + error.localizedDescription)
}
func mapView(Mapa: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
if annotation is MKUserLocation {
//return nil so map view draws "blue dot" for standard user location
return nil
}
var pin = "pin"
var view = Mapa.dequeueReusableAnnotationViewWithIdentifier(pin) as? MKPinAnnotationView
if view == nil {
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: pin)
view!.canShowCallout = true
view!.animatesDrop = true
var arrowButton = UIButton()
view!.rightCalloutAccessoryView = UIButton.buttonWithType(.DetailDisclosure) as! UIButton
} else {
view!.annotation = annotation
}
return view
}
func mapView(Mapa: MKMapView!, annotation: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
if control == annotation.rightCalloutAccessoryView {
println("Pressed!")
}
}
}

You want to put an accessory button on the callout of the blue dot (user location).
In the viewForAnnotation delegate method, you do have code that creates an annotation view and sets the rightCalloutAccessoryView but at the top of the method there is this code:
func mapView(Mapa: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
if annotation is MKUserLocation {
//return nil so map view draws "blue dot" for standard user location
return nil
}
The map's user location annotation is of type MKUserLocation so when the map view calls viewForAnnotation for it, this code returns nil for it which the map view interprets as "display the default view and callout for this annotation". The default callout only shows a title and subtitle.
The code that creates a custom view and sets rightCalloutAccessoryView never executes.
To set a rightCalloutAccessoryView, you need a reference to the actual annotation view object.
You could remove that return nil for the MKUserLocation but then you'll get a standard pin instead of an animated blue dot.
You can't create your own instance of the animated blue dot view because that class is private.
So you can't create or get access to the user location annotation view in the viewForAnnotation delegate method.
A place where you can reliably get access to the actual user location annotation view is in the didAddAnnotationViews delegate method. I suggest this method because it means an annotation view has actually been created and is on the screen.
In this method, call the map view's viewForAnnotation instance method and pass it its userLocation annotation and then set the view's rightCalloutAccessoryView:
func mapView(mapView: MKMapView!, didAddAnnotationViews views: [AnyObject]!) {
if let ulav = mapView.viewForAnnotation(mapView.userLocation) {
ulav.rightCalloutAccessoryView = UIButton.buttonWithType(.DetailDisclosure) as! UIButton
}
}
Unrelated to the callout button not appearing but:
Your calloutAccessoryControlTapped delegate method is named wrong and will not get called when the button is tapped.
Your method is named mapView(annotation:calloutAccessoryControlTapped).
The annotation parameter must be named annotationView and even though naming the map view parameter Mapa will work, I suggest sticking to the signature given in the documentation so the revised method would be:
func mapView(mapView: MKMapView!, annotationView view: MKAnnotationView!, calloutAccessoryControlTapped control: UIControl!) {
if control == annotationView.rightCalloutAccessoryView {
println("Pressed!")
}
}

Related

How to display customer callout screens on a map using Swift and Xcode

I am fairly new to IOS development, and I am working on creating a map to display different venues from a JSON file which works like a charm. Now, what I want to do is, once a user clicks a pin, I want a callout view to be displayed at the bottom of the map that shows the Venue's logo on the left, and the following on the right:
a phone number that's clickable so call them directly, a website url that's clickable to access the website, and finally, a directions button to open the map to show directions to the Venue.
Once a user clicks a different pin, then the information about that new venue is displayed.
Here is the portion of the code that I think needs edits, and below is the full code
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?
{
if let annotation = annotation as? Venue {
let identifier = "pin"
var view: MKPinAnnotationView
if let dequeuedView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKPinAnnotationView {
dequeuedView.annotation = annotation
view = dequeuedView
} else {
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
view.canShowCallout = true
view.calloutOffset = CGPoint(x: -5, y: 5)
view.rightCalloutAccessoryView = UIButton(type: .detailDisclosure) as UIView
}
return view
}
return nil
}
Please help me with step by step instructions. I am having a hard time getting around Swift.
THANKS A BUNCH!
import UIKit
import CoreLocation
import MapKit
//import SwiftyJSON
struct Place: Codable {
let id: Int
let name, address, number, imageURL: String
let lat, long: Double
enum CodingKeys: String, CodingKey {
case id, name, address, number
case imageURL = "imageUrl"
case lat, long
}
}
class YogaViewController: UIViewController {
// MARK: - Properties
var locationManager: CLLocationManager!
var mapView: MKMapView!
let centerMapButton: UIButton = {
let button = UIButton(type: .system)
button.setImage(#imageLiteral(resourceName: "location-arrow-flat").withRenderingMode(.alwaysOriginal), for: .normal)
button.addTarget(self, action: #selector(handleCenterLocation), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
// MARK: - Init
override func viewDidLoad() {
super.viewDidLoad()
configureLocationManager()
configureMapView()
enableLocationServices()
// mapView.addAnnotations(venues)
// //JSON STUFF
let jsonUrlString = "https://www.elev8dfw.com/Gyms.json"
// let jsonUrlString = "https://www.elev8dfw.com/Venues.json"
// let jsonUrlString = "https://api.letsbuildthatapp.com/jsondecodable/course"
guard let url = URL(string: jsonUrlString) else {return}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let places = try JSONDecoder().decode([Place].self, from: data)
for place in places {
// print(place.lat)
// print(place.long)
let sampleStarbucks = Venue(title: place.name, locationName: place.address, coordinate: CLLocationCoordinate2D(latitude: place.lat, longitude: place.long))
self.mapView.addAnnotation(sampleStarbucks)
}
} catch let jsonErr{
print("Error serializing json: ", jsonErr)
}
}.resume()
mapView.delegate = self
}
// MARK: - Selectors
#objc func handleCenterLocation() {
centerMapOnUserLocation()
centerMapButton.alpha = 0
}
// MARK: - Helper Functions
func configureLocationManager() {
locationManager = CLLocationManager()
locationManager.delegate = self
}
func configureMapView() {
mapView = MKMapView()
mapView.showsUserLocation = true
mapView.delegate = self
mapView.userTrackingMode = .follow
view.addSubview(mapView)
mapView.frame = view.frame
view.addSubview(centerMapButton)
centerMapButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -100).isActive = true
centerMapButton.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -20).isActive = true
centerMapButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
centerMapButton.widthAnchor.constraint(equalToConstant: 50).isActive = true
centerMapButton.layer.cornerRadius = 50 / 2
centerMapButton.alpha = 0
}
func centerMapOnUserLocation() {
guard let coordinate = locationManager.location?.coordinate else { return }
let region = MKCoordinateRegion(center: coordinate, latitudinalMeters: 4000, longitudinalMeters: 4000)
mapView.setRegion(region, animated: true)
}
}
// MARK: - MKMapViewDelegate
extension YogaViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
UIView.animate(withDuration: 0.5) {
self.centerMapButton.alpha = 1
}
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?
{
if let annotation = annotation as? Venue {
let identifier = "pin"
var view: MKPinAnnotationView
if let dequeuedView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKPinAnnotationView {
dequeuedView.annotation = annotation
view = dequeuedView
} else {
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
view.canShowCallout = true
view.calloutOffset = CGPoint(x: -5, y: 5)
view.rightCalloutAccessoryView = UIButton(type: .detailDisclosure) as UIView
}
return view
}
return nil
}
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
let location = view.annotation as! Venue
let launchOptions = [MKLaunchOptionsDirectionsModeKey : MKLaunchOptionsDirectionsModeDriving]
location.mapItem().openInMaps(launchOptions: launchOptions)
}
}
// MARK: - CLLocationManagerDelegate
extension YogaViewController: CLLocationManagerDelegate {
func enableLocationServices() {
switch CLLocationManager.authorizationStatus() {
case .notDetermined:
print("Location auth status is NOT DETERMINED")
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
centerMapOnUserLocation()
case .restricted:
print("Location auth status is RESTRICTED")
case .denied:
print("Location auth status is DENIED")
case .authorizedAlways:
print("Location auth status is AUTHORIZED ALWAYS")
case .authorizedWhenInUse:
print("Location auth status is AUTHORIZED WHEN IN USE")
locationManager.startUpdatingLocation()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
centerMapOnUserLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
self.mapView.showsUserLocation = true
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
guard locationManager.location != nil else { return }
centerMapOnUserLocation()
}
}
If you are attempting to open a custom UIView coming up from the bottom upon tapping a annotation you could utilize didSelect and do something like the following:
final func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
let location = view.annotation as! Venue
// Update your new custom view with the Venue here.
// Now lets show the view
self.bottomViewConstraint.constant = 0
UIView.animate(withDuration: 0.2) {
self.view.layoutIfNeeded()
}
}
So in your storyboard. Create a UIView, set its height and width constraints. Set its bottom constraint to be a negative number so it is not showing on the screen. Create an IBOutlet to the bottom constraint (I named it bottomViewConstraint in the example code). This is a very basic example, untested, to get you started.

iOS Swift MapKit Custom Annotation [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 years ago.
Improve this question
I am having some trouble getting a custom annotation to load inside of my map view when I try to place a pin.
import UIKit
import MapKit
import CoreLocation
class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate{
#IBAction func ReportBtn(sender: AnyObject) {
//MARK: Report Date And Time Details
let ReportTime = NSDate()
let TimeStamp = NSDateFormatter()
TimeStamp.timeStyle = NSDateFormatterStyle.ShortStyle
TimeStamp.dateStyle = NSDateFormatterStyle.ShortStyle
TimeStamp.stringFromDate(ReportTime)
//MARK: Default Point Annotation Begins
let ReportAnnotation = MKPointAnnotation()
ReportAnnotation.title = "Annotation Created"
ReportAnnotation.subtitle = ReportTime.description
ReportAnnotation.coordinate = locationManager.location!.coordinate
mapView(MainMap, viewForAnnotation: ReportAnnotation)
MainMap.addAnnotation(ReportAnnotation)
}
#IBOutlet weak var MainMap: MKMapView!
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.startUpdatingLocation()
self.MainMap.showsUserLocation = true
}
//MARK: - Location Delegate Methods
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations.last
let center = CLLocationCoordinate2D(latitude: location!.coordinate.latitude, longitude: location!.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02 ))
self.MainMap.setRegion(region, animated: true)
//self.locationManager.stopUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError){
print(error.localizedDescription)
}
//MARK:Custom Annotation Begins Here
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
guard !annotation.isKindOfClass(MKUserLocation) else {
return nil
}
/*if annotation.isKindOfClass(MKUserLocation){
//emty return, guard wasn't cooperating
}else{
return nil
}*/
let annotationIdentifier = "AnnotationIdentifier"
var annotationView: MKAnnotationView?
if let dequeuedAnnotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(annotationIdentifier){
annotationView = dequeuedAnnotationView
annotationView?.annotation = annotation
}
else{
let av = MKAnnotationView(annotation: annotation, reuseIdentifier: annotationIdentifier)
av.rightCalloutAccessoryView = UIButton(type: .DetailDisclosure)
annotationView = av
}
if let annotationView = annotationView {
annotationView.canShowCallout = true
annotationView.image = UIImage(named: "image.png")
}
return annotationView
}
}
Added Information
I am positive that the button functionality works perfect. With the current code, dumped above, the default red pin annotation appears right where it should. When I tap on the pin, the description I specified also appears without an issue. The only problem I am having with this code is that I cannot get my image to take the place of the boring, default red pin
I recommend subclassing `MKPointAnnotation.
Pokémon Pin
I have included only the necessary code to display a custom map pin. Think of it as a template.
Outline
We will create a point annotation object and assigning a custom image name with the CustomPointAnnotation class.
We will subclass the MKPointAnnotation to set image and assign it on the delegate protocol method viewForAnnotation.
We will add an annotation view to the map after setting the coordinate of the point annotation with a title and a subtitle.
We will implement the viewForAnnotation method which is an MKMapViewDelegate protocol method which gets called for pins to display on the map. viewForAnnotation protocol method is the best place to customise the pin view and assign a custom image to it.
We will dequeue and return a reusable annotation for the given identifier and cast the annotation to our custom CustomPointAnnotation class in order to access the image name of the pin.
We will create a new image set in Assets.xcassets and place image#3x.png and image#2x.png accordingly.
Don't forget plist.
NSLocationAlwaysUsageDescription and NSLocationWhenInUseUsageDescription
As always test on a real device.
The swizzle 🌀
//1
CustomPointAnnotation.swift
import UIKit
import MapKit
class CustomPointAnnotation: MKPointAnnotation {
var pinCustomImageName:String!
}
//2
ViewController.swift
import UIKit
import MapKit
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet weak var pokemonMap: MKMapView!
let locationManager = CLLocationManager()
var pointAnnotation:CustomPointAnnotation!
var pinAnnotationView:MKPinAnnotationView!
override func viewDidLoad() {
super.viewDidLoad()
//Mark: - Authorization
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
pokemonMap.delegate = self
pokemonMap.mapType = MKMapType.Standard
pokemonMap.showsUserLocation = true
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = CLLocationCoordinate2D(latitude: 35.689949, longitude: 139.697576)
let center = location
let region = MKCoordinateRegionMake(center, MKCoordinateSpan(latitudeDelta: 0.025, longitudeDelta: 0.025))
pokemonMap.setRegion(region, animated: true)
pointAnnotation = CustomPointAnnotation()
pointAnnotation.pinCustomImageName = "Pokemon Pin"
pointAnnotation.coordinate = location
pointAnnotation.title = "POKéSTOP"
pointAnnotation.subtitle = "Pick up some Poké Balls"
pinAnnotationView = MKPinAnnotationView(annotation: pointAnnotation, reuseIdentifier: "pin")
pokemonMap.addAnnotation(pinAnnotationView.annotation!)
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print(error.localizedDescription)
}
//MARK: - Custom Annotation
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
let reuseIdentifier = "pin"
var annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseIdentifier)
if annotationView == nil {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier)
annotationView?.canShowCallout = true
} else {
annotationView?.annotation = annotation
}
let customPointAnnotation = annotation as! CustomPointAnnotation
annotationView?.image = UIImage(named: customPointAnnotation.pinCustomImageName)
return annotationView
}
}
There are a few issues you need to deal with.
MKMapView and annotations
Firstly, it is necessary to understand how MKMapView displays an annotation view from an annotation. There are
MKMapView - displays the map and manages annotations.
MKMapViewDelegate - you return data from specific functions to MKMapView.
MKAnnotation - contains data about a location on the map.
MKAnnotationView - displays an annotation.
An MKAnnotation holds the data for a location on the map. You create this data and hand it to MKMapView. At some point in the future, when the map view is ready to display the annotation it will call back to the delegate and ask it to create an MKAnnotationView for an MKAnnotation. The delegate creates and returns the view and the map view displays it. You specify the delegate in the storyboard, or in code e.g. mapView.delegate = self.
Location
Tracking the users location is complicated by:
Permission is needed from the user before location tracking is enabled.
There is a delay after the user allows tracking, until the user's location is available.
Location services might not even be enabled.
Your code needs to deal with authorisation by checking CLLocationManager.authorizationStatus, and implementing CLLocationManagerDelegate methods.
Note that to use requestWhenInUseAuthorization requires entry for NSLocationWhenInUseUsageDescription in Info.plist
Example
Example project on GitHub.
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
// Instance of location manager.
// This is is created in viewDidLoad() if location services are available.
var locationManager: CLLocationManager?
// Last location made available CoreLocation.
var currentLocation: MKUserLocation? {
didSet {
// Hide the button if no location is available.
button.hidden = (currentLocation == nil)
}
}
// Date formatter for formatting dates in annotations.
// We use a lazy instance so that it is only created when needed.
lazy var formatter: NSDateFormatter = {
let formatter = NSDateFormatter()
formatter.timeStyle = NSDateFormatterStyle.ShortStyle
formatter.dateStyle = NSDateFormatterStyle.ShortStyle
return formatter
}()
#IBOutlet var button: UIButton!
#IBOutlet var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
// Track the user's location if location services are enabled.
if CLLocationManager.locationServicesEnabled() {
locationManager = CLLocationManager()
locationManager?.delegate = self
locationManager?.desiredAccuracy = kCLLocationAccuracyBest
switch CLLocationManager.authorizationStatus() {
case .AuthorizedAlways, .AuthorizedWhenInUse:
// Location services authorised.
// Start tracking the user.
locationManager?.startUpdatingLocation()
mapView.showsUserLocation = true
default:
// Request access for location services.
// This will call didChangeAuthorizationStatus on completion.
locationManager?.requestWhenInUseAuthorization()
}
}
}
//
// CLLocationManagerDelegate method
// Called by CLLocationManager when access to authorisation changes.
//
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .AuthorizedAlways, .AuthorizedWhenInUse:
// Location services are authorised, track the user.
locationManager?.startUpdatingLocation()
mapView.showsUserLocation = true
case .Denied, .Restricted:
// Location services not authorised, stop tracking the user.
locationManager?.stopUpdatingLocation()
mapView.showsUserLocation = false
currentLocation = nil
default:
// Location services pending authorisation.
// Alert requesting access is visible at this point.
currentLocation = nil
}
}
//
// MKMapViewDelegate method
// Called when MKMapView updates the user's location.
//
func mapView(mapView: MKMapView, didUpdateUserLocation userLocation: MKUserLocation) {
currentLocation = userLocation
}
#IBAction func addButtonTapped(sender: AnyObject) {
guard let coordinate = currentLocation?.coordinate else {
return
}
let reportTime = NSDate()
let formattedTime = formatter.stringFromDate(reportTime)
let annotation = MKPointAnnotation()
annotation.title = "Annotation Created"
annotation.subtitle = formattedTime
annotation.coordinate = coordinate
mapView.addAnnotation(annotation)
}
//
// From Bhoomi's answer.
//
// MKMapViewDelegate method
// Called when the map view needs to display the annotation.
// E.g. If you drag the map so that the annotation goes offscreen, the annotation view will be recycled. When you drag the annotation back on screen this method will be called again to recreate the view for the annotation.
//
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
guard !annotation.isKindOfClass(MKUserLocation) else {
return nil
}
let annotationIdentifier = "AnnotationIdentifier"
var annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(annotationIdentifier)
if annotationView == nil {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: annotationIdentifier)
annotationView!.rightCalloutAccessoryView = UIButton(type: .DetailDisclosure)
annotationView!.canShowCallout = true
}
else {
annotationView!.annotation = annotation
}
annotationView!.image = UIImage(named: "smile")
return annotationView
}
}
check your image.png in your project bundle or Assets.xcassets
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard !annotation.isKind(of: MKUserLocation.self) else {
return nil
}
let annotationIdentifier = "AnnotationIdentifier"
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: annotationIdentifier)
if annotationView == nil {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: annotationIdentifier)
annotationView?.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
annotationView!.canShowCallout = true
}
else {
annotationView!.annotation = annotation
}
annotationView!.image = UIImage(named: "image.png")
return annotationView
}
Do as follow may be work for you.
1) Create custom class for the Annotation Pin.
class CustomPointAnnotation: MKPointAnnotation {
var imageName: UIImage!
}
2)Define variable as below.
var locationManager = CLLocationManager()
3) Call below method in viewDidLoad()
func checkLocationAuthorizationStatus() {
if CLLocationManager.authorizationStatus() == .AuthorizedAlways {
map.showsUserLocation = false
} else {
locationManager.requestWhenInUseAuthorization()
}
}
4) Put below code in viewWillAppear()
self.map.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.delegate = self
dispatch_async(dispatch_get_main_queue(),{
self.locationManager.startUpdatingLocation()
})
5) Most important implement below method.
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if !(annotation is CustomPointAnnotation) {
return nil
}
let reuseId = "Location"
var anView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId)
if anView == nil {
anView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
anView!.canShowCallout = true
}
else {
anView!.annotation = annotation
}
let cpa = annotation as! CustomPointAnnotation
anView!.image = cpa.imageName
return anView
}
6) Execute below code where you you have received custom pin image
let strLat = "YOUR LATITUDE"
let strLon = "YOUR LONGITUDE"
let info = CustomPointAnnotation()
info.coordinate = CLLocationCoordinate2DMake(strLat.toDouble()!,strLon.toDouble()!)
info.imageName = resizedImage
info.title = dict!["locationName"]! as? String
self.map.addAnnotation(info)
From the code and according to the MapKit guide, your code look correct. I am thinking that it could be this line annotationView.image = UIImage(named: "image.png")
Is there a chance that image.png could be the wrong image name or not added in to the project when compile? Also just fyi, if you are using .xcassets, you does not have to add a .png.
As annotationView.image is a optional, when the image UIImage(named: "image.png") is nil, it will not crash but just render the default pin image.
If this is not the issue, please provide more info on the debugging steps that you have taken so the rest of us can understand better and help you. Cheers =)

How to make annotation point draggable in MapKit using swift 2 and xcode 7?

It is very frustrating to find the solution for this problem. I posted the whole code to understand the problem. Please help
import UIKit
import MapKit
import CoreLocation
class LocationViewController: UIViewController,MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet weak var mapView: MKMapView!
let locationManager = CLLocationManager()
var annotationPoint: MKPointAnnotation!
var getMovedMapCenter: CLLocation!
var myPinView:MKPinAnnotationView!
override func viewDidLoad() {
super.viewDidLoad()
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
if #available(iOS 8.0, *) {
self.locationManager.requestWhenInUseAuthorization()
} else {
// Fallback on earlier versions
}
self.locationManager.startUpdatingLocation()
self.mapView.showsUserLocation = true
// Do any additional setup after loading the view.
}
// MARK: - Location Delegate Methods
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
let location = locations.last
let center = CLLocationCoordinate2D(latitude: location!.coordinate.latitude, longitude: location!.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1))
self.mapView.setRegion(region, animated: true)
if(self.annotationPoint == nil)
{
self.annotationPoint = MKPointAnnotation()
getMovedMapCenter = CLLocation(latitude: location!.coordinate.latitude, longitude: location!.coordinate.longitude)
self.annotationPoint.coordinate = getMovedMapCenter.coordinate
// self.annotationPoint.title = location_value
//self.annotationPoint.subtitle = ""
// println_debug(self.annotationPoint)
self.mapView.addAnnotation(self.annotationPoint)
}
self.locationManager.stopUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError)
{
print("Errors: " + error.localizedDescription)
}
//MARK:- View for Annotation
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView?
{
if annotation is MKPointAnnotation {
let pinAnnotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "myPin")
pinAnnotationView.pinColor = .Purple
pinAnnotationView.draggable = true
pinAnnotationView.canShowCallout = true
pinAnnotationView.animatesDrop = true
return pinAnnotationView
}
return nil
}
//MARK:- Annotation Changed State
func mapView(mapView: MKMapView, annotationView view: MKAnnotationView, didChangeDragState newState: MKAnnotationViewDragState, fromOldState oldState: MKAnnotationViewDragState) {
print("hiii")
// println_debug("End State coordinates: \(self.annotationPoint.coordinate.latitude),\(self.annotationPoint.coordinate.longitude)")
if(newState == .Ending)
{
let center = CLLocationCoordinate2D(latitude: self.annotationPoint.coordinate.latitude, longitude: self.annotationPoint.coordinate.longitude)
// self.currentLocationNameA(center)
// dispatch_async(GlobalBackgroundQueue)
// {
//
//
// }
}
}
//MARK:- update Not Get
func mapView(mapView: MKMapView,
didFailToLocateUserWithError error: NSError)
{
// AppHelper.showALertWithTag(121, title: APP_NAME, message: "Failed to get location. Please check Location setting", delegate: nil, cancelButtonTitle: "Ok", otherButtonTitle: nil)
}
}
I got stuck in this code. Becasuse didChangeDragState Delegate never called. Please help me to find a better solution for this. I am new to swift; So its hard to convert the objective c code in swift
The drag state typically changes in response to user interactions with
the annotation view. However, the annotation view itself is
responsible for changing that state as well.
replace this method and try once. may be working fine
func mapView(mapView: MKMapView!, annotationView view: MKAnnotationView!, didChangeDragState newState: MKAnnotationViewDragState, fromOldState oldState: MKAnnotationViewDragState) {
switch (newState) {
case .Starting:
view.dragState = .Dragging
case .Ending, .Canceling:
view.dragState = .None
default: break
}
}
Replace you point annotation method with this one.
let pa = MKPointAnnotation()
pa.coordinate = placemark.location.coordinate
pa.title = placemark.name //or whatever title you like
self.mapView.addAnnotation(pa)
for more , you can see Code and Another Sample Code
pinAnnotationView.canShowCallout = false
This line solves my problem. Popup of showing "current location" was the main cause of this problem.
and these changes in code make it smooth dragging. Mehul and Vijay Answer help me in finding this solution.
func mapView(mapView: MKMapView, annotationView view: MKAnnotationView, didChangeDragState newState: MKAnnotationViewDragState, fromOldState oldState: MKAnnotationViewDragState) {
switch (newState) {
case .Ending, .Canceling:
view.dragState = .None
default: break
}
}
if (annotation is MKUserLocation) {
return nil
}
let reuseId12 = "pin12"
var pinView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId)
if pinView == nil {
pinView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId12)
pinView.image = UIImage(named:"pin-1250.png")
pinView.canShowCallout = false
pinView.draggable = true
}
else {
pinView.annotation = annotation
}
return pinView
Try this code.

Showing a new viewController when tapping on a mark on a mapView?

I have a mapView with pins showing users positions, tapping on them I show some info about users and places. I'd like to implement a feature in wich by tapping on those info little panel, I can navigate to another viewController from which
send a personal message via Parse. Even a button could be fine.
this is my mapViwController code
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!)
{
let point = PFGeoPoint(latitude:manager.location.coordinate.latitude, longitude:manager.location.coordinate.longitude)
let location = locations.last as! CLLocation
let center = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1))
self.mapView.setRegion(region, animated: true)
CLGeocoder().reverseGeocodeLocation(manager.location, completionHandler: {(placemarks, error)->Void in
if (error != nil)
{
println("Error: " + error.localizedDescription)
return
}
if placemarks.count > 0
{
let pm = placemarks[0] as! CLPlacemark
self.displayLocationInfo(pm, point: point)
}
else
{
println("Error with the data.")
}
})
}
func displayLocationInfo(placemark: CLPlacemark, point: PFGeoPoint)
{
self.locationManager.stopUpdatingLocation()
self.createAnnotations(point, address: "\(placemark.locality) \(placemark.administrativeArea) \(placemark.postalCode) \(placemark.country)")
}
func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!)
{
println("Error: " + error.localizedDescription)
}
// MARK: - Create Annotation
func createAnnotations(point: PFGeoPoint, address: String)
{
var query = PFUser.query()
var withinKms = NSUserDefaults.standardUserDefaults().objectForKey("withinKms") as! Double
// query?.whereKey("location", nearGeoPoint: point, withinKms: withinKms) // also withinKilometers if wanted
query?.whereKey("location", nearGeoPoint: point, withinKilometers: withinKms)
query?.limit = 10
query?.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
if error == nil
{
for(var i = 0; i < objects!.count; i++)
{
let user = objects![i] as! PFUser
var myHomePin = MKPointAnnotation()
let userPoint = user["location"] as! PFGeoPoint
myHomePin.coordinate = CLLocationCoordinate2DMake(userPoint.latitude, userPoint.longitude)
myHomePin.title = user.username
myHomePin.subtitle = address
self.mapView.addAnnotation(myHomePin)
}
}
else
{
println("Error: " + error!.localizedDescription)
}
})
}
Anbu's Answer
#IBOutlet weak var mapView: MKMapView!
//************************************************
func mapView(mapView: mapView!, annotationView view: MKAnnotationView!, calloutAccessoryControlTapped control: UIControl!) {
println("disclosure pressed: \(view.annotation.title)")
// do your stuff here
// #IBAction func startChat(sender: UIButton)
// add your segue
if control == annotationView.rightCalloutAccessoryView {
self.performSegueWithIdentifier("nextViewController", sender: self)
}
}
//************************************************
receive mapView delegate callback method
//if you pressed the annotation view the following method is called
func mapView(mapView: MKMapView!, didSelectAnnotationView view: MKAnnotationView!)
// if you press the calloutButton the following method is called
func mapView(mapView: MKMapView!, annotationView view: MKAnnotationView!, calloutAccessoryControlTapped control: UIControl!)
for example
func mapView(mapView: MKMapView!, annotationView view: MKAnnotationView!, calloutAccessoryControlTapped control: UIControl!) {
println("disclosure pressed: \(view.annotation.title)")
// do your stuff here
// add your segue
if control == annotationView.rightCalloutAccessoryView {
self.performSegueWithIdentifier("nextViewController", sender: self)
}
}
Please try with
annotationView.canShowCallout = YES;
and implement the method in below delegate:
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
NSLog(#"calloutAccessoryControlTapped: annotation = %#", view.annotation);
MyDetailViewController *detailView=[[MyDetailViewController alloc] initWithNibName:#"MyDetailViewController" bundle:nil];
//here, can set annotation info in some property of detailView
[[self navigationController] pushViewController:detailView animated:YES];
[detailView release];
}

Cannot change pins for images in a map

I have a map where I mark points based on Parse data. This works correctly. When I try to change the red pins of the map with a custom image, it seems that everything is OK but when I run the app in the simulator, the red pins are there and my image doesn't appear. I have tried some similar versions of the code I have with the same result. I tried to print the annotationView in the console and seems that have the image saved the line before the return. Where is the error? What I have to do to show the custom image correctly? Thanks.
import UIKit
import MapKit
import CoreLocation
import Foundation
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet weak var mapView: MKMapView!
let locationManager = CLLocationManager()
var MapViewLocationManager:CLLocationManager! = CLLocationManager()
var currentLoc: PFGeoPoint! = PFGeoPoint()
override func viewDidLoad() {
super.viewDidLoad()
self.mapView.delegate = self
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
self.mapView.showsUserLocation = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]){
let location = locations.last
let center = CLLocationCoordinate2D(latitude: location!.coordinate.latitude, longitude: location!.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))
self.mapView.setRegion(region, animated: true)
self.locationManager.stopUpdatingLocation()
}
override func viewDidAppear(animated: Bool) {
let annotationQuery = PFQuery(className: "_User")
currentLoc = PFGeoPoint(location: MapViewLocationManager.location)
annotationQuery.whereKey("restaurant", equalTo: true)
annotationQuery.whereKey("restaurantPosition", nearGeoPoint: currentLoc, withinMiles: 6000)
annotationQuery.findObjectsInBackgroundWithBlock {
(posts, error) -> Void in
if error == nil {
// The find succeeded.
print("Successful query for annotations")
let myPosts = posts as [PFObject]!
for post in myPosts {
let point = post["restaurantPosition"] as! PFGeoPoint
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2DMake(point.latitude, point.longitude)
self.mapViewN(self.mapView, viewForAnnotation: annotation).annotation = annotation
self.mapView.addAnnotation(annotation)
}
} else {
// Log details of the failure
print("Error: \(error)")
}
}
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print("Error: " + error.localizedDescription)
}
func mapViewN(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView {
let identifier = "pin"
// Reuse the annotation if possible
var annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(identifier)
if annotationView == nil {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView!.canShowCallout = true
annotationView!.image = UIImage(named: "map_marker_2")
}
else {
annotationView!.annotation = annotation
}
print(annotationView?.image)
return annotationView!
}
}
Your method map view:viewForAnnotation: is misnamed. You have
func mapViewN(mapView: MKMapView,
viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView
That N in the name does not belong there, so your delegate method will never be called.
(The method name for a delegate method must be exactly correct or the method doesn't get called. For a required delegate method you may even crash.)
It should be:
func mapView(mapView: MKMapView,
viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView
mapViewN:viewForAnnotation delegate is called?
Maybe you should rename mapViewN to mapView.
func mapViewN(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView {
->
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {

Resources