Search code examples
swiftcllocationmanagerswiftuigoogle-maps-sdk-iosxcode11

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?


Solution

  • 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>) {
    
        }
    }