Change Location Button Image - Google Maps iOS SDK - ios

I have got the Location function of my app working but I want to add a custom image for the Location Button. How do I go about doing that?

I created a whole new button and made it serve as a location button with this code:
#IBAction func myLocationButton(_ sender: UIButton) {
guard let lat = self.mapView.myLocation?.coordinate.latitude, let lng = self.mapView.myLocation?.coordinate.longitude else {
return
}
let camera = GMSCameraPosition.camera(withLatitude: lat, longitude: lng, zoom: 16)
self.mapView.animate(to: camera)
}
The final outcome:

Related

Custom google maps style takes a second to load

I'm implementing a custom style for my google maps view for a bus transit app I'm developing in XCode 9 w/ Swift 4. Whenever I load a map view, it always takes a little less than a second to load the custom style and I'm not sure what's causing this to happen.
Here's the effect I'm describing:
As you can see, the tan background is the default style for google's mapview, and it's visible for only a small period of time.
Here's my code that implements the map view:
class StopPredictionVC: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setUpMapView()
}
private func setUpMapView() {
let stopLatitude = Double(stop!.lat)
let stopLongitude = Double(stop!.lon)
let camera = GMSCameraPosition.camera(withLatitude: stopLatitude!, longitude: stopLongitude!, zoom: 16.4)
let frame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height)
mapView = GMSMapView.map(withFrame: frame, camera: camera)
let nightModeEnabled = APIWrapper.sharedInstance.settings.nightMode!
if nightModeEnabled {
mapView.mapStyle(withFilename: "nightStyle", andType: "json")
} else {
mapView.mapStyle(withFilename: "mapStyle", andType: "json")
}
let marker = GMSMarker()
marker.icon = #imageLiteral(resourceName: "marker")
marker.appearAnimation = .pop
marker.position = CLLocationCoordinate2D(latitude: stopLatitude!, longitude: stopLongitude!)
marker.title = "Bus Stop Name"
marker.snippet = "Example description"
marker.map = mapView
buildRoute(routePath: routeConfig!.path)
view.addSubview(mapView)
}
}
extension GMSMapView {
func mapStyle(withFilename name: String, andType type: String) {
do {
if let styleURL = Bundle.main.url(forResource: name, withExtension: type) {
self.mapStyle = try GMSMapStyle(contentsOfFileURL: styleURL)
} else {
NSLog("Unable to find style.json")
}
} catch {
NSLog("One or more of the map styles failed to load. \(error)")
}
}
}
buildRoute(routePath:) is a function that builds the blue colored path on the road if anyone was wondering.
Obviously this isn't a huge bug, but it's quite frustrating to see every time I load a map view. Anyone see anything in my code that could be causing this?
You call it inside viewWillAppear it has 2 problems , first it's called after viewDidLoad (has some delay) , second it may be called every time the VC is shown such as returning from a push / dismiss Modal
so call setUpMapView inside viewDidLoad
note this buildRoute(routePath: routeConfig!.path) should be in a background queue if it calls any web service or do any long job inside mainQueue
BTW i may also try to load
GMSMapStyle(contentsOfFileURL: styleURL)
in background thread and after load set in mainQueue and that only if using GMSMapStyle is allowed inside a back thread

Get back to a view with the same directions that existed before

well, guess this one is kinda big...
I'm making an app in which there will be a MapKit and a button. When the user clicks the button, he/she will open a new ViewController with several options of destinations to go. After confirming the one he wants to go, the MapKit view will reopen with directions for the specific place. To do this, I created a function on the ViewController linked to the MapKit view:
func createmap(latit: Double, longit: Double){
//set what's going to show up in the Map
MapView.delegate = self
MapView.showsScale = true
MapView.showsPointsOfInterest = true
MapView.showsUserLocation = true
//request authorization for user location data storage
locationManager.requestAlwaysAuthorization()
locationManager.requestWhenInUseAuthorization()
//if authorization is given, use the user location
if CLLocationManager.locationServicesEnabled(){
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
//set source and destination coordinates of direction recommendation
let sourceCoordinates = locationManager.location?.coordinate
let destinationCoords = CLLocationCoordinate2D(latitude: latit, longitude: longit)
//set placemarks with source and destination coordinates
let sourcePlacemark = MKPlacemark(coordinate: sourceCoordinates!)
let destPlacemark = MKPlacemark(coordinate: destinationCoords)
//put placemarks on maps with the source and destination coordinates
let sourceItem = MKMapItem(placemark: sourcePlacemark)
let destItem = MKMapItem(placemark: destPlacemark)
//set direction request, source and destination request coordinates and transport type
let directionRequest = MKDirectionsRequest()
directionRequest.source = sourceItem
directionRequest.destination = destItem
directionRequest.transportType = .automobile
//set response if sucess or error
let directions = MKDirections(request: directionRequest)
directions.calculate(completionHandler: {
response, error in
guard let response = response else {
if let error = error {
print("Something went wrong.")
}
return
}
//set format of route line
let route = response.routes[0]
self.MapView.add(route.polyline, level: .aboveRoads)
//set map framing
let rekt = route.polyline.boundingMapRect
self.MapView.setRegion(MKCoordinateRegionForMapRect(rekt), animated: true)
})
}
And then, to make this function run, I did this:
override func viewDidLoad() {
super.viewDidLoad()
createmap(latit: lat, longit: long)
}
lat and long are variables declared publicly.
In the other view, I have a button called "go back to the Map" in which I try to make a simple segue. However, there is a problem with this code: if I get directions to some place, reopen the other view and then press "go back to the Map", the directions that were set disappear. I don't know what to do to make them stay, can someone please help?
just a bit more information: the "go back to the Map" button is linked to the MapKit view as a simple segue (i clicked, dragged and dropped to the MapKit view, didn't write any line of code)
I think problem is your Map VC is already added into the navigation stack so ViewDidLoad method is not called again you should move your code from viewdidload to viewWill appear like
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
createmap(latit: lat, longit: long)
}
Did you used this Function to add a polyline? you added poly line correctly . but I think you had not provided values to poly line for drawing path
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.strokeColor = UIColor.blue
renderer.lineWidth = 4.0
return renderer
}

Move to current location when location button clicked in Esri map

Need to move to current location in Esri Map when user clicked on current location button. I try to find out but not able to find anything about it for Esri map
I do following code but it is not working
//MARK:
//MARK: current location button clicked
func btnCurLoc_Clicked(_ sender:UIButton)
{
arcGISMapView.locationDisplay.startDataSource()
}
If you want the map to automatically zoom to the current location you need to also set the autopan modeon the locationDisplay
to one of the options here
The most basic centering one is AGSLocationDisplayAutoPanModeRecenter.
swift 4 and Esri ios SDK 100.1 examples;
func locationOpen(){
self.mapView.locationDisplay.start { _ in
let x:Double = (self.mapView.locationDisplay.mapLocation?.x)!
let y:Double = (self.mapView.locationDisplay.mapLocation?.y)!
DispatchQueue.main.async {
if (self.mapView.locationDisplay.started == false)
{
// permission location
}
else
{ // zoom to geometry
self.mapView.setViewpointCenter(AGSPoint(x: x, y: y, spatialReference: AGSSpatialReference.webMercator()), scale: self.mapView.mapScale, completion: nil)
}
}
}
}

Zoom in on user location in iOS

I've been trying to solve this for a few hours and I can't seem to arrive at a solution. I am trying to create a button on my map view that zooms in on the users location when pressed. Here is the code for the function that pertains to the button:
func zoomInOnLocation() {
let userLocation = MKUserLocation()
let locationManager = CLLocationManager()
locationManager.requestWhenInUseAuthorization()
let currentLocation: CLLocation? = userLocation.location
let latitude = currentLocation?.coordinate.latitude
let longitude = currentLocation?.coordinate.longitude
let span: MKCoordinateSpan = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
let location: CLLocationCoordinate2D = CLLocationCoordinate2DMake(latitude!, longitude!)
let region: MKCoordinateRegion = MKCoordinateRegionMake(location, span)
mapView.setRegion(region, animated: true)
}
When I click the button in the simulator, I receive an error stating fatal error:
unexpectedly found nil while unwrapping an Optional value
with the fifth line mapDelegate.mapView!... highlighted in red. Also, I added the proper tag to Info.plist. Any help is much appreciated.
Check this :
if #available(iOS 9.0, *) {
locationManager.requestLocation()
} else {
// Fallback
}
let latitude:CLLocationDegrees = //insert latitutde
let longitude:CLLocationDegrees = //insert longitude
let latDelta:CLLocationDegrees = 0.05
let lonDelta:CLLocationDegrees = 0.05
let span = MKCoordinateSpanMake(latDelta, lonDelta)
let location = CLLocationCoordinate2DMake(latitude, longitude)
let region = MKCoordinateRegionMake(location, span)
mapView.setRegion(region, animated: false)
For more : Making the map zoom to user location and annotation (swift 2)
So you probably do not want to to interact with the mapView delegate as you are doing right now.
How about adding the delegates to the class of the view that holds the mapView, like this:
class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
and setting the delegates in viewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
mapView.delegate = self
}
And of course you want to have the locationManager and userLocation set up:
let locationManager = CLLocationManager()
var userLocation = CLLocation()
Notice that userLocation is a variable because most likely you would want to update it at some point.
This is how you probably want to work with locationManager:
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
You would make changes according to the needs of your app, and taking into account that the desired accuracy has an impact on battery life. Also, are you starting and stopping updates on userLocation? Because I do not see that in your code, unless you are doing it outside of this function.
A good practice is to try to minimize what an specific function does down to one task. You probably want to do all this setup elsewhere and then only zoom in inside the function. :)
Finally, in order to zoom in, you can change the values of MKCoordinateSpanMake, and remember that larger span values zoom in the map, so a smaller area is viewable.
let userLocationCoordinates = CLLocationCoordinate2D(latitude: userLocation.coordinate.latitude, longitude: userLocation.coordinate.longitude)
let span = MKCoordinateSpanMake(0.3, 0.3)
let region = MKCoordinateRegion(center: userLocationCoordinates, span: span)
mapView.setRegion(region, animated: true)
Hopefully that helps you out a bit, let me know how it goes!
Perhaps this will help. I created this function to zoom into an area defined by an array of positions, ranging from just the current user location out to the area around a set of points making up a polyline. The function provides for a buffer around the points based on a regionPaddingFactor constant set in my system constants.
func setRectView(_ locations: [MKAnnotation], mapView: MKMapView) // Size the area for display and reset the view
{
var maxLat = -90.0
var minLat = 90.0
var maxLon = -180.0
var minLon = 180.0
if locations.count >= 1 {
for waypoint in locations {
maxLat = max(maxLat, waypoint.coordinate.latitude)
minLat = min(minLat, waypoint.coordinate.latitude)
maxLon = max(maxLon, waypoint.coordinate.longitude)
minLon = min(minLon, waypoint.coordinate.longitude)
}
let loc = CLLocationCoordinate2DMake((maxLat-fabs(maxLat - minLat)/2), (maxLon-fabs(maxLon - minLon)/2))
let span = MKCoordinateSpanMake(0.001 + (1.0 + Setting.shared.regionPaddingFactor) * fabs(maxLat - minLat), 0.001 + (1.0 + Setting.shared.regionPaddingFactor) * fabs(maxLon-minLon))
// The 0.001 values above ensure that you do not get a 0.0 valued span if all of the points have the same latitude, longitude, or both, or if there is only one point
// The regionPaddingFactor is a constant to allow some space around the points passed in
let reg = MKCoordinateRegionMake(loc, span)
mapView.setRegion(reg, animated: true)
mapView.animatedZoom(zoomRegion: reg, duration: 0.8)
}
}
In the calling code, I provide for 3 settings that rotate as the user presses the button:
Show a tight view (just pass current location in the call)
Show the whole route (pass all points in the template)
Change to manual zooming / positioning
The last option is needed since I call the function whenever a new current position is received to reposition the view based on current location, which repeatedly refocuses the view if the user is trying to reposition the map.
If you don't want the flexibility of sending different position arrays, you can do the positioning here using mapView.annotations or just the current location as the array.

Google Maps API (GMS) map doesn't show up

Im trying to make the map automatically follow the user (location) but somehow the app either it crashes (no error message) or the map doesn't show up when I start up the app. What have I done wrong?
I have tried without the locationManager func, and then it does work. Are there any other ways to follow the user?
class GMSTestViewController: BaseViewController, GMSMapViewDelegate {
#IBOutlet weak var mapView: GMSMapView!
let locationManager = CLLocationManager()
var manager:CLLocationManager!
override func viewDidLoad() {
super.viewDidLoad()
//Setup Location Manager
manager = CLLocationManager()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
manager.requestAlwaysAuthorization()
manager.startUpdatingLocation()
//Map type
mapView.mapType = kGMSTypeTerrain
}
override func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLocation = locations.last
let center = CLLocationCoordinate2D(latitude: userLocation!.coordinate.latitude, longitude: userLocation!.coordinate.longitude)
let camera = GMSCameraPosition.cameraWithLatitude(userLocation!.coordinate.latitude,
longitude: userLocation!.coordinate.longitude, zoom: 8)
let mapView = GMSMapView.mapWithFrame(.zero, camera: camera)
mapView.myLocationEnabled = true
self.view = mapView
let marker = GMSMarker()
marker.position = center
marker.title = "Current Location"
marker.snippet = "XXX"
marker.map = mapView
locationManager.stopUpdatingLocation()
}
}
It looks like this when I start it up, and then it keeps blinking with the map (on the users location).
Check for these things :-
You have set the delegate of your GMSMapview #IBOutlet to self
mapView.delegate = self
You have updated your info.plist file with specific key's
You have a proper internet connection.
You have configured Google Maps in your AppDelegate
var googleApiKey = String(_yourAPIkey)
GMSServices.provideAPIKey(googleApiKey)
I believe the best way to do it is to use key-value observing (KVO):
override func viewWillAppear(animated: Bool) {
mapView.addObserver(self, forKeyPath: "myLocation", options:0, context:nil)
}
deinit {
mapView.removeObserver(self, forKeyPath:"myLocation", context:0)
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if(keyPath! == "myLocation"]) {
let location = [object myLocation]
let target =
CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude);
mapView.animateToLocation(target)
mapView.animateToZoom(17)
}
}
It seems that google map is not appear in your view.I think you want to get view like in image.
So i suggest you to download Google map SDK version below 1.10 because I am facing this problem too many times.
I suggest you to download google map SDK 1.9.1 for google map from below link
https://developers.google.com/maps/documentation/ios-sdk/releases
Just try to use version 1.9.1 once. Hope this thing works
NOTE: If you want to use Google map's place auto complete and other new services then use higher versions of Google map SDK
Had the same problem.
Fixed by getting new API Key.
As I discovered, you need two keys: one for Google Places, one for Google Maps. At least for testing purpose.
In Google docs it doesn't mention.

Resources