I want to show annotations on the locations I get from my geofire database but even after writing the code and not getting any error no annotations are visible on the map.
My variable is
var vendorStore: Dictionary<String,Int>
My code in viewDidLoad
for (key,_) in KeyValue{
geoFireKeyValue.getLocationForKey(key, withCallback: { (location, error) in
if (error != nil) {
print("An error occurred getting the location for \"firebase-hq\": \(String(describing: error?.localizedDescription))")
} else if location != nil {
let annotation = MKPointAnnotation()
self.lat = (location?.coordinate.latitude)!
self.long = (location?.coordinate.longitude)!
annotation.coordinate = CLLocationCoordinate2D(latitude: self.lat, longitude: self.long)
self.mapView.addAnnotation(annotation)
self.mapView.showAnnotations(self.mapView.annotations, animated: true)
} else {
print("GeoFire does not contain a location for \"firebase-hq\"")
}
})
}
I am successfully getting the latitude and longitude in print statement as well but still no annotations are showing up. I tried moving the code else outside the for loop (just to check if any annotation show up) with some hard coded lat long coordinates and the annotation showed successfully. But still they aren't showing up when set in the geofire function within the loop (also not showing up in the loop outside the geofire function even with hard coded values). So kindly help me in resolving this issue.
I found the issue. The main problem was that I was sending the data from the previous view controller. That view controller was changed before the data was sent from the previous controller. So the solution is to make sure that data is set in the variable of the next view controller before the view actually appears. That's what I am trying to achieve now.
Related
I've been working on an app to annotate an MKMapView with all nightlife locations near a user. I have implemented a recenter function that is called by a SwiftUI button. The recenter works fine based on CLLocationManager.location, but after any MKLocalSearch query, the setRegion stops working. It must be the setRegion because the correct lat/long are still printed. I also know it is not due to the annotations because the same bug happens when the annotation add/remove part is commented out. Does it possibly have something to do with linking the searchRequest.region and map.region? manager is the CLLocationManager instance, map is the MapView
func recenter(){
print("Recenter called")
guard let center = manager.location?.coordinate else{
print("Could not get location for recenter")
return
}
let newRegion = MKCoordinateRegion(center: center, latitudinalMeters: 1000, longitudinalMeters: 1000)
print("\(center.latitude) and \(center.longitude)")
map.setRegion(newRegion, animated: true)
}
func queryAndAnnotate(){
let searchRequest = MKLocalSearch.Request()
searchRequest.naturalLanguageQuery = "nightlife"
searchRequest.region = map.region
let search = MKLocalSearch(request: searchRequest)
search.start{response, error in
guard let response = response else {
Alert(title:Text("Alert"), message: Text("Error: \(error?.localizedDescription ?? "Unknown Error")"))
return
}
var mapItemPlacemarks: [MKPointAnnotation] = []
self.barsList = []
for i in response.mapItems{
let x = MKPointAnnotation()
x.coordinate = i.placemark.coordinate
x.title = i.name
mapItemPlacemarks.append(x)
}
self.map.removeAnnotations(mapItemPlacemarks)
self.map.addAnnotations(mapItemPlacemarks)
}
}
MKLocalSearch.start calls the completionHandler in background task.
In this handler you are updating your mapView instance in background task.
Make sure to call all update methods of mapView in main task (removeAnnotations, addAnnotations, setRegion)
btw in
self.map.removeAnnotations(mapItemPlacemarks)
self.map.addAnnotations(mapItemPlacemarks)
you are removing mapItemPlacemarks that do not exist in the mapView. This makes no sense, but it is not your problem.
As it turns out the problem was coming from a dynamic update to a ScrollView. The ScrollView was within a slide up drawer that sat above the MapView on the Z axis. When the ScrollView was updated with the results from the local search, the MapView object lost track of userLocation (read 0,0) and the region could not be programmatically changed. I ended up fixing the problem by ditching the recenter() function altogether and instead implementing an MKUserTrackingButton within its own UIViewRepresentable (for SwiftUI). The MKUserTrackingButton is how the system maps cycles through user tracking modes. You can read the docs for it here: https://developer.apple.com/documentation/mapkit/mkusertrackingbutton
Using Google Places API for iOS, I am using the Place AutoComplete feature. When I start typing a place name for example 'Starbucks' it gives me starbucks location in several countries but not my local starbucks. How do I pass the current location to the API so the search results start from closest to my location?
You can pass a GMSCoordinateBounds to the which is "object biasing the results to a specific area specified by latitude and longitude bounds".
They have an example on their site showing how to do this:
let visibleRegion = self.mapView.projection.visibleRegion()
let bounds = GMSCoordinateBounds(coordinate: visibleRegion.farLeft, coordinate: visibleRegion.nearRight)
let filter = GMSAutocompleteFilter()
filter.type = GMSPlacesAutocompleteTypeFilter.City
placesClient.autocompleteQuery("Sydney Oper", bounds: bounds, filter: filter, callback: { (results, error: NSError?) -> Void in
guard error == nil else {
print("Autocomplete error \(error)")
return
}
for result in results! {
print("Result \(result.attributedFullText) with placeID \(result.placeID)")
}
})
This example show how to pull this data from a map view but you can also create the GMSCoordinateBounds manually.
Hope this helps.
I'm using the Google Place Picker APi and wanted to know if there is a way to remove the back button and the search button and also prevent the map from moving around, that is created with the _placePicker = [[GMSPlacePicker alloc] initWithConfig:config]; ?
if not, is there an alternative i can use that provides same functionality? Basically, I want the the closest points of interest near a users location..
Thanks.
I'm also trying to figure that out. So far, what I've come up with is to combine GMSMapView and GMSPlacesClient into your own custom viewcontroller.
To gather the nearby points of interest, you'll use the GMSPlacesClient:
placesClient = GMSPlacesClient.sharedClient()
likelyPlace = [GMSPlaces]()
placesClient.currentPlaceWithCallback({ (placeLikelihoods, error) in
if let error = error {
print("Error with Current place: \(error.localizedDescription)")
} else {
if let likelihoodList = placeLikelihoods{
for likelihood in likelihoodList.likelihoods {
let place = likelihood.place
self.likelyPlaces.append(place)
}
}
}
})
this will put the nearby places in your likelyPlaces array. Then it's up to you how you'd want to display the contents. Maybe put them in a tableView or as annotations on the map.
Hope this helps.
I am making a single view application in Xcode, with Google Map SDK. I have followed instructions online and my application can successfully load the google map view. I have also enabled myLocation, so that myLocation button shows on the map view.
I understand that clicking the myLocation button will change the camera location automatically, but I'm wondering what I should do to use the data of myLocation (say to add a marker or add a path node)?
I've tried directly accessing mapView.myLocation, for example
let lat = mapView.myLocation?.coordinate.latitude
let long = mapView.myLocation?.coordinate.longitude
path.addCoordinate(CLLocationCoordinate2D(latitude: lat!, longitude: long!))
However, this will crash the applicaton and throw:
fatal error: unexpectedly found nil while unwrapping an Optional value
What does this error message mean and how should I resolve this?
The error says that myLocation property of mapView is nil. You should check if there is a value before accessing it.
if let myLocation = mapView.myLocation {
let lat = myLocation.coordinate.latitude
let long = myLocation.coordinate.longitude
path.addCoordinate(CLLocationCoordinate2D(latitude: lat, longitude: long))
}
Also verify why myLocation is nil. It might be that the user didn't allow location services.
This will not give any errors:
let lat = mapView.myLocation?.coordinate.latitude ?? give any value of latitude
let long = mapView.myLocation?.coordinate.longitude ?? give any value of longitude
so, my current problem is as follows.
I would like to essentially use my current viewcontroller, but change the location settings somewhere else within the app and have the data pass though the viewcontroller with the updated location information.
I'll elaborate. I'm currently utilizing the following code for reverse geocoding, and obtaining the users location.
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
locationManager.stopUpdatingLocation()
if(locations.count > 0){
let location = locations[0] as! CLLocation
// println(location.coordinate)
if let currentLocatino = currLocation {
if CLLocation(latitude: currentLocatino.latitude, longitude: currentLocatino.longitude).distanceFromLocation(location) > 500 {
currLocation = location.coordinate
self.skip = 0
self.loadObjects()
}
}
else {
currLocation = location.coordinate
self.skip = 0
self.loadObjects()
}
CLGeocoder().reverseGeocodeLocation(CLLocation(latitude: currLocation!.latitude, longitude: currLocation!.longitude), completionHandler: {(placemarks, error) -> Void in
if error != nil {
println("Reverse geocoder failed with error" + error.localizedDescription)
return
}
if placemarks.count > 0 {
let date = NSDate()
let formatter = NSDateFormatter()
formatter.dateStyle = .MediumStyle
formatter.stringFromDate(date)
let pm = placemarks[0] as! CLPlacemark
var testifempty = "\(pm.subLocality)"
if testifempty == "nil"
{
self.locationManager.startUpdatingLocation()
if let lbutton = self.lbutton{
lbutton.text = "Hey " + "\(pm.locality)" //+ "\n" + formatter.stringFromDate(date)
}
}
else
{
self.locationManager.startUpdatingLocation()
if let lbutton = self.lbutton {
lbutton.text = "Hey " + "\(pm.subLocality)\n" // + formatter.stringFromDate(date)
}
}
}
else {
println("Problem with the data received from geocoder")
}
})
} else {
println("Cannot fetch your location")
}
}
What I would like, it to essentially pass through a NEW location and set a boolean of sorts so that if said boolean is set to false, the NEW location information is passed through the app.
I'm thinking about using a switch on the main viewcontroller, and changing the location on a secondary viewcontroller.
I'm sorry if this is a long post, but I feel as though I'm right on the edge of figuring out how to do this but I need to be sure I'm going in the right direction.
Maybe I could use a mapkit and use annotations?
I would not recommend using a view of any type to pass or hold information. A switch (like a UISwitch) should represent some other data in a graphical way for the user.
If you're new to mobile development, I'd recommend taking a look at the MVC design pattern. Here, are a couple different resources to get you started on that.
If you need to keep some data somewhere in your app that you can access from any class (viewController, view, custom object, etc.) then a Singleton would be a great solution. You can have map data that comes in be accessible across the entire app, you can store variables and be able to re-assign them from other classes, and you always end up with only one instance so your logical wires never get crossed.
Singletons get use fairly often, so I'd recommend looking at what the implementation code looks like and getting familiar with it. Here's a brief example of an implementation.
For your problem, the delegation pattern seems like the most appropriate choice.
If you create a protocol for your primary view controller that declares a method to update its data, such as locationDidUpdate:, then the primary view controller can be set as a delegate of the secondary view controller. The secondary view controller can then call this delegate method and update the primary view controller with new data.
As an example, a protocol can be declared in your first view controller like:
protocol LocationViewControllerDelegate: class {
func locationDidUpdate(location: CLLocation)
}
Then have your secondary view controller conform to the protocol and create a delegate property:
weak var delegate: LocationViewControllerDelegate?
When you need to update the location, call the delegate method with:
delegate.locationDidUpdate(myNewLocation)
Having a protocol for managing event-based updates is an advantage, in general, because it provides a clear contract that can easily be implemented by other classes.