mapView: GMSMapView! nil causing the app to crash (swift) - ios

I'm completely new when it comes to use Google Maps SKD for iOS and I have the following problem: My app uses Firebase and the MapVC is the first VC if the user is logged in. I set a UIVIew in the storyboard with the size I wanted and made its class GMSMapView. I also use the CLLocation Manager and the location comes ok, I pass it on to the camera variable and it's fine. But if add this code: mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera) or this code: self.mapView.camera = camera the app crashes because mapView is nil. If I don't add those lines of code, the map shown is just the default map (Europe). What I need is the map camera to show the location I'm getting from CLLocation and updates it on the map as the location changes as well.
My code for the MapVC is below:
import UIKit
import GoogleMaps
import GooglePlaces
import CoreLocation
import Alamofire
import Firebase
import FirebaseFirestore
class AlertaVC: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var alertaBtn: RoundButton!
var latitude: CLLocationSpeed?
var longitude: CLLocationDegrees?
var mapView: GoogleMaps.GMSMapView!
var db: Firestore!
var userID: String?
var nameUser: Any?
let locationManager = CLLocationManager()
#IBAction func dispararAlertaBtn(_ sender: UIButton) {
//changing the title and color after the alert is sent
alertaBtn.isSelected = !alertaBtn.isSelected
if alertaBtn.isSelected {
//setting up twilio to send the alert SMS
let headers: HTTPHeaders = [
"Content-Type": "application/x-www-form-urlencoded"
]
//add the phone numbers from the firebase document
let parameters: Parameters = [
"To": "phone-number",
"Body": "Ola, to enviando SMS!"
]
//change the SMS body to include the user's name and the link with live location
AF.request("https://url.twil.io/smsAlerta", method: .post, parameters: parameters, headers: headers).responseJSON { response in
print(response.response as Any, "response alamofire")
}
alertaBtn.setTitle("Encerrar alerta!", for: .normal)
alertaBtn.backgroundColor = UIColor(red: 0.83529, green: 0.4, blue: 0.5725490196, alpha: 1.0)
} else {
alertaBtn.setTitle("Disparar alerta!", for: .normal)
alertaBtn.backgroundColor = UIColor(red: 0.3254, green: 0.1921, blue: 0.2627, alpha: 1.0)
}
}
override func viewDidLoad() {
super.viewDidLoad()
locationManager.requestAlwaysAuthorization()
locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
}
db = Firestore.firestore()
let user = Auth.auth().currentUser
if let user = user {
// The user's ID, unique to the Firebase project.
self.userID = user.uid
print(self.userID!, "user ID firebase")
}
}
override func viewWillAppear(_ animated: Bool) {
//showCurrentLocation() Tried calling the function #ViewWillAppear but it didn't work either
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let locValue: CLLocationCoordinate2D = manager.location?.coordinate else { return }
latitude = locValue.latitude
longitude = locValue.longitude
let camera: GMSCameraPosition = GMSCameraPosition.camera(withLatitude: latitude ?? 19.741755, longitude: longitude ?? -155.844437, zoom: 7.0) //if doesnt load will show Hawaii
print(camera as Any, "camera") <-- WORKS FINE: GMSCameraPosition 0x600000f389f0: target:(37.332, -122.031) bearing:0.000 zoomLevel:7.000 viewingAngle:0.000 camera
self.mapView.camera = camera <<--- CRASHES HERE: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
self.mapView?.animate(to: camera) <<-- WITHOUT THE LINE ABOVE THIS ANIMATION DOESN`T HAPPEN, BECAUSE MAPVIEW IS NIL
print("locations = \(latitude ?? 56.56), \(longitude ?? 45.45)")
}
func showCurrentLocation() { <<- NOT BEING CALLED, BUT IF IT IS, CRASHES
//mapView.settings.myLocationButton = true
let locationObj = locationManager.location!
let coord = locationObj.coordinate
let lattitude = coord.latitude
let longitude = coord.longitude
print(" lat in updating \(lattitude) ")
print(" long in updating \(longitude)")
let center = CLLocationCoordinate2D(latitude: locationObj.coordinate.latitude, longitude: locationObj.coordinate.longitude)
let marker = GMSMarker()
marker.position = center
marker.title = "current location"
marker.map = mapView
let camera: GMSCameraPosition = GMSCameraPosition.camera(withLatitude: lattitude, longitude: longitude, zoom: 7.0)
mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera) <-- CRASHES HERE
self.mapView.animate(to: camera)
}
func fetchUserName(userId: String) {
db.collection("Usuarias").document(self.userID!).getDocument() {
(document, err) in
if let document = document, document.exists {
let dataDescription = document.data()!["Nome"] ?? "nil"
print("Document data: \(dataDescription)")
self.nameUser = dataDescription
print(self.nameUser as Any, "dentro do fetch")
} else {
print("Document does not exist")
}
}
}
}
I'd appreciate any help. Thanks =)

If you've created the 'View' in storyboard and assign as 'GMSMapView' you should add it as 'IBOutlet' in ViewController, you can do it by clicking 'Option' on keyboard and drag from map to ViewController like you've add 'alertaBtn', if you want to create map programmatically you need to initiate it in 'viewDidLoad' method then use it to set camera or to do other changes.

Related

Using Google Places API to find nearby places BY TYPE (parks) for iOS SwiftUI

I'm pretty new to Swift/iOS app dev. so far, so I'm struggling to figure this out. Basically, I'm trying to make it so when the app opens (first screen is the map), it automatically finds nearby places that are only parks around the user's current location and have these locations annotated with markers on a map (Google Maps) using Google Places API for iOS using updated SwiftUI/Swift 5.0: Using no storyboards!. Table I types, in this case, parks: https://developers.google.com/places/web-service/supported_types
So far, this is the code I have... It uses GMSPlaceLikelihood of places nearby the user's location. This is more so an example of what I want to achieve, however using Google Places API nearby search instead so I can show only parks. Image of app running:
Image
(The place's found from nearby are then listed in a table as shown on the image. This list is just for show)
Thanks in advance for any advice/help.
GoogleMapsView.swift:
#ObservedObject var locationManager = LocationManager()
#ObservedObject var place = PlacesManager()
func makeUIView(context: Self.Context) -> GMSMapView {
let camera = GMSCameraPosition.camera(withLatitude: locationManager.latitude, longitude: locationManager.longitude, zoom: 14)
let mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
mapView.isMyLocationEnabled = true
mapView.settings.rotateGestures = false
mapView.settings.tiltGestures = false
mapView.isIndoorEnabled = false
mapView.isTrafficEnabled = false
mapView.isBuildingsEnabled = false
mapView.settings.myLocationButton = true
place.currentPlacesList(completion: { placeLikelihoodList in
if let placeLikelihoodList = placeLikelihoodList {
print("total places: \(placeLikelihoodList.count)")
for likelihood in placeLikelihoodList {
let place = likelihood.place
let position = CLLocationCoordinate2D(latitude: place.coordinate.latitude, longitude: place.coordinate.longitude)
let marker = GMSMarker(position: position)
marker.title = place.name
marker.map = mapView
}
}
})
return mapView
}
func updateUIView(_ mapView: GMSMapView, context: Context) {
// let camera = GMSCameraPosition.camera(withLatitude: locationManager.latitude, longitude: locationManager.longitude, zoom: zoom)
// mapView.camera = camera
mapView.animate(toLocation: CLLocationCoordinate2D(latitude: locationManager.latitude, longitude: locationManager.longitude))
}
PlacesManager.swift:
class PlacesManager: NSObject, ObservableObject {
private var placesClient = GMSPlacesClient.shared()
#Published var places = [GMSPlaceLikelihood]()
override init() {
super.init()
currentPlacesList { (places) in
guard let places = places else {
return
}
self.places = places
}
}
func currentPlacesList(completion: #escaping (([GMSPlaceLikelihood]?) -> Void)) {
// Specify the place data types to return.
let fields: GMSPlaceField = GMSPlaceField(rawValue: UInt(GMSPlaceField.name.rawValue) |
UInt(GMSPlaceField.placeID.rawValue) | UInt(GMSPlaceField.types.rawValue) | UInt(GMSPlaceField.coordinate.rawValue))!
placesClient.findPlaceLikelihoodsFromCurrentLocation(withPlaceFields: fields, callback: {
(placeLikelihoodList: Array<GMSPlaceLikelihood>?, error: Error?) in
if let error = error {
print("An error occurred: \(error.localizedDescription)")
return
}
if let placeLikelihoodList = placeLikelihoodList {
for likelihood in placeLikelihoodList {
let place = likelihood.place
}
completion(placeLikelihoodList)
}
})
}
ContentView.swift:
var body: some View {
VStack {
GoogleMapsView()
.edgesIgnoringSafeArea(.top)
.frame(height: 400)
PlacesList()
}
.offset(y: 100)
}
https://developers.google.com/places/web-service/search
I suggest you visit this page, it describes the API in detail and shows examples how to call it with different parameters.
After you get the response (I suggest getting it in json format and using SwiftyJSON cocoa pod to parse it) populate the table.

swift 4 Annotation from json not visible until user drags on map

I am having problems showing the annotations on the map. They only show when I move or drag the map.
I tried to follow tutorial on youtube and also one of the questions which is c sharp
Please can sone one help .. here is the code
Here I created a class for the annotation
class customPin: NSObject, MKAnnotation {
var coordinate: CLLocationCoordinate2D
var title: String?
var subtitle: String?
init(pinTitle:String, pinSubTitle:String, location:CLLocationCoordinate2D) {
self.title = pinTitle
self.subtitle = pinSubTitle
self.coordinate = location
}}
class ViewController: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var mapView: MKMapView!
var locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
mapView.register(CustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
self.locationManager.requestWhenInUseAuthorization()
//json show the list of the parks
pipiCanList()
//show location Barcelona
let location = CLLocationCoordinate2D(latitude: 41.3851 , longitude:2.1734)
let region = MKCoordinateRegion(center: location, span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05))
self.mapView.setRegion(region, animated: true)
self.mapView.delegate = self
}
get the json information
func pipiCanList(){
let url = "http://bluewave.lk/Apps/pipiCan/Api/read.php";
let urlObj = URL(string:url);
URLSession.shared.dataTask(with: urlObj!) {(data, response, error) in
do{
let pipiCans = try JSONDecoder().decode([pipiCanDBList].self, from: data!)
for pipiCan in pipiCans{
let Latitude = (pipiCan.ParkLatitude! as NSString).doubleValue
let Longitude = (pipiCan.ParkLongitude! as NSString).doubleValue
let location = CLLocationCoordinate2D(latitude: Latitude, longitude: Longitude)
//add to
let pin = customPin(pinTitle: pipiCan.ParkName!, pinSubTitle: pipiCan.Area!, location: location)
self.mapView.addAnnotation(pin)
}
} catch{
print ("Error - cannot get list")
}
}.resume()
}
}
You need
DispatchQueue.main.async {
let pin = customPin(pinTitle: pipiCan.ParkName!, pinSubTitle: pipiCan.Area!, location: location)
self.mapView.addAnnotation(pin)
}
As URLSession.shared.dataTask callback is in a background thread and you should access UIKit elements like mapView in the main thread You can also do
self.mapView.showAnnotations(self.mapView.annotations, animated: true)
after the for loop to show all added annotations also keep in mind you set a region here
let location = CLLocationCoordinate2D(latitude: 41.3851 , longitude:2.1734)
let region = MKCoordinateRegion(center: location, span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05))
that the response may be with far location values than the above region

Taking latitude & longitude from Cloud Firestore and adding them to Mapbox as annotation

I'm trying to retrieve the flight number, latitude & longitude from all Firestore documents and add them as Mapbox annotations to a mapView. So far the code pulls the data from Cloud Firestore and stores them as variables. Aditionally the code displays a Mapbox map with coordinates but they must be assigned manually in the array
Im having trouble with appending the variables from Firestore to the coordinates array.
I found this "Add annotation after retrieving users latitude and longitude from Firebase" which was along the correct lines but its to do with firebase and not firestore.
Any help would be greatly appreciated!
class HomeViewController: UIViewController, MGLMapViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let mapView = MGLMapView(frame: view.bounds)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.styleURL = MGLStyle.darkStyleURL
mapView.tintColor = .lightGray
mapView.centerCoordinate = CLLocationCoordinate2D(latitude: 0, longitude: 66)
mapView.zoomLevel = 2
mapView.delegate = self
view.addSubview(mapView)
var aircraftArray = [""]
let db = Firestore.firestore()
let AircraftRef = db.collection("LiveAircraftData").getDocuments { (snapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in snapshot!.documents {
let FlightNumber = document.documentID
let latitude = document.get("Latitude") as! Double
let longitude = document.get("Longitude") as! Double
print(FlightNumber, latitude, longitude)
var Coordinates = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
aircraftArray.append(Coordinates)
var pointAnnotations = [MGLPointAnnotation]()
for coordinate in Coordinates {
let point = MGLPointAnnotation()
point.coordinate = coordinate
point.title = "\(coordinate.latitude), \(coordinate.longitude)"
pointAnnotations.append(point)
}
mapView.addAnnotations(pointAnnotations)
}
}
}
}
This is what my database looks like
screenshot of code

Add two coordinates in a button function to launch mapKit and start navigation between two points (Swift)

I'm using this class
import UIKit
import CoreLocation
import GoogleMaps
import GooglePlaces
import SwiftyJSON
import Alamofire
import MapKit
class FinalClass: UIViewController {
#IBOutlet weak var containerView: UIView!
#IBOutlet weak var bottomInfoView: UIView!
#IBOutlet weak var descriptionLabel: UILabel!
#IBOutlet weak var distanceLabel: UILabel!
var userLocation:CLLocationCoordinate2D?
var places:[QPlace] = []
var index:Int = -1
var locationStart = CLLocation()
var locationEnd = CLLocation()
var mapView:GMSMapView!
var marker:GMSMarker?
override func loadView() {
super.loadView()
}
override func viewDidLoad() {
super.viewDidLoad()
guard index >= 0, places.count > 0 else {
return
}
let place = places[index]
let lat = place.location?.latitude ?? 1.310844
let lng = place.location?.longitude ?? 103.866048
// Google map view
let camera = GMSCameraPosition.camera(withLatitude: lat, longitude: lng, zoom: 12.5)
mapView = GMSMapView.map(withFrame: self.view.bounds, camera: camera)
mapView.autoresizingMask = [.flexibleHeight, .flexibleWidth, .flexibleTopMargin, .flexibleBottomMargin, .flexibleLeftMargin, .flexibleRightMargin]
self.containerView.addSubview(mapView)
// Add gesture
addSwipeGesture()
didSelect(place: place)
if userLocation != nil {
addMarkerAtCurrentLocation(userLocation!)
}
}
func addSwipeGesture() {
let directions: [UISwipeGestureRecognizerDirection] = [.right, .left]
for direction in directions {
let gesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe(sender:)))
gesture.direction = direction
self.bottomInfoView.addGestureRecognizer(gesture)
}
}
func addMarkerAtCurrentLocation(_ userLocation: CLLocationCoordinate2D) {
let marker = GMSMarker()
marker.position = userLocation
marker.title = "Your location"
marker.map = mapView
}
func didSelect(place:QPlace) {
guard let coordinates = place.location else {
return
}
// clear current marker
marker?.map = nil
// add marker
marker = GMSMarker()
marker?.position = coordinates
marker?.title = place.name
marker?.map = mapView
mapView.selectedMarker = marker
moveToMarker(marker!)
// update bottom info panel view
let desc = place.getDescription()
descriptionLabel.text = desc.characters.count > 0 ? desc : "-"
distanceLabel.text = "-"
// update distance
if userLocation != nil {
let dist = distance(from: userLocation!, to: coordinates)
distanceLabel.text = String.init(format: "Distance %.2f meters", dist)
self.drawPath(startLocation: userLocation!, endLocation: coordinates)
}
title = place.name
}
func moveToMarker(_ marker: GMSMarker) {
let camera = GMSCameraPosition.camera(withLatitude: marker.position.latitude,
longitude: marker.position.longitude,
zoom: 12.5)
self.mapView.animate(to: camera)
}
// distance between two coordinates
func distance(from: CLLocationCoordinate2D, to: CLLocationCoordinate2D) -> CLLocationDistance {
let from = CLLocation(latitude: from.latitude, longitude: from.longitude)
let to = CLLocation(latitude: to.latitude, longitude: to.longitude)
return from.distance(from: to)
}
func handleSwipe(sender: UISwipeGestureRecognizer) {
guard index >= 0, places.count > 0 else {
return
}
if sender.direction == .left {
if index < places.count - 2 {
index += 1
didSelect(place: places[index])
}
} else if sender.direction == .right {
if index > 1 {
index -= 1
didSelect(place: places[index])
}
}
}
func drawPath(startLocation: CLLocationCoordinate2D, endLocation: CLLocationCoordinate2D) {
let from = CLLocation(latitude: startLocation.latitude, longitude: startLocation.longitude)
let to = CLLocation(latitude: endLocation.latitude, longitude: endLocation.longitude)
let origin = "\(from.coordinate.latitude),\(from.coordinate.longitude)"
let destination = "\(to.coordinate.latitude),\(to.coordinate.longitude)"
let url = "https://maps.googleapis.com/maps/api/directions/json?origin=\(origin)&destination=\(destination)&mode=driving"
Alamofire.request(url).responseJSON { response in
print(response.request as Any) // original URL request
print(response.response as Any) // HTTP URL response
print(response.data as Any) // server data
print(response.result as Any) // result of response serialization
let json = JSON(data: response.data!)
let routes = json["routes"].arrayValue
// print route using Polyline
for route in routes
{
let routeOverviewPolyline = route["overview_polyline"].dictionary
let points = routeOverviewPolyline?["points"]?.stringValue
let path = GMSPath.init(fromEncodedPath: points!)
let polyline = GMSPolyline.init(path: path)
polyline.strokeWidth = 4
polyline.strokeColor = UIColor.black
polyline.map = self.mapView
}
}
}
#IBAction func navigationStart(_ sender: Any) {
}
with a google maps to add place markers, draw the direction on the map and show the distance between two points, now i would like to launch the navigator between startLocation: userLocation! and endLocation: coordinates but with some research i saw that i can not launch the navigator in the same view, i need to open the maps application, so i decided to add the MapKit and a button
#IBAction func navigationStart(_ sender: Any) {
}
so how can i do that by pressing the button the map application opens with direction from userLocation to coordinates ? I already looked to similar question but is a little different to my problem, because i already have the points but in different format.
Your question is a little confusing but if you want to open the maps app and show direction from a user's current location to another point on the map then you don't need to pass the user's location, just the destination:
Swift 4:
let coordinate = CLLocationCoordinate2DMake(51.5007, -0.1246)
let placeMark = MKPlacemark(coordinate: coordinate)
let mapItem = MKMapItem(placemark: placeMark)
mapItem.name = "Big Ben"
mapItem.openInMaps(launchOptions: [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving])
Objective C:
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(51.5007, -0.1246);
MKPlacemark *placemark = [[MKPlacemark alloc] initWithCoordinate: coordinate];
MKMapItem *mapItem = [[MKMapItem alloc] initWithPlacemark:placemark];
[mapItem setName:#"Big Ben"];
[mapItem openInMapsWithLaunchOptions:#{MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving}];

Firebase database now only displaying newly added annotations on map

I have an app that allows people to put annotations on a map that get stored in a Firebase database, I have recently been trying to add a new feature to the app so to avoid messing about with my real database I created another app in firebase and used it for me to do my testing on. I deleted the GoogleService-Info.Plist and imported the one for the new test app but now when I delete the test GoogleService-Info.Plist and replace it with the one that works with my Live app none of my annotations show up on the map, but any I create from that point onwards will show up.
I've done a clean and build but still the same result. All the data is still there in my original database as my AppStore version of the app works exactly as it should, I really don't know what could be wrong here, I'll let you see what my code is for retrieving the annotations, in case that helps you with your answer.
import UIKit
import MapKit
import Firebase
class MapViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
#IBOutlet weak var postboxesLoggedLabel: UILabel!
var locationManager = CLLocationManager()
let annotation = MKPointAnnotation()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
displayAnnotations()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLocation: CLLocation = locations[0]
let latitude = userLocation.coordinate.latitude
let longitude = userLocation.coordinate.longitude
let latDelta: CLLocationDegrees = 0.05
let lonDelta: CLLocationDegrees = 0.05
let span = MKCoordinateSpan(latitudeDelta: latDelta, longitudeDelta: lonDelta)
let location = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
let region = MKCoordinateRegion(center: location, span: span)
self.mapView.setRegion(region, animated: true)
locationManager.stopUpdatingLocation()
}
func displayAnnotations() {
let ref = Database.database().reference()
ref.child("Postbox").observe(.childAdded, with: { (snapshot) in
let monToFri = (snapshot.value as AnyObject!)!["Monday to Friday Collection Time"] as! String!
let sat = (snapshot.value as AnyObject!)!["Saturday Collection Time"] as! String!
let latitude = (snapshot.value as AnyObject!)!["Latitude"] as! String!
let longitude = (snapshot.value as AnyObject!)!["Longitude"] as! String!
let lastCollection = "Mon - Fri: \(monToFri!)" + " Sat: \(sat!)"
self.annotation.coordinate = CLLocationCoordinate2D(latitude: (Double(latitude!))!, longitude: (Double(longitude!))!)
self.annotation.title = "Last Collection:"
self.annotation.subtitle = lastCollection
self.mapView.addAnnotation(self.annotation)
self.postboxesLoggedLabel.text = String(self.mapView.annotations.count)
})
}
/* func removeAnnotation(gesture: UIGestureRecognizer) {
if gesture.state == UIGestureRecognizerState.ended {
self.mapView.removeAnnotation(annotation)
print("Annotation Removed")
}
} */
#IBAction func myLocation(_ sender: Any) {
locationManager.startUpdatingLocation()
}
#IBAction func mapType(_ sender: Any) {
switch ((sender as AnyObject).selectedSegmentIndex) {
case 0:
mapView.mapType = .standard
case 1:
mapView.mapType = .satellite
default: // or case 2
mapView.mapType = .hybrid
}
}
}

Resources