I have built an app that has a split view: a table and a map. I want to change the marker image according to the type of animal. I add the code below but the marker hasn't changed.
LocationListController.swift
override func viewDidLoad() {
super.viewDidLoad()
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Animal")
do{
animalList = try managedObjectContext.fetch(fetchRequest) as! [Animal]
}
catch {
fatalError("Failded to fetch teams: \(error)")
}
let location: FencedAnnotation = FencedAnnotation (newTitle: "*****", newSubtitle: "*****", lat: -33, long: 145.045374, newType: "Start", newPhoto: "none")
filteredList = animalList
for animal in filteredList{
let annotation = FencedAnnotation(newTitle: animal.name!, newSubtitle: animal.animaldescription!, lat: animal.latitude, long: animal.longtitude, newType: animal.type!, newPhoto: animal.photo!)
self.locationList.add(annotation)
self.mapViewController?.addAnnotation(annotation: annotation)
}
geoLocation = CLCircularRegion(center: location.coordinate, radius: 500, identifier: location.title!)
geoLocation!.notifyOnExit = true
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
locationManager.stopMonitoring(for: geoLocation!)
let searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search Animal"
navigationItem.searchController = searchController
tableView.reloadData()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem
}
mapView.swift
func addAnnotation(annotation: FencedAnnotation) {
self.mapView.addAnnotation(annotation)
}
func removeAnnotation(annotation: FencedAnnotation) {
self.mapView.removeAnnotation(annotation)
}
func focusOn(annotation: MKAnnotation) {
self.mapView.centerCoordinate = annotation.coordinate
self.mapView.selectAnnotation(annotation, animated:true)
}
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
let reuseIdentifier = "pin"
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier)
if annotationView == nil {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier)
annotationView?.canShowCallout = true
} else {
annotationView?.annotation = annotation
}
let customPointAnnotation = annotation as! FencedAnnotation
if customPointAnnotation.type == "cat"{
annotationView?.image = #imageLiteral(resourceName: "cat")
}
if customPointAnnotation.type == "dog"{
annotationView?.image = #imageLiteral(resourceName: "dog")
}
if customPointAnnotation.type == "fish"{
annotationView?.image = #imageLiteral(resourceName: "fish")
}
if customPointAnnotation.type == "monkey"{
annotationView?.image = #imageLiteral(resourceName: "monkey")
}
if customPointAnnotation.type == "fish"{
annotationView?.image = #imageLiteral(resourceName: "bird")
}
if customPointAnnotation.type == "fish"{
annotationView?.image = #imageLiteral(resourceName: "mouse")
}
else{
annotationView?.image = #imageLiteral(resourceName: "placeholder")
}
annotationView?.canShowCallout = true
return annotationView
}
I fetch the list from core data and use a customer MKAnnotation to add the annotation.
Related
In the app that I'm building I was able to implement succesfully a map.
The problem is that when I try to implement a bar with the SearchButton the app crashes and the output says: "Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key searchButton.'"
Here is the MapViewController file.
Thanks for the help...
import UIKit
import MapKit
class MapViewController: UIViewController, UISearchBarDelegate {
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
mapView.userTrackingMode = .follow
let annotations = LocationsStorage.shared.locations.map { annotationForLocation($0) }
mapView.addAnnotations(annotations)
NotificationCenter.default.addObserver(self, selector: #selector(newLocationAdded(_:)), name: .newLocationSaved, object: nil)
}
#IBAction func addItemPressed(_ sender: Any) {
guard let currentLocation = mapView.userLocation.location else {
return
}
LocationsStorage.shared.saveCLLocationToDisk(currentLocation)
}
func annotationForLocation(_ location: Location) -> MKAnnotation {
let annotation = MKPointAnnotation()
annotation.title = location.dateString
annotation.coordinate = location.coordinates
return annotation
}
#IBAction func searchButton(_ sender: Any)
{
let searchController = UISearchController(searchResultsController: nil)
searchController.searchBar.delegate = self
present(searchController, animated: true, completion: nil)
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar)
{
//Ignoring user
UIApplication.shared.beginIgnoringInteractionEvents()
//Activity Indicator
let activityIndicator = UIActivityIndicatorView()
activityIndicator.style = UIActivityIndicatorView.Style.gray
activityIndicator.center = self.view.center
activityIndicator.hidesWhenStopped = true
activityIndicator.startAnimating()
self.view.addSubview(activityIndicator)
//Hide search bar
searchBar.resignFirstResponder()
dismiss(animated: true, completion: nil)
//Create the search request
let searchRequest = MKLocalSearch.Request()
searchRequest.naturalLanguageQuery = searchBar.text
let activeSearch = MKLocalSearch(request: searchRequest)
activeSearch.start { (response, error) in
activityIndicator.stopAnimating()
UIApplication.shared.endIgnoringInteractionEvents()
if response == nil
{
print("ERROR")
}
else
{
//Remove annotations
let annotations = self.mapView.annotations
self.mapView.removeAnnotations(annotations)
//Getting data
let latitude = response?.boundingRegion.center.latitude
let longitude = response?.boundingRegion.center.longitude
//Create annotation
let annotation = MKPointAnnotation()
annotation.title = searchBar.text
annotation.coordinate = CLLocationCoordinate2DMake(latitude!, longitude!)
self.mapView.addAnnotation(annotation)
//Zooming in on annotation
let coordinate:CLLocationCoordinate2D = CLLocationCoordinate2DMake(latitude!, longitude!)
let span = MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)
let region = MKCoordinateRegion(center: coordinate, span: span)
self.mapView.setRegion(region, animated: true)
}
}
}
#objc func newLocationAdded(_ notification: Notification) {
guard let location = notification.userInfo?["location"] as? Location else {
return
}
let annotation = annotationForLocation(location)
mapView.addAnnotation(annotation)
}
}
you have your output disconnected, check out your storyboard and check that your button it's linked to its corresponding outlets and events.
In the connections inspector you should be able to see the outlets of the button if it's a bar button item.
So I have multiple locations stored in my database, using Geofire. I want to centre the map on a specific one.
I want to know if it is possible to access the firebase database to only retrieve the coordinates saved by GeoFire as a variable so that I can create a CLLocation and centre the map on this one location. Here is my code.
I call getJobLocation in my viewDidLoad. This is where I want to grab the job coordinates from Firebase and then call the other methods i.e. showJobsOnMap using these coordinates
func getJobLocation(jobID: String) {
...
}
func centreMapOnLocation(location: CLLocation) {
let coordinateRegion = MKCoordinateRegionMakeWithDistance(location.coordinate, 2000, 2000)
mapView.setRegion(coordinateRegion, animated: true)
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let annoIdentifier = "job"
var annotationView: MKAnnotationView?
if annotation.isKind(of: MKUserLocation.self) {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "User")
annotationView?.image = UIImage(named:"currentLocationPin")
} else if let deqAnno = mapView.dequeueReusableAnnotationView(withIdentifier: "job") {
annotationView = deqAnno
annotationView?.annotation = annotation
} else {
let MKAV = MKAnnotationView(annotation: annotation, reuseIdentifier: annoIdentifier)
MKAV.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
annotationView = MKAV
}
if let annotationView = annotationView, let _ = annotation as? JobAnnotation {
let postsRef = self.dataBaseRef.child("jobs")
postsRef.observe(.value, with: { (posts) in
for _ in posts.children {
annotationView.canShowCallout = true
annotationView.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
annotationView.image = UIImage(named: "jobPin")
}
})
}
return annotationView
}
func showJobsOnMap(location: CLLocation) {
let circleQuery = geoFire!.query(at: location, withRadius: 2.5)
_ = circleQuery?.observe(GFEventType.keyEntered, with: { (key, location) in
if let key = key, let location = location {
if key == self.job.postID {
let postsRef = self.dataBaseRef.child("jobs").child(key)
postsRef.observe(.value, with: { (posts) in
for _ in posts.children {
let post = Post(snapshot: posts )
let jobDate = post.postDateTime
let price = post.price
let anno = JobAnnotation(coordinate: location.coordinate, jobID: key, jobDate: jobDate, title: "£\(price) - \(jobDate)")
self.mapView.addAnnotation(anno)
}
})
}
}
})
}
There is no error but I can not get the location from my current location to what I search. Can someone please help. It says this with I click on the Car logo to get directions: MKDirectionsErrorCode=7, NSLocalizedDescription=Directions Not Available}
I am only missing the line to get to the direction. When I click on the car logo, it just does not do anything but zoom out to my current location. I am missing anything or did something wrong?
Here is my code:
protocol HandleMapSearch {
func dropPinZoomIn(placemark:MKPlacemark)
}
class ViewController: UIViewController {
#IBOutlet weak var mapView: MKMapView! // Handle the Map Kit View
var selectedPin:MKPlacemark? = nil //Any incoming placemarks
// Destination for directions*********
var destination:MKMapItem = MKMapItem()
var MyPosition = CLLocationCoordinate2D()
var resultSearchController:UISearchController? = nil // Keep the UISearchController in memory after it's created
var locationManager = CLLocationManager() // Core Location
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest // Accuracy level
locationManager.requestWhenInUseAuthorization() // Triggers the location permission dialog
locationManager.startUpdatingLocation()
//locationManager.requestLocation() // Trigers a one-time location request
// Set up the search results table
let locationSearchTable = storyboard!.instantiateViewController(withIdentifier: "LocationSearchTable") as! LocationSearchTable
resultSearchController = UISearchController(searchResultsController: locationSearchTable)
resultSearchController?.searchResultsUpdater = locationSearchTable
// Set up the search bar
let searchBar = resultSearchController!.searchBar
searchBar.sizeToFit()
searchBar.placeholder = "Search for places"
navigationItem.titleView = resultSearchController?.searchBar
// Configure the UISearchController appearance
resultSearchController?.hidesNavigationBarDuringPresentation = false
resultSearchController?.dimsBackgroundDuringPresentation = true
definesPresentationContext = true
locationSearchTable.mapView = mapView // Passes along a handle of the mapView
locationSearchTable.handleMapSearchDelegate = self // Handles the map search
}
//Getting direction of location
#objc func getDirections(sender: AnyObject){
if let selectedPin = selectedPin {
let mapItem = MKMapItem(placemark: selectedPin)
//let launchOptions = [MKLaunchOptionsDirectionsModeKey : MKLaunchOptionsDirectionsModeDriving]
//mapItem.openInMaps(launchOptions: launchOptions)
let request = MKDirectionsRequest()
request.source = MKMapItem.forCurrentLocation()
request.destination = destination
request.requestsAlternateRoutes = false
let directions = MKDirections(request: request)
// 8.
directions.calculate {
(response, error) -> Void in
guard let response = response else {
if let error = error {
print("Error: \(error)")
}
return
}
let route = response.routes[0]
self.mapView.add((route.polyline), level: MKOverlayLevel.aboveRoads)
let rect = route.polyline.boundingMapRect
self.mapView.setRegion(MKCoordinateRegionForMapRect(rect), animated: true)
}
}
/*
directions.calculate(completionHandler: {(response: MKDirectionsResponse!, error: Error!) in
if error != nil {
print("Error \(error)")
} else {
self.displayRout(response)
var overlays = self.mapView.overlays
for route in response.routes {
self.mapView.add(route.polyline, level: MKOverlayLevel.aboveRoads)
for next in route.steps {
print(next.instructions)
}
}
}
})
}
*/
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.strokeColor = UIColor.blue
renderer.lineWidth = 4.0
return renderer
}
}
//destination = MKMapItem(placemark: selectedPin!)
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension ViewController : CLLocationManagerDelegate {
// When user responds to the permission dialog
private func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
locationManager.requestLocation()
}
}
// When location information comes back (Zoom to the user's current location)
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.first {
//****
MyPosition = location.coordinate
locationManager.stopUpdatingLocation()
let span = MKCoordinateSpanMake(0.01, 0.01)
let region = MKCoordinateRegion(center: location.coordinate, span: span)
mapView.setRegion(region, animated: true)
}
}
// Print out the error
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Error finding location: \(error.localizedDescription)")
}
}
// Searches the location and city/state
extension ViewController: HandleMapSearch {
func dropPinZoomIn(placemark:MKPlacemark){
// cache the pin
selectedPin = placemark
// clear existing pins
mapView.removeAnnotations(mapView.annotations)
let annotation = MKPointAnnotation()
annotation.coordinate = placemark.coordinate
annotation.title = placemark.name
if let city = placemark.locality,
let state = placemark.administrativeArea {
annotation.subtitle = "\(city) \(state)"
}
mapView.addAnnotation(annotation)
let span = MKCoordinateSpanMake(0.01, 0.01)
let region = MKCoordinateRegionMake(placemark.coordinate, span)
mapView.setRegion(region, animated: true)
}
}
// Shows Pins and Car Logo
extension ViewController : MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?{
if annotation is MKUserLocation {
//return nil so map view draws "blue dot" for standard user location
return nil
}
let reuseId = "pin"
var pinView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId) as? MKPinAnnotationView
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
pinView?.pinTintColor = UIColor.red
pinView?.canShowCallout = true
let smallSquare = CGSize(width: 30, height: 30)
let button = UIButton(frame: CGRect(origin: CGPoint.zero, size: smallSquare))
button.setBackgroundImage(UIImage(named: "Car"), for: .normal)
button.addTarget(self, action: #selector(getDirections), for: .touchUpInside)
pinView?.leftCalloutAccessoryView = button
return pinView
}
}
Have a look at below code for reference hope it helps :
1 using two locations here as source and as destination and drawing a path according to locations
import UIKit
import CoreLocation
import MapKit
class ViewController: UIViewController, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
// Do any additional setup after loading the view, typically from a nib.
let sourceLocation = CLLocationCoordinate2D(latitude: 40.759011, longitude: -73.984472)
let destinationLocation = CLLocationCoordinate2D(latitude: 40.748441, longitude: -73.985564)
// 3.
let sourcePlacemark = MKPlacemark(coordinate: sourceLocation, addressDictionary: nil)
let destinationPlacemark = MKPlacemark(coordinate: destinationLocation, addressDictionary: nil)
// 4.
let sourceMapItem = MKMapItem(placemark: sourcePlacemark)
let destinationMapItem = MKMapItem(placemark: destinationPlacemark)
// 5.
let sourceAnnotation = MKPointAnnotation()
sourceAnnotation.title = "Times Square"
if let location = sourcePlacemark.location {
sourceAnnotation.coordinate = location.coordinate
}
let destinationAnnotation = MKPointAnnotation()
destinationAnnotation.title = "Empire State Building"
if let location = destinationPlacemark.location {
destinationAnnotation.coordinate = location.coordinate
}
// 6.
self.mapView.showAnnotations([sourceAnnotation,destinationAnnotation], animated: true )
// 7.
let directionRequest = MKDirectionsRequest()
directionRequest.source = sourceMapItem
directionRequest.destination = destinationMapItem
directionRequest.transportType = .walking
// Calculate the direction
let directions = MKDirections(request: directionRequest)
// 8.
directions.calculate {
(response, error) -> Void in
guard let response = response else {
if let error = error {
print("Error: \(error)")
}
return
}
let route = response.routes[0]
self.mapView.add((route.polyline), level: MKOverlayLevel.aboveRoads)
let rect = route.polyline.boundingMapRect
self.mapView.setRegion(MKCoordinateRegionForMapRect(rect), animated: true)
}
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.strokeColor = UIColor.blue
renderer.lineWidth = 4.0
return renderer
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Output :
How can I set my map MKAnnotation CalloutAccessoryView to show the selected image mapped out from my realm database (with icons and buttons on either side, as the left and right calloutAccessory?). I also need it to recognize the class/member for other functions on my MapViewController here? The mapping of the data and left and right callout accessories are working fine, but I can't figure out how to add the selected image, so it show up as a large poster-style image on the callout (with the small icons & buttons on either side).
The image is SpecimenAnnotation.objectPoster
The SpecimenAnnotation (which is working fine to map the data) and 'objectPoster' value is the selected (and mapped) Annotation from the class Specimen, included in my file Specimen.swift, like this;
class Specimen: Object{
dynamic var name = ""
dynamic var objectPoster = ""
dynamic var latitude = 0.0
dynamic var longitude = 0.0
dynamic var created = NSDate()
dynamic var category: Category!
}
In my MapViewController, on the line 'annotationView.detailCalloutAccessoryView = UIImageView(image: SpecimenAnnotation.objectPoster)' I am getting a red alert error "instance member 'objectPoster' cannot be used on type 'Specimen Annotation'
I am a beginner. Would greatly appreciate your suggestions. Any ideas?
Here are the code blocks regarding this data and error:
// Create annotations for each one
for specimen in specimens { // 3
let coord = CLLocationCoordinate2D(latitude: specimen.latitude, longitude: specimen.longitude);
let specimenAnnotation = SpecimenAnnotation(coordinate: coord, poster: specimen.objectPoster, subtitle: specimen.category.name, specimen: specimen)
mapView.addAnnotation(specimenAnnotation) // 4
}
}
}
//MARK: - MKMapview Delegate
extension MapViewController: MKMapViewDelegate {
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
guard let subtitle = annotation.subtitle! else { return nil }
if (annotation is SpecimenAnnotation) {
if let annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(subtitle) {
return annotationView
} else {
let currentAnnotation = annotation as! SpecimenAnnotation
let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: subtitle)
annotationView.image = UIImage(named:"IconStudent")
annotationView.enabled = true
annotationView.canShowCallout = true
// ERROR message here>
annotationView.detailCalloutAccessoryView = UIImageView(image: SpecimenAnnotation.objectPoster)
// right accessory view
let infoButton = UIButton(type: UIButtonType.InfoDark)
// Left accessory view
let image = UIImage(named: "button50")
let specimenButton = UIButton(type: .Custom)
specimenButton.frame = CGRectMake(0, 0, 30, 30)
specimenButton.setImage(image, forState: .Normal)
annotationView.rightCalloutAccessoryView = infoButton
annotationView.leftCalloutAccessoryView = specimenButton
if currentAnnotation.title == "Empty" {
annotationView.draggable = true
}
return annotationView
}
}
return nil
}
I solved it, by pulling the value into the currentAnnotation, and adding it in a UIImageView on the DetailCalloutAccessoryView
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
guard let subtitle = annotation.subtitle! else { return nil }
if (annotation is SpecimenAnnotation) {
if let annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(subtitle) {
return annotationView
} else {
let currentAnnotation = annotation as! SpecimenAnnotation
let currentImage = currentAnnotation.objectPoster!
let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: subtitle)
}
annotationView.enabled = true
annotationView.canShowCallout = true
// right accessory view
let calloutInfoButton = UIButton(type: UIButtonType.InfoDark)
// Left accessory view
let calloutLeftImage = UIImage(named: "button50p")
let calloutLeftButton = UIButton(type: .Custom)
calloutLeftButton.frame = CGRectMake(0, 0, 30, 30)
calloutLeftButton.setImage(calloutLeftImage, forState: .Normal)
//show Image on callout Accessory
let url = NSURL(string: currentImage)
let data = NSData(contentsOfURL: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check
//annotationView.image = UIImage(data: data!)
let calloutImage = UIImage(data:data!)
annotationView.detailCalloutAccessoryView = UIImageView(image: calloutImage)
annotationView.rightCalloutAccessoryView = calloutInfoButton
annotationView.leftCalloutAccessoryView = calloutLeftButton
if currentAnnotation.title == "Empty" {
annotationView.draggable = true
}
return annotationView
}
}
return nil
}
I am trying to add custom images to my mapView but am not having any luck so far. I keep getting a SIGABRT error when the map is loaded. here is my code:
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if annotation.isKindOfClass(NiteOwlAnnotations) {
let reuseId = "test"
var annoView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId)
if annoView == nil {
annoView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
annoView?.image = UIImage(named: "bar-map-icon")
annoView?.canShowCallout = true
} else {
annoView?.annotation = annotation
}
return annoView
/* let annoView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "Default")
// change color of pins.
annoView.pinTintColor = UIColor.blackColor()
annoView.animatesDrop = false
annoView.image = UIImage(named: "bar-map-icon")
annoView.backgroundColor = UIColor.clearColor()
annoView.canShowCallout = true
return annoView
*/
} else if annotation.isKindOfClass(MKUserLocation) {
return nil
}
return nil
}// end func
//--------------------------------------------------------------
func createAnnotationForLocation(location: CLLocation) {
let niteOwl = NiteOwlAnnotations(coordinate: location.coordinate)
map.addAnnotation(niteOwl)
}
I have looked for more information on this and still no luck yet:
Change pin image on MKMapView in Swift