Weird behavior of custom annotations views - swift

I've built the same code in SwiftUI and Storyboard and I get the same weird issues.
Whenever I run and go to background and back to foreground, the custom views are lost.
Whenever I zoom-drag far (say 10km), I also lose the non-picture-holding pin, eg "Pencil".
I'm not sure where I've gone amiss, except maybe that I'd need to separate my pins per annotation view "type" (with a given picture, with text...) and set a different reuse identifier based on that, but it sounds weird given I'm not actually creating anything new (only setting properties of the base MKAnnotationView class).
Using the UIKit version as reference:
import UIKit
import MapKit
class MapViewController: UIViewController {
#IBOutlet weak var mapView: MKMapView!
let cityMapManager = CityMapManager()
let cityMapViewDelegate = CityMapViewDelegate()
let places: [Place] = [Place(name: "Cathedral", location: .init(latitude: 50.640364, longitude: 3.062058))]
override func viewDidLoad() {
super.viewDidLoad()
mapView.setRegion(MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 50.640364, longitude: 3.062058), latitudinalMeters: 500.0, longitudinalMeters: 500.0), animated: false)
mapView.showsUserLocation = true
mapView.showsScale = true
mapView.delegate = cityMapViewDelegate
annotateLocalPOI(map: mapView)
}
private func annotateLocalPOI(map: MKMapView) {
var annotation = MKPointAnnotation()
annotation.title = "Red Pin"
annotation.subtitle = "I'm a pin and I'm red. Maybe, or not."
annotation.coordinate = CLLocationCoordinate2D(latitude: 50.64, longitude: 3.06)
// add the annotation to the map (will use red pin, mapView:viewFor: allows changing the display)
map.addAnnotation(annotation)
annotation = MKPointAnnotation()
annotation.title = "Pencil"
annotation.subtitle = "Will not display"
annotation.coordinate = CLLocationCoordinate2D(latitude: 50.642, longitude: 3.059)
map.addAnnotation(annotation)
}
}
struct Place: Identifiable {
let id: UUID = UUID()
let name: String
let location: CLLocationCoordinate2D
}
class CityMapViewDelegate: NSObject, MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard annotation is MKPointAnnotation else { return nil }
let identifier = "Annotation"
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
if annotationView == nil {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView!.canShowCallout = true
if annotation.title == "Red Pin" {
let pinImage = UIImage(systemName: "timelapse")
annotationView?.image = pinImage
}
if annotation.title == "Pencil" {
// this overrules any subtitle available
annotationView?.detailCalloutAccessoryView = UIImageView(image: UIImage(systemName: "pencil"))
}
} else {
// reset
annotationView?.detailCalloutAccessoryView = nil
annotationView?.image = nil
// set it up again
annotationView?.annotation = annotation
if annotation.title == "Red Pin" {
let pinImage = UIImage(systemName: "timelapse")
annotationView?.image = pinImage
}
if annotation.title == "Pencil" {
// this overrules any subtitle available
annotationView?.detailCalloutAccessoryView = UIImageView(image: UIImage(systemName: "pencil"))
}
}
return annotationView
}
}
class CityLocationManagerDelegate: NSObject, CLLocationManagerDelegate {
var locationStatus = "Status not determined"
var locationFixFound = false
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// We've (at least once) gone under a certain minimum accuracy required to consider we have a "fix"
// we could use that to reflect that information on the UI (color, shape...)
if !locationFixFound {
for location in locations {
if location.horizontalAccuracy < 25.0 {
locationFixFound = true
}
}
}
}
func locationManager(_ manager: CLLocationManager,
didFailWithError error: Error) {
manager.stopUpdatingLocation()
print("Location manager stopped due to error: \(error)")
}
func locationManager(_ manager: CLLocationManager,
didChangeAuthorization status: CLAuthorizationStatus) {
print("Authorization Status changed")
var shouldRequestLocationUpdates = false
switch status {
case CLAuthorizationStatus.restricted:
locationStatus = "Restricted Access to location"
case CLAuthorizationStatus.denied:
locationStatus = "User denied access to location"
case CLAuthorizationStatus.notDetermined:
locationStatus = "Status not determined"
default:
locationStatus = "Allowed to location Access"
shouldRequestLocationUpdates = true
}
if (shouldRequestLocationUpdates == true) {
NSLog("Location to Allowed")
// Start location services
manager.startUpdatingLocation()
} else {
NSLog("Denied access: \(locationStatus)")
}
}
}
struct CityMapManager {
let locationDelegate: CLLocationManagerDelegate
let locationManager: CLLocationManager
init() {
locationDelegate = CityLocationManagerDelegate()
locationManager = CLLocationManager()
locationManager.delegate = locationDelegate
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.requestAlwaysAuthorization()
}
}
and a storyboard that holds a fullscreen map within a class set to MapViewController.

Related

iOS Mapkit Annotation Pin Shrinking

MapView is pin skrinking everytime. I'm using MKAnnotationView. Can anyone tell what what am I missing here why the pin shrinking? I want to show the pin like that star icon which is also annotation.
Also everytime I change the current location co-cordinate it will create new pin and old one also be there.
class CustomPointAnnotation: MKPointAnnotation {
var imageName: String!
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
let annotationView = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "MyMarker")
annotationView.canShowCallout = true
switch annotation.title! {
case "Your Current Location":
annotationView.glyphImage = UIImage(named: "user_location")
default:
annotationView.canShowCallout = true
annotationView.glyphImage = UIImage(named: "user_location")
let buttonPin = UIButton(type: .custom)
buttonPin.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
buttonPin.setImage(UIImage(named: "PhoneIcon"), for: .normal)
annotationView.rightCalloutAccessoryView = buttonPin
annotationView.markerTintColor = UIColor.purple
}
return annotationView
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
mapView.removeAnnotation(annotation)
let currentLocationValue = manager.location!.coordinate
let servicelocValue: CLLocationCoordinate2D = CLLocationCoordinate2DMake(coordinates.latitude, coordinates.longitude)
let annotation1 = CustomPointAnnotation()
annotation1.title = "Your Current Location"
annotation1.coordinate = currentLocationValue
annotation1.imageName = "user_location.png"
let annotation2 = CustomPointAnnotation()
annotation2.title = service ?? ""
annotation2.subtitle = location
annotation2.coordinate = servicelocValue
annotation2.imageName = "user_location.png"
annotations.append(annotation1)
annotations.append(annotation2)
mapView.addAnnotations(annotations)
mapView.cameraZoomRange = MKMapView.CameraZoomRange(minCenterCoordinateDistance: 50, maxCenterCoordinateDistance: 2000)
mapView.selectAnnotation(annotation2, animated: false)
}
}

Swift - How to update observed data in a (custom or not) MKAnnotation callout WITHOUT deselecting the annotation

I'm struggling to get KVO updates within a callout already displayed.
My use case: I want to display on an open callout the real time distance between user location and the annotation I add to the map. Annotation does not change its position.
I add annotations to mapView, using a custom annotation I have defined. No issue here.
On each annotation selected, the callout displays all the information defined in the custom annotation
However, the distance is refreshed in the callout ONLY if I unselect the annotation and reselect it
The distance property is declared as #objc dynamic so it can be observed.
I compute the distance each time the user location change. This part works too.
I cannot figure out what I'm missing to have the callout updated without closing and reopening it.
The code I'm using is what is described here by Rob: Swift -How to Update Data in Custom MKAnnotation Callout?
So my question: is it possible to change realtime a value (observed) in a notificationView callout ? If yes is KVO the best approach ?
In the link below, how would be implemented the mapView viewFor method ?
Any example would be very helpful.
It's my first post here, so please if I did it wrong, let me know and I will provide more information and details.
But my situation is trivial: the standard callout performs Key-Value Observation (KVO) on title and subtitle. (And the annotation view observes changes to coordinate.). But how to display change of values in the current open callout ? That is the think I do not get.
CustomAnnotation class:
class CustomAnnotation: NSObject, MKAnnotation {
#objc dynamic var title: String?
#objc dynamic var subtitle: String?
#objc dynamic var coordinate: CLLocationCoordinate2D
#objc dynamic var distance: CLLocationDistance
var poiColor: String?
var poiPhone: String?
init(title: String, subtitle: String, coordinate: CLLocationCoordinate2D, poiColor: String, poiPhone: String, distance: CLLocationDistance) {
self.title = title
self.subtitle = subtitle
self.coordinate = coordinate
self.poiColor = poiColor
self.poiPhone = poiPhone
self.distance = distance
super.init()
}
}
CustomAnnotationView class:
class CustomAnnotationView: MKMarkerAnnotationView {
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
displayPriority = .required
canShowCallout = true
detailCalloutAccessoryView = createCallOutWithDataFrom(customAnnotation: annotation as? CustomAnnotation)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
removeAnyObservers()
}
override var annotation: MKAnnotation? {
didSet {
removeAnyObservers()
if let customAnnotation = annotation as? CustomAnnotation {
updateAndAddObservers(for: customAnnotation)
}
}
}
private var subtitleObserver: NSKeyValueObservation?
private var distanceObserver: NSKeyValueObservation?
private let subtitleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
private let distanceLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
}
private extension CustomAnnotationView {
func updateAndAddObservers(for customAnnotation: CustomAnnotation) {
subtitleLabel.text = customAnnotation.subtitle
subtitleObserver = customAnnotation.observe(\.subtitle) { [weak self] customAnnotation, _ in
self?.subtitleLabel.text = customAnnotation.subtitle
}
let locationManager = CLLocationManager()
let theLatitude:CLLocationDegrees = (locationManager.location?.coordinate.latitude)!
let theLongitude:CLLocationDegrees = (locationManager.location?.coordinate.longitude)!
// Get pin location
let pointLocation = CLLocation(latitude: customAnnotation.coordinate.latitude, longitude: customAnnotation.coordinate.longitude)
//Get user location
let userLocation = CLLocation(latitude: theLatitude, longitude: theLongitude)
// Return distance en meters
let distanceFromUser = pointLocation.distance(from: userLocation)
customAnnotation.distance = distanceFromUser*100
distanceLabel.text = String(format: "%.03f", customAnnotation.distance)+" cm"
distanceObserver = customAnnotation.observe(\.distance) { [weak self] customAnnotation, _ in
self?.distanceLabel.text = "\(customAnnotation.distance) cm"
}
}
func removeAnyObservers() {
subtitleObserver = nil
distanceObserver = nil
}
func createCallOutWithDataFrom(customAnnotation: CustomAnnotation?) -> UIView {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = true
view.addSubview(subtitleLabel)
view.addSubview(distanceLabel)
NSLayoutConstraint.activate([
subtitleLabel.topAnchor.constraint(equalTo: view.topAnchor),
subtitleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor),
subtitleLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor),
subtitleLabel.bottomAnchor.constraint(equalTo: distanceLabel.topAnchor),
distanceLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor),
distanceLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor),
distanceLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
if let customAnnotation = customAnnotation {
updateAndAddObservers(for: customAnnotation)
}
return view
}
}
And to finish:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation { return nil }
let annotation = annotation as? CustomAnnotation
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "CustomAnnotation") as? CustomAnnotationView
if annotationView == nil {
annotationView = CustomAnnotationView(annotation: annotation, reuseIdentifier: "CustomAnnotation")
annotationView?.canShowCallout = true
} else {
annotationView?.annotation = annotation
}
return annotationView
}
Thank you.
You would appear to have correctly configured the observers for the subtitle and distance. The problem is that a change in location is not triggering an update to distance. Thus, there is nothing triggering the KVO.
You have an observer for distance, which will trigger an update of the label. But you are not changing distance. You should remove the CLLocationManager code from that routine where you add the observers, and instead create a location manager (not within the annotation view, though) which uses its delegate to update all of the annotation distances, e.g.:
class ViewController: UIViewController {
#IBOutlet weak var mapView: MKMapView!
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.distanceFilter = 5
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
}
extension ViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let currentLocation = locations.last(where: { $0.horizontalAccuracy >= 0 }) else { return }
mapView.annotations
.compactMap { $0 as? CustomAnnotation }
.forEach {
$0.distance = CLLocation(latitude: $0.coordinate.latitude, longitude: $0.coordinate.longitude)
.distance(from: currentLocation)
}
}
}
Obviously, you would remove the CLLocationManager code from updateAndAddObservers.

How to focus Google Maps camera on user's current location at startup using swiftUI views

I'm trying to make an app that uses Google Maps and focuses on the user's location when the app is opened.
Right now I have the map initializing and im able to focus on the users location after pressing the 'myLocation' button that is inherent to GoogleMaps, BUT the map's camera keeps focusing to a specified location and not the users location.
I used these 2 tutorials to get to where I'm at now:
- https://developers.google.com/maps/documentation/ios-sdk/start
- https://www.raywenderlich.com/197-google-maps-ios-sdk-tutorial-getting-started
After searching Google and here, it seems like I need to utilize CLLocationManager() to get the user's device coordinates and then use that somehow? Im think my code regarding CLLocationManager() may be placed in the wrong file or is used incorrectly, but im not getting any errors.
My code works like this: SceneDelegate.swift sets my LandmarkList.swift as the rootViewController. Then the LandmarkList calls GoogMapView.swift to display the instance of Google maps.
SceneDelegate.swift:
I think my usage of locationManager here may be wrong?
import UIKit
import SwiftUI
import GoogleMaps
import GooglePlaces
import CoreLocation
class SceneDelegate: UIResponder, UIWindowSceneDelegate, CLLocationManagerDelegate {
var window: UIWindow?
private let locationManager = CLLocationManager()
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
// Use a UIHostingController as window root view controller
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: LandmarkList())
self.window = window
window.makeKeyAndVisible()
}
locationManager.requestAlwaysAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
}
}
}
LandmarkList.swift:
import SwiftUI
struct LandmarkList: View {
#State private var searchText = ""
#State private var locationText = ""
var body: some View {
ZStack(alignment: Alignment.top) {
GoogMapView()
.frame(height: 750)
SlideOverCard {
VStack(alignment: .leading) {
List(landmarkData) { landmark in
NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
LandmarkRow(landmark: landmark)
}
}
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: Alignment.topLeading)
}
}
}
}
GoogMapView.swift:
Note: The print statement below only returns 'User's location is unknown'
import SwiftUI
import UIKit
import GoogleMaps
import GooglePlaces
import CoreLocation
struct GoogMapView : UIViewRepresentable {
let marker : GMSMarker = GMSMarker()
//Creates a `UIView` instance to be presented.
func makeUIView(context: Context) -> GMSMapView {
// Create a GMSCameraPosition
let camera = GMSCameraPosition.camera(withLatitude: 42.361145, longitude: -71.057083, zoom: 16.0)
let mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
mapView.setMinZoom(14, maxZoom: 20)
mapView.settings.compassButton = true
mapView.isMyLocationEnabled = true
mapView.settings.myLocationButton = true
mapView.settings.scrollGestures = true
mapView.settings.zoomGestures = true
mapView.settings.rotateGestures = true
mapView.settings.tiltGestures = true
mapView.isIndoorEnabled = false
if let mylocation = mapView.myLocation {
print("User's location: \(mylocation)")
} else {
print("User's location is unknown")
}
return mapView
}
// Updates the presented `UIView` (and coordinator) to the latestconfiguration.
func updateUIView(_ mapView: GMSMapView, context: Context) {
// Creates a marker in the center of the map.
marker.position = CLLocationCoordinate2D(latitude: 42.361145, longitude: -71.057083)
marker.title = "Boston"
marker.snippet = "USA"
marker.map = mapView
}
}
Again, I think my code regarding locationManager in SceneDelegate.swift would make the instance of GoogleMaps camera focus on the users location at startup, but it doesnt.
Anyone know what im doing wrong?
I was finally able to figure out how to focus on "my location" immediately, instead of requiring input from the user. Here are some more details...hope it helps!
updateUIView
You want updateUIView to be called when the user's location is retrieved. From what I have read updateUIView is called when a #State or #Binding object changes (or #ObservedObject). So, the UIViewRepresentable needs to have one of these. In the case of my code below, I use an #ObservedObject. Whenever one of the #ObservedObject's #Published properties changes, updateUIView will be called.
Creating The Observed Object
I created a class (LocationManager) that conforms to the ObservableObject protocol.
Within the class, I exposed the lastKnownLocation as a #Published property. As mentioned above, when the lastKnownLocation is updated, any subscribers to an instance of the LocationManager class will see those updates
Getting The User's Coordinates
CLLocationManager is used to get updates on the user's location.
The LocationManager class I created implements the delegate functions for CLLocationManager
The delegate function didUpdateLocations is called when the user's location is updated. Within that function, I then update the #Published property so that the UIViewRepresentable sees those changes, and updateUIView is automatically called accordingly.
The UIViewRepresentable
import SwiftUI
import GoogleMaps
import Combine
struct HomeView: UIViewRepresentable {
// Listen to changes on the locationManager
#ObservedObject var locationManager = LocationManager()
func makeUIView(context: Self.Context) -> GMSMapView {
// Just default the camera to anywhere (this will be overwritten as soon as myLocation is grabbed
let camera = GMSCameraPosition.camera(withLatitude: 0, longitude: 0, zoom: 16.0)
let mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
mapView.setMinZoom(14, maxZoom: 20)
mapView.settings.compassButton = true
mapView.isMyLocationEnabled = true
mapView.settings.myLocationButton = true
mapView.settings.scrollGestures = true
mapView.settings.zoomGestures = true
mapView.settings.rotateGestures = true
mapView.settings.tiltGestures = true
mapView.isIndoorEnabled = false
return mapView
}
func updateUIView(_ mapView: GMSMapView, context: Self.Context) {
// When the locationManager publishes updates, respond to them
if let myLocation = locationManager.lastKnownLocation {
mapView.animate(toLocation: myLocation.coordinate)
print("User's location: \(myLocation)")
}
}
}
The LocationManager class:
class LocationManager: NSObject, CLLocationManagerDelegate, ObservableObject {
// Publish the user's location so subscribers can react to updates
#Published var lastKnownLocation: CLLocation? = nil
private let manager = CLLocationManager()
override init() {
super.init()
self.manager.delegate = self
self.manager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
self.manager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// Notify listeners that the user has a new location
self.lastKnownLocation = locations.last
}
}
Using comments here and others on StackOverflow, I made this solution:
//
// GoogMapView.swift
// Landmarks
//
// Created by Zahr Lyttle on 10/14/19.
// Copyright © 2019 Apple. All rights reserved.
//
import SwiftUI
import UIKit
import GoogleMaps
import GooglePlaces
import CoreLocation
import Foundation
struct GoogMapView: View {
var body: some View {
GoogMapControllerRepresentable()
}
}
class GoogMapController: UIViewController, CLLocationManagerDelegate {
var locationManager = CLLocationManager()
var mapView: GMSMapView!
let defaultLocation = CLLocation(latitude: 42.361145, longitude: -71.057083)
var zoomLevel: Float = 15.0
let marker : GMSMarker = GMSMarker()
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.distanceFilter = 50
locationManager.startUpdatingLocation()
locationManager.delegate = self
let camera = GMSCameraPosition.camera(withLatitude: defaultLocation.coordinate.latitude, longitude: defaultLocation.coordinate.longitude, zoom: zoomLevel)
mapView = GMSMapView.map(withFrame: view.bounds, camera: camera)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.isMyLocationEnabled = true
mapView.setMinZoom(14, maxZoom: 20)
mapView.settings.compassButton = true
mapView.isMyLocationEnabled = true
mapView.settings.myLocationButton = true
mapView.settings.scrollGestures = true
mapView.settings.zoomGestures = true
mapView.settings.rotateGestures = true
mapView.settings.tiltGestures = true
mapView.isIndoorEnabled = false
// if let mylocation = mapView.myLocation {
// print("User's location: \(mylocation)")
// } else {
// print("User's location is unknown")
// }
marker.position = CLLocationCoordinate2D(latitude: 42.361145, longitude: -71.057083)
marker.title = "Boston"
marker.snippet = "USA"
marker.map = mapView
// Add the map to the view, hide it until we've got a location update.
view.addSubview(mapView)
// mapView.isHidden = true
}
// Handle incoming location events.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location: CLLocation = locations.last!
print("Location: \(location)")
let camera = GMSCameraPosition.camera(withLatitude: location.coordinate.latitude, longitude: location.coordinate.longitude, zoom: zoomLevel)
if mapView.isHidden {
mapView.isHidden = false
mapView.camera = camera
} else {
mapView.animate(to: camera)
}
}
// Handle authorization for the location manager.
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .restricted:
print("Location access was restricted.")
case .denied:
print("User denied access to location.")
// Display the map using the default location.
mapView.isHidden = false
case .notDetermined:
print("Location status not determined.")
case .authorizedAlways: fallthrough
case .authorizedWhenInUse:
print("Location status is OK.")
}
}
// Handle location manager errors.
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
locationManager.stopUpdatingLocation()
print("Error: \(error)")
}
}
struct GoogMapControllerRepresentable: UIViewControllerRepresentable {
func makeUIViewController(context: UIViewControllerRepresentableContext<GMControllerRepresentable>) -> GMController {
return GMController()
}
func updateUIViewController(_ uiViewController: GMController, context: UIViewControllerRepresentableContext<GMControllerRepresentable>) {
}
}
You can implement this by "pretending" your View is a ViewController. Try setting GoogleMapView as the delegate, instead. Then put your initialization code in makeUIView and then conform to the protocol:
struct GoogMapView : CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
guard status == .authorizedWhenInUse else {
return
}
locationManager.startUpdatingLocation()
mapView.isMyLocationEnabled = true
mapView.settings.myLocationButton = true
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else {
return
}
mapView.camera = GMSCameraPosition(target: location.coordinate, zoom: 15, bearing: 0, viewingAngle: 0)
locationManager.stopUpdatingLocation()
}
}
Above taken from https://www.raywenderlich.com/197-google-maps-ios-sdk-tutorial-getting-started.

How to Display User's Place on Snippet Google Maps Swift

I have google map and want to display user's place (like city) in snippet.
How to do that?
here's my current code:
class ViewController: UIViewController, GMSMapViewDelegate, CLLocationManagerDelegate{
#IBOutlet weak var mapView: GMSMapView!
var latitude = -7.034323799999999
var longitude = 110.42400399999997
var locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
mapView.delegate = self
let camera = GMSCameraPosition.camera(withLatitude: Double(latitude), longitude: Double(longitude), zoom: 17)
mapView.animate(to: camera)
let markerImage = UIImage(named: "ic_home_detail_marker_location")
let markerView = UIImageView(image: markerImage)
let marker = GMSMarker()
marker.position = CLLocationCoordinate2DMake(Double(latitude), Double(longitude))
marker.isDraggable = true
marker.snippet = "\(marker.position)"
mapView.selectedMarker = marker
marker.iconView = markerView
mapView.selectedMarker = marker
marker.map = mapView
}
}
If you want to get the user's city or state name you have to use CLGeocoder.
var currentLatitude:Double!
var currentLongitude:Double!
var cityName:String!
var stateName:String!
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("locationManager function called")
// Fetch current location coordinates
let locValue:CLLocationCoordinate2D = (locationManager.location?.coordinate)!
currentLatitude = locValue.latitude
currentLongitude = locValue.longitude
print("Current Location = \(currentLatitude!), \(currentLongitude!)")
// Zoom to current location
let camera: GMSCameraPosition = GMSCameraPosition.camera(withLatitude: currentLatitude!, longitude: currentLongitude!, zoom: 17.0)
viewMap.camera = camera
// check for current city
CLGeocoder().reverseGeocodeLocation(locationManager.location!, completionHandler: {(placemarks, error) -> Void in
if error != nil {
print("Reverse geocoder failed with error" + (error?.localizedDescription)!)
return
}
if (placemarks?.count)! > 0 {
let pm = placemarks?[0]
self.cityName = (pm?.locality)!
self.stateName = (pm?.administrativeArea)
print("Current City: \(self.cityName!)")
print("Curret State: \(self.stateName!)")
}
else {
print("Problem with the data received from geocoder")
}
})
locationManager.stopUpdatingLocation()
}
Now you have the current city stored in a variable.
The next step is that when the user touches a marker, it should display the cityname. For this to achieve implement this:
This delegate must be added:
GMSMapViewDelegate
and this is the marker function, when user taps on it.
func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
mapView.delegate = self
marker.snippet = ("Current city: \(cityName!)")
// return false so as to show the marker details or
// return true to hide marker details.
return false
}

Save map annotation using UserDefaults

I have been searching relentlessly for this solution and left with no choice except to rely on the expertise here on Stackoverflow. I am working on saving a map annotation if the app closes and I have been using the UserDefault to save the annotation.
This is the Objective C code that I found and I tried converting it into Swift and I think there is an error with it. I am not too sure. I place this code at viewDidLoad()
This is the save annotation
var pinnedAnnotation: CLLocationCoordinate2D = (parkedCarAnnotation?.coordinate)!
var coordinateData: NSData = NSData(bytesNoCopy: pinnedAnnotation, length: sizeof(pinnedAnnotation), freeWhenDone: false)
UserDefaults.standard.set(coordinateData, forKey: pinnedAnnotation)
UserDefaults.standard.synchronize()
And I needed a load annotation when the app open.
I dont know if viewDidLoad is the right place to put. Previously I put it in a mapView function of updatingLocation
Edited: Added code for further clarification of what I have done that needed to be corrected
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self as MKMapViewDelegate
checkLocationAuthorizationStatus()
tabBar.delegate = self
if UserDefaults.standard.object(forKey: "pinnedAnnotation") != nil {
let annotation = MKPointAnnotation()
self.mapView.addAnnotation(annotation)
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let pinnedAnnotation: CLLocationCoordinate2D = (parkedCarAnnotation?.coordinate)!
let locationData = ["latitude": parkedCarAnnotation?.coordinate.latitude, "longitude": parkedCarAnnotation?.coordinate.longitude]
UserDefaults.standard.set(locationData, forKey: "pinnedAnnotation")
UserDefaults.standard.synchronize()
print("Saving data ", UserDefaults.standard.set(locationData, forKey: "pinnedAnnotation"))
}
func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
if(item.tag == 0){
if mapView.annotations.count == 1{
mapView.addAnnotation(parkedCarAnnotation!)
} else {
mapView.removeAnnotation(mapView.annotations as! MKAnnotation)
}
}
extension ViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if let annotation = annotation as? ParkingSpot{
let identifier = "pin"
var view: MKPinAnnotationView
view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
view.canShowCallout = true
view.animatesDrop = true
view.pinTintColor = UIColor.orange
view.calloutOffset = CGPoint(x: -8, y: -3)
view.rightCalloutAccessoryView = UIButton.init(type:.detailDisclosure) as UIView
return view
} else {
return nil
}
}
extension ViewController: CLLocationManagerDelegate{
func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) {
centerMapOnLocation(location: CLLocation(latitude: userLocation.coordinate.latitude, longitude: userLocation.coordinate.longitude))
let locationServiceCoordinate = LocationService.instance.locationManager.location!.coordinate
parkedCarAnnotation = ParkingSpot(title: "My Parking Spot", locationName: "Find your way back to your car location", coordinate: CLLocationCoordinate2D(latitude: locationServiceCoordinate.latitude, longitude: locationServiceCoordinate.longitude))
}
}
I'm not entirely sure what you're asking...
For starters when you're saving data to UserDefaults the key needs to be a string, I also believe you'll need to save you data in UserDefaults as a Dictionary
let locationData = ["lat": parkedCarAnnotation?.coordinate?.latitude, "long": parkedCarAnnotation?.coordinate.longitude]
UserDefaults.standard.set(locationData, forKey: "pinned_annotation")
And then to retrieve the data you would call
if let annotationData = UserDefaults.standard.object(forKey: "pinned_annotation") as? Dictionary {
guard let lat = annotationData["lat"], let long = annotationData["long"] else { return }
let coordinate = CLLocationCoordinate2D(latitude: lat, longitude: long)
}
Now you should hopefully be able to set your annotation with the coordinate