Swift 3 Firebase to MapKit - ios

I am looking to get data from a Firebase Database into a map. I have a good amount working but am stuck. This is all 100% code, no storyboards.
What I have below works. It will show all pins on a map but I am stuck at that point. I want to be able to tap each pin and get the data for that particular pin. When I do, using the code below I will print out "tap" and the array for MTA.
The data could be shown in a pin annotation/info window or in labels below the map in the View Controller. I am unsure of where to put the code/get it to work. I assume not in the for snap in snapshot but I cannot get the data out for each individual record/pin.
View did load for reference:
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
self.navigationItem.title = stop
mapContainerView.delegate = self
view.addSubview(mapContainerView)
setUpContorller()
fetchTrip()
}
Function to get coordinates for map:
func fetchTrip(){
let ref = FIRDatabase.database().reference()
let tripsRef = ref.child("Trips").child(stop!)
tripsRef.observeSingleEvent(of: .value, with: { (snapshot) in
for snap in snapshot.children {
let tripSnap = snap as! FIRDataSnapshot
if let dict = tripSnap.value as? [String:AnyObject] {
let lat = dict["lat"] as! CLLocationDegrees
let lng = dict["lng"] as! CLLocationDegrees
let MTA = dict["MTAStop"] as! String
let center = CLLocationCoordinate2D(latitude: lat, longitude: lng)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.08, longitudeDelta: 0.08))
self.stopMTA.append(MTA)
self.mapContainerView.setRegion(region, animated: true)
let pinCoordinate: CLLocationCoordinate2D = CLLocationCoordinate2DMake(lat, lng)
let annotation = MKPointAnnotation()
annotation.coordinate = pinCoordinate
self.mapContainerView.addAnnotation(annotation)
}
}
})
}
Function to tap pin: (I know I need more.. not sure what, possibly a class to hold the data.)
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
// do something
print("tap")
print(stopMTA)
}
Here is an example of the database from simulator testing.
Thanks in advance!

Using MKPointAnnotation() will only place a pin on the map. You need to use a custom class to hold the annotation information and call it with initializers.
Original:
let annotation = MKPointAnnotation()
annotation.coordinate = pinCoordinate
self.mapContainerView.addAnnotation(annotation)
Working version:
let annotation = PinAnnotation(title: MTA, coordinate: pinCoordinate, info: MTA)
annotation.coordinate = pinCoordinate
self.mapContainerView.addAnnotation(annotation)
Also Need:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
}
and
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
}

Related

How can I show the annotation point information on the map?

When I click Annotation on the map, I want the information about the clicked location to appear in the back view at the bottom.I used Mapkit to create the map
Annotation Array
let annotationLocations = [
["title":"X vet Clinic","latitude":39.895177 , "longitude":32.838194],
["title":"Y Vet Clinic","latitude": 39.894749, "longitude":32.841074],
["title":"Z Vet Clinic","latitude": 39.893615, "longitude":32.841476]
]
With this function, I can show the locations specified in the latitude longitudes above on the map
func createAnnotations(locations: [[String: Any]])
{
for location in locations{
let annotations = MKPointAnnotation()
annotations.title = location["title"] as? String
annotations.coordinate = CLLocationCoordinate2D(latitude: location["latitude"] as! CLLocationDegrees, longitude: location["longitude"] as! CLLocationDegrees)
myMap.addAnnotation(annotations)
}
}
You can subclass your annotation and put the required data in there;
class VetClinicAnnotation: MKPointAnnotation {
let name: String
let address: String
let image: UIImage
init(with name: String, address: String, image: UIImage) {
self.name = name
self.address = address
self.image = image
}
}
Then you can get the annotation info from your map view delegate;
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
if let annotation = view.annotation as? VetClinicAnnotation {
// Use the annotation data
}
}
First set the delegate
myMap.delegate = self
Then implement
func mapView(_ mapView: MKMapView,didSelect view: MKAnnotationView) {
let ann = view.annotation as! MKPointAnnotation
// use ann
}

MapKit: Route not being displayed between two annotations

Im trying to display a route between two annotations.
The annotations and the region work fine but the route won't show up and I have no idea why
It looks like the route is not being rendered at all.
I'm sure that the route exists because I tried to print it and it is in the directionResponse.routes
Any suggestions?
I'm using SwiftUI
Then this is included in a parent view.
import SwiftUI
import MapKit
import FirebaseFirestore
struct MapView: UIViewRepresentable {
var packageLocation: GeoPoint
var destination: GeoPoint
var driverLocation = CLLocationCoordinate2D()
func makeUIView(context: UIViewRepresentableContext<MapView>) -> MKMapView {
MKMapView()
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolygonRenderer(overlay: overlay)
renderer.strokeColor = .blue
renderer.lineWidth = 2.0
return renderer
}
func updateUIView(_ uiView: MKMapView, context: UIViewRepresentableContext<MapView>) {
let requestLocation: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: packageLocation.latitude, longitude: packageLocation.longitude)
let destinationLocation: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: destination.latitude, longitude: destination.longitude)
//let span = MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1)
//let region = MKCoordinateRegion(center: requestLocation, span: span)
//uiView.setRegion(region, animated: true)
let annotation = MKPointAnnotation()
annotation.coordinate = requestLocation
annotation.title = "Package Title"
uiView.addAnnotation(annotation)
let annotation2 = MKPointAnnotation()
annotation2.coordinate = destinationLocation
annotation2.title = "Destiantion"
uiView.addAnnotation(annotation2)
let sourcePlacemark = MKPlacemark(coordinate: requestLocation)
let destinationPlacemark = MKPlacemark(coordinate: destinationLocation)
let directionRequest = MKDirections.Request()
directionRequest.source = MKMapItem(placemark: sourcePlacemark)
directionRequest.destination = MKMapItem(placemark: destinationPlacemark)
directionRequest.transportType = .automobile
let directions = MKDirections(request: directionRequest)
directions.calculate { (response, error) in
guard let directionResponse = response else {
if let error = error {
print(error.localizedDescription)
}
return
}
print(directionResponse)
let route = directionResponse.routes[0]
uiView.addOverlay(route.polyline, level: .aboveRoads)
let rect = route.polyline.boundingMapRect
uiView.setRegion(MKCoordinateRegion(rect), animated: true)
}
}
}
You've almost got it.
The one issue that you need to resolve is the use of the MKMapView delegate functions.
The easiest way to do that is to subclass MKMapView and make your own map view that has conforms to MKMapViewDelegate.
Firstly, create your own map view, subclassing MKMapView and conforming to MKMapViewDelegate. At the moment you're only really using the rendererFor overlay delegate method so I'll just implement that, but you can add other methods if you require them.
class WrappableMapView: MKMapView, MKMapViewDelegate {
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.strokeColor = .red
renderer.lineWidth = 4.0
return renderer
}
}
Then you need to update your UIViewRepresentable to use the new WrappableMapView that you just created. I have gone for making a functional example, so here I am passing in the request and destination locations. You can handle this how you want but at least this will give you something that works.
struct MyMapView: UIViewRepresentable {
#Binding var requestLocation: CLLocationCoordinate2D
#Binding var destinationLocation: CLLocationCoordinate2D
private let mapView = WrappableMapView()
func makeUIView(context: UIViewRepresentableContext<MyMapView>) -> WrappableMapView {
mapView.delegate = mapView // make sure we set our delegate to be the mapView we just created
return mapView
}
func updateUIView(_ uiView: WrappableMapView, context: UIViewRepresentableContext<MyMapView>) {
let requestAnnotation = MKPointAnnotation()
requestAnnotation.coordinate = requestLocation
requestAnnotation.title = "Package Title"
uiView.addAnnotation(requestAnnotation)
let destinationAnnotation = MKPointAnnotation()
destinationAnnotation.coordinate = destinationLocation
destinationAnnotation.title = "Destination"
uiView.addAnnotation(destinationAnnotation)
let requestPlacemark = MKPlacemark(coordinate: requestLocation)
let destinationPlacemark = MKPlacemark(coordinate: destinationLocation)
let directionRequest = MKDirections.Request()
directionRequest.source = MKMapItem(placemark: requestPlacemark)
directionRequest.destination = MKMapItem(placemark: destinationPlacemark)
directionRequest.transportType = .automobile
let directions = MKDirections(request: directionRequest)
directions.calculate { response, error in
guard let response = response else { return }
let route = response.routes[0]
uiView.addOverlay(route.polyline, level: .aboveRoads)
let rect = route.polyline.boundingMapRect
uiView.setRegion(MKCoordinateRegion(rect), animated: true)
// if you want insets use this instead of setRegion
// uiView.setVisibleMapRect(rect, edgePadding: .init(top: 50.0, left: 50.0, bottom: 50.0, right: 50.0), animated: true)
}
}
}
Finally we can put it all together with a ContentView that shows it works:
struct ContentView: View {
#State var requestLocation = CLLocationCoordinate2D(latitude: 51.509865, longitude: -0.118092)
#State var destinationLocation = CLLocationCoordinate2D(latitude: 51.501266, longitude: -0.093210)
var body: some View {
MyMapView(requestLocation: $requestLocation, destinationLocation: $destinationLocation)
}
}
This is what it should look like:
One thing to note, using the rendererFor overlay delegate function in the simulator causes an error. This only happens in the simulator and not on device, so don't be surprised if you see an error message like this in the console.
2019-11-08 18:50:30.034066+0000 StackOverflow[80354:9526181] Compiler error: Invalid library file

Get Latitude and longitude center of Google Map

I want to show full screen mapView, always get Latitude and longitude of center of mapView and show marker in this point.
func mapView(_ mapView: GMSMapView, didChange position: GMSCameraPosition) {
let lat = mapView.camera.target.latitude
print(lat)
let lon = mapView.camera.target.longitude
print(lon)
marker.position = CLLocationCoordinate2DMake(CLLocationDegrees(centerPoint.x) , CLLocationDegrees(centerPoint.y))
marker.map = self.mapView
returnPostionOfMapView(mapView: mapView)
}
func mapView(_ mapView: GMSMapView, idleAt position: GMSCameraPosition) {
print("idleAt")
//called when the map is idle
returnPostionOfMapView(mapView: mapView)
}
func returnPostionOfMapView(mapView:GMSMapView){
let geocoder = GMSGeocoder()
let latitute = mapView.camera.target.latitude
let longitude = mapView.camera.target.longitude
let position = CLLocationCoordinate2DMake(latitute, longitude)
geocoder.reverseGeocodeCoordinate(position) { response , error in
if error != nil {
print("GMSReverseGeocode Error: \(String(describing: error?.localizedDescription))")
}else {
let result = response?.results()?.first
let address = result?.lines?.reduce("") { $0 == "" ? $1 : $0 + ", " + $1 }
print(address)
// self.searchBar.text = address
}
}
}
i use this code in how can know Latitude and longitude that return in returnPostionOfMapView method is position of center mapView and show marker in this position?
You are doing it right to get the center of the map by using the google maps's func mapView(_ mapView: GMSMapView, didChange position: GMSCameraPosition) delegate.
Take a variable for center coordinates
var centerMapCoordinate:CLLocationCoordinate2D!
Implement this delegate to know the center position.
func mapView(_ mapView: GMSMapView, didChange position: GMSCameraPosition) {
let latitude = mapView.camera.target.latitude
let longitude = mapView.camera.target.longitude
centerMapCoordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
self.placeMarkerOnCenter(centerMapCoordinate:centerMapCoordinate)
}
Function to place a marker on center point
func placeMarkerOnCenter(centerMapCoordinate:CLLocationCoordinate2D) {
let marker = GMSMarker()
marker.position = centerMapCoordinate
marker.map = self.mapView
}
In this case you will get a lot of markers. So keep a global hold of marker and check if it is already present, just change the position
var marker:GMSMarker!
func placeMarkerOnCenter(centerMapCoordinate:CLLocationCoordinate2D) {
if marker == nil {
marker = GMSMarker()
}
marker.position = centerMapCoordinate
marker.map = self.mapView
}

Passing an object to custom view in Google maps SDK

I have custom view (a nib with its class) that I show when user taps on map marker. I also have an array of objects from where I'd like to show data of selected object when user taps on one of the markers. I create markers from the same array in a separate method. How to I get my element in array (to show additional data) when user taps the marker ? Or is there another way to get the object from array based on pressed marker. Apart from regexing based on lon and lat etc ?
I solved this by adding my model to
marker.userData
example:
func addMarkers(){
for place in places{
let marker = GMSMarker()
let placeLat = place.latitude
let placeLon = place.longitude
marker.position = CLLocationCoordinate2D(latitude: CLLocationDegrees(placeLat), longitude: CLLocationDegrees(placeLon))
marker.appearAnimation = .pop
marker.map = mpView
marker.userData = place
}
}
Then later I access it in:
func mapView(_ mapView: GMSMapView, markerInfoWindow marker: GMSMarker) -> UIView? {
let title = (marker.userData as! placeModel).name
let add = (marker.userData as! placeModel).address
let id = (marker.userData as! placeModel).id
let image = (marker.userData as! placeModel).image
let imageAddressInDocuments = ("\(getDocumentsDirectory())\(image)")
print("image address in documents %#", imageAddressInDocuments )
var infoWindow = Bundle.main.loadNibNamed("custView", owner: self, options: nil)?.first as! custView
infoWindow.titleLabel.text = title
infoWindow.addressLabel.text = add
infoWindow.restaurantImage.image = image != "" ? UIImage(contentsOfFile: imageAddressInDocuments) : UIImage(named: "addImage")
return infoWindow
}
GMSMapViewDelegate has the delegate function which lets you to return
your customView as info window.
optional public func mapView(_ mapView: GMSMapView, markerInfoWindow marker: GMSMarker) -> UIView?
You have to subclass GSMarker which takes your model in init.
class MapItem:GMSMarker {
var model: Model! //your data model
init(data: Model) {
super.init()
// pass cordinate
self.position = CLLocationCoordinate2D(latitude: data.latitude!, longitude: data.longitude!)
// pass Model
self.model = data
}
}
func mapView(_ mapView: GMSMapView, markerInfoWindow marker: GMSMarker) -> UIView? {
// Wrap GSMarker to your custom marker which is MapItem here
let customMarker = marker as! MapItem
// pass your model to CustomView(marker info window) in init
let customView = CustomView(markerModel: customMarker.model)
return customView
}

Sending Images to Several Instances of a ViewController in Swift

I download several "events" from CloudKit in my program. Each one contains a picture, title, and location. The second two are used right away and there is no problem. The image is only seen if the user clicks on the info button on the annotation that shows the picture using a separate view controller. I am having trouble getting the images to be passed to the ViewController before it is used while also keeping each event's picture separate.
Here is where each record is queried from CloudKit and put into a for loop that send then to the method below:
func loadEvent(_ completion: #escaping (_ error:NSError?, _ records:[CKRecord]?) -> Void)
{
//...record is downloaded from cloudkit
for record in records
{
if let asset = record["Picture"] as? CKAsset,
let data = NSData(contentsOf: asset.fileURL),
let image1 = UIImage(data: data as Data)
{
self.drawEvents(record["LocationF"] as! CLLocation, title1: record["StringF"] as! String, pic1: image1)
}
}
}
Here is where the variables are assigned and used to create the point annotation (it is a custom one that includes an image):
func drawEvents(_ loc: CLLocation, title1: String, pic1: UIImage)
{
mapView.delegate = self
let center = CLLocationCoordinate2D(latitude: loc.coordinate.latitude, longitude: loc.coordinate.longitude)
let lat: CLLocationDegrees = center.latitude
let long: CLLocationDegrees = center.longitude
self.pointAnnotation1 = CustomPointAnnotation()
self.pointAnnotation1.imageName = pic1
self.pointAnnotation1.title = title1
self.pointAnnotation1.subtitle = "Event"
self.pointAnnotation1.coordinate = CLLocationCoordinate2D(latitude: lat, longitude: long)
self.pinAnnotationView = MKPinAnnotationView(annotation: self.pointAnnotation1, reuseIdentifier: nil)
self.mapView.addAnnotation(self.pinAnnotationView.annotation!)
}
Here is the function that makes EventPage appear when the info button on an MKMapAnnotation is clicked:
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
let eventPageViewController:EventPageViewController = storyboard?.instantiateViewController(withIdentifier: "EventPage") as! EventPageViewController
self.present(eventPageViewController, animated: true, completion: nil)
}
Here is the ViewController for EventPage:
class EventPageViewController: UIViewController {
#IBOutlet weak var eventPic: UIImageView!
var photo: UIImage!
override func viewDidLoad() {
super.viewDidLoad()
let firstViewController:FirstViewController = storyboard?.instantiateViewController(withIdentifier: "Home") as! FirstViewController
photo = firstViewController.pointAnnotation1.imageName
//photo is nil
eventPic.image = photo
}
}
Intead of storing your annotations in a single instance property (which means that you only have access to the last one), store them in an array, although you don't need to store them at all in order to respond to taps.
var annotations = [CustomPointAnnotation]()
func drawEvents(_ loc: CLLocation, title1: String, pic1: UIImage)
{
mapView.delegate = self
let center = CLLocationCoordinate2D(latitude: loc.coordinate.latitude, longitude: loc.coordinate.longitude)
let lat: CLLocationDegrees = center.latitude
let long: CLLocationDegrees = center.longitude
var newAnnotation = CustomPointAnnotation()
newAnnotation = CustomPointAnnotation()
newAnnotation.imageName = pic1
newAnnotation.title = title1
newAnnotation.subtitle = "Event"
newAnnotation.coordinate = CLLocationCoordinate2D(latitude: lat, longitude: long)
self.mapView.addAnnotation(newAnnotation)
self.annotations.append(newAnnotation)
}
The annotation view that is tapped is passed to your delegate method, so you know which item it is. The annotation view's annotation property is your CustomPointAnnotation instance.
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
let eventPageViewController:EventPageViewController = storyboard?.instantiateViewController(withIdentifier: "EventPage") as! EventPageViewController
if let annotation = view.annotation as CustomPointAnnotation {
eventPageViewController.photo = annotation.imageName
}
self.present(eventPageViewController, animated: true, completion: nil)
}

Resources